Build OAuth2.0 authentication Authorization server using Owin middleware

Source: Internet
Author: User
Tags oauth tag name

Objective

Here the main summary of my recent half-month on the construction of OAUTH2.0 server work experience. As to why the need for OAuth2.0, why is Owin, what is the Owin and so on, no longer repeat. I assume that the reader is using ASP. and need to build a OAuth2.0 server that has a basic understanding of the Claims Based authentication, Owin, OAuth2.0, and other knowledge points involved. If you do not understand, please refer to the following articles first:

    • Mvc5-asp.net Identity Login principle-claims-based authentication and Owin
    • Next Generation ASP Owin (1)--owin Background and brief introduction
    • Understanding OAuth 2.0
    • rfc6749
Where to start?

After a preliminary understanding of the points listed in the preface, where do we begin?
A Demo:owin OAuth 2.0 Authorization Server is recommended here
In addition to the demo, it is recommended to prepare Katanaproject source code

Next, we mainly look at this demo

Demo:authorization Server

From the RFC documentation for OAUTH2.0, we know that OAuth has multiple licensing modes, and this is just about the authorization code approach.
First look at the authorization server project, which has three chunks:

    • Clients
    • Authorization Server
    • Resource Server

Illustrated by RFC6749:
Clients corresponding to various licensing methods of the client, here we only see the corresponding authorization code mode of the Authorizationcodegrant project;
Authorization server is the authentication authorization server that provides the OAuth service;
Resource server is the resource server that the client has Accesstoken access to after Accesstoken (this simply provides a/api/me display of the user's name).
Also note the constants project, which sets some key data, including the interface address and the client ID and secret.

Client:authorizationcodegrant

The Authorizationcodegrant project uses a Webserverclient class in the Dotnetopenauth.oauth2 package as the client for communication with authorization server.
(This is due to the fact that it encapsulates some of the underlying details so that you may encounter a few pits when you do not use this package and authorization server interaction)
Here are a few key points to look at:

1. After running the project, the page appears, click the "Authorize" button, redirect the user to Authorization Server for the first time

if (!string.IsNullOrEmpty(Request.Form.Get("submit.Authorize"))){    var userAuthorization = _webServerClient.PrepareRequestUserAuthorization(new[] { "bio", "notes" });    userAuthorization.Send(HttpContext);    Response.End();}

Here new[] {"Bio", "notes"} is the scopes that needs to be requested, or the interface identifier of resource server, or interface permissions. Then send (HttpContext) is redirected.

2. Regardless of the situation after redirecting users to authorization server, assuming that the user has completed the authorization operation on authorization server, authorization server redirects the user to the client, where The specific callback address is the page where you clicked the "Authorize" button, and the URL has a one-time code parameter for the client to initiate a request from the server to the authorization server for code Exchange Accesstoken. The key code is as follows:

if (string.IsNullOrEmpty(accessToken)){    var authorizationState = _webServerClient.ProcessUserAuthorization(Request);    if (authorizationState != null)    {        ViewBag.AccessToken = authorizationState.AccessToken;        ViewBag.RefreshToken = authorizationState.RefreshToken;        ViewBag.Action = Request.Path;    }}

We found that this code would also be triggered when we clicked on authorize, but there was no code parameter at that time (Request was _webserverclient.processuserauthorization if code was missing). does not initiate a request), so Accesstoken is not taken.

3. After getting Accesstoken, the rest is to call Api,callapi, try to find the return is just the user login authorization server used by the user name (Resource server details later).

4. At this point, the client side of the Code analysis is complete (Refreshtoken please try, self-understanding). Without complex content, according to RFC6749 's design, the client needs only these steps. For the client part, the only thing that needs to be solemnly reminded again is that Accesstoken must not be disclosed, such as unencrypted in the browser cookie.

Easy after the difficult, and then look at resource Server

Let's put authorization server in the first place and then look at the resource server.
Resource server is very simple and there is only one code in the Startup.auth configuration in App_start:

app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions());

Then, the only controller Mecontroller is also very simple:

[Authorize]public class MeController : ApiController{    public string Get()    {        return this.User.Identity.Name;    }}

Effective code on these, the implementation of non-user authorization can not be accessed, authorized to obtain the user login user name. (In fact, there is a key configuration in Webconfig, and later)

So what does the code in Startup.auth mean? Why does the client access the API while User.Identity.Name is the login name of the authorized user instead of the client's login name?

Let's look at the first question and find the Useoauthbearerauthentication () method. Specifically how to find the nonsense, I directly explain its source code location in the Katana project source code in the Security directory of the Microsoft.Owin.Security.OAuth project. An extension method for Iappbuilder is in the OAuthBearerAuthenticationExtensions.cs file. And this extension method is actually set up a oauthbearerauthenticationmiddleware, in order to parse for Accesstoken. The result of the parsing is similar to the client's access to the API interface with the authorization user's identity (that is, the second problem, User.Identity.Name is the login name of the authorized user), and obtains the information data belonging to that user.

There is only so much to know about resource Server.
(For interface validation scopes, get user primary key, add custom tag in Accesstoken, etc., after you have seen authorization server)

Authorization Server

Authorization server is the core and most complex part of this article.

Startup.auth configuration section

First look at the Startup.Auth.cs file for the authorization server project, the settings on the OAuth2.0 service side are here.

// Enable Application Sign In Cookieapp.UseCookieAuthentication(new CookieAuthenticationOptions{    AuthenticationType = "Application", //这里有个坑,先提醒下    AuthenticationMode = AuthenticationMode.Passive,    LoginPath = new PathString(Paths.LoginPath),    LogoutPath = new PathString(Paths.LogoutPath),});

Since here, first remind this setting: AuthenticationType is the user login authorization server after the login credentials of the tag name, simply understand the cookie key name on the line. Why do I have to remind you, because this and oauth/authorize check whether the user is currently logged in has a relationship, sometimes the default setting of this value may be "Applicationcookie".

OK, formally look at the settings of the Oauthserver section:

 Setup Authorization serverapp.useoauthauthorizationserver (New oauthauthorizationserveroptions{ Authorizeendpointpath = new PathString (paths.authorizepath), Tokenendpointpath = new PathString (Paths.TokenPath), Ap Plicationcandisplayerrors = True, #if DEBUG allowinsecurehttp = true,//important!! The settings here include whether the entire process communication environment is enabled SSL#ENDIF//Authorization server provider which controls the lifecycle of Authorization server P        Rovider = new Oauthauthorizationserverprovider {Onvalidateclientredirecturi = Validateclientredirecturi, Onvalidateclientauthentication = validateclientauthentication, ongrantresourceownercredentials = GrantResourceOwne Rcredentials, ongrantclientcredentials = grantclientcredetails},//Authorization code provider which create s and receives authorization code Authorizationcodeprovider = new Authenticationtokenprovider {OnCreate = Cr Eateauthenticationcode, onreceive = Receiveauthenticationcode,},//RefreSH token provider which creates and receives Referesh token Refreshtokenprovider = new Authenticationtokenprovider { OnCreate = Createrefreshtoken, OnReceive = Receiverefreshtoken,}});
In a paragraph we look at:
...AuthorizeEndpointPath = new PathString(Paths.AuthorizePath),TokenEndpointPath = new PathString(Paths.TokenPath),...

With these two endpointpath, there is no need to rewrite the Oauthauthorizationserverprovider Matchendpoint method (if you inherit it, write your own serverprovider, Otherwise, you can also set Onmatchendpoint to achieve and override the same effect).
Conversely, if your endpointpath is more complex, such as the previous possibility to carry culture information because of internationalization, you can customize it with the override Matchendpoint method.
But remember, after rewriting matchendpoint (or setting up onmatchendpoint), I recommend commenting out these two line assignment statements. As for why, please look at katana The Microsoft.Owin.Security.OAuth project under the Security directory in Project source code OAuthAuthorizationServerHandler.cs lines 38th through 46th.
By the way, if your project uses some global filters, decide if you want to avoid both paths (Authorizeendpointpath is the authorize method in the corresponding OAuth controller The Tokenendpointpath is the Oauthauthorizationserver middleware that is completely configured here).

ApplicationCanDisplayErrors = true, #if DEBUG    AllowInsecureHttp = true, //重要!!这里的设置包含整个流程通信环境是否启用ssl#endif

Here the first line is not much to say, the literal meaning of understanding.
Important!! Allowinsecurehttp Sets whether SSL is enabled for the entire communication environment, not only the OAuth service side, but also the client side (when set to False, if the registered client end multiplicity directed URL is not HTTPS, no redirection is made. If you step on the pit, the problem is difficult to locate and experience.

// Authorization server provider which controls the lifecycle of Authorization ServerProvider = new OAuthAuthorizationServerProvider{    OnValidateClientRedirectUri = ValidateClientRedirectUri,    OnValidateClientAuthentication = ValidateClientAuthentication,    OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials,    OnGrantClientCredentials = GrantClientCredetails}

Here is the core provider, all on the beginning, is actually the delegate method, the middleware defines the OAuth2 a set of processes, but it has several key events in the way of the delegation exposed.

    • Onvalidateclientredirecturi: Verify client redirect URL, this is for security, anti-phishing
    • Onvalidateclientauthentication: Verifying the identity of the client (ClientID and Clientsecret)
    • Ongrantresourceownercredentials and Ongrantclientcredentials are the other two licensing methods available in this demo, and are not covered in this article.

Specific to the role of these delegates, we then look at the code for the corresponding method:

//验证重定向url的private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context){    if (context.ClientId == Clients.Client1.Id)    {        context.Validated(Clients.Client1.RedirectUrl);    }    else if (context.ClientId == Clients.Client2.Id)    {        context.Validated(Clients.Client2.RedirectUrl);    }    return Task.FromResult(0);}

Here context. ClientID is the ClientID obtained in the context of the OAuth2 processing process, and Clients.Client1.Id is the pre-defined test data in the constants project that was previously said. If we have a client registration mechanism, then the Clients.Client1.Id corresponding CLIENTS.CLIENT1.REDIRECTURL may be read from the database. The RedirectURL read in the database can be passed directly to context.validated (RedirectURL) as a string parameter. In this way, this part of the logic is even over.

//验证Client身份private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){    string clientId;    string clientSecret;    if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||        context.TryGetFormCredentials(out clientId, out clientSecret))    {        if (clientId == Clients.Client1.Id && clientSecret == Clients.Client1.Secret)        {            context.Validated();        }        else if (clientId == Clients.Client2.Id && clientSecret == Clients.Client2.Secret)        {            context.Validated();        }    }    return Task.FromResult(0);}

Similar to the above authentication redirect URL, this is the authentication client. But pay special attention to two tryget methods, these two tryget methods correspond to how Oauth2server receive client authentication information (this demo uses the encapsulated client, do not encounter this problem, Previously, this is the kind of pit you might encounter without using a webserverclient class in the Dotnetopenauth.oauth2 package.

    • Trygetbasiccredentials: means that the client can submit ClientID and Clientsecret in accordance with the rules for Basic authentication
    • Trygetformcredentials: means that the client can submit ClientID and Clientsecret in the form form of the POST request

So when do you need client to submit ClientID and Clientsecret? In front of the client to take a one-time code parameter to the OAuth server-side Exchange Accesstoken.
Basic identity authentication, reference RFC2617
The basic simple explanation is to add an HTTP Header as follows:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== //这只是个例子

Where basic is followed by a string of Clientid:clientsecret form BASE64 encoded string, authorization is the key name of the HTTP header, and basic to the end is the value of the header.
Form this as long as the attention of two key names is client_id and Client_secret.

 private readonly ConcurrentDictionary<string, string> _authenticationCodes =        new ConcurrentDictionary<string, string>(StringComparer.Ordinal);    private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)    {        context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));        _authenticationCodes[context.Token] = context.SerializeTicket();    }    private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)    {        string value;        if (_authenticationCodes.TryRemove(context.Token, out value))        {            context.DeserializeTicket(value);        }    }

Here is to correspond to the previous said to exchange Accesstoken code parameter generation and validation, with Concurrentdictionary is for thread safety; _ Authenticationcodes.tryremove is a one - time code that has been highlighted before, and is deleted after validation.

private void CreateRefreshToken(AuthenticationTokenCreateContext context){    context.SetToken(context.SerializeTicket());}private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context){    context.DeserializeTicket(context.Token);}

This handles the generation and reception of Refreshtoken, simply invoking token's cryptographic settings and decryption methods.

At this point, the basic end of the Startup.auth section, we'll look at the OAuth controller section next.

OAuth Controller

There is only one action in Oauthcontroller, which is authorize.
The authorize method does not differentiate between httpget or httppost, the main reason may be the method signature (the action has the same name, even if the HttpGet and HttpPost are set unless the arguments are different). The compiler will also think that you have defined two identical actions, which may be a little trickier if we want to take them apart.

It's still a paragraph.
if (Response.StatusCode != 200){    return View("AuthorizeError");}

To tell the truth, I still do not understand why to judge the next 200, it is possible to consider the Owin middleware will deal with the point in advance what? I didn't see anything unusual, or I didn't notice ... This paragraph is dispensable.

var authentication = HttpContext.GetOwinContext().Authentication;var ticket = authentication.AuthenticateAsync("Application").Result;var identity = ticket != null ? ticket.Identity : null;if (identity == null){    authentication.Challenge("Application");    return new HttpUnauthorizedResult();}

Here is to determine whether authorized users have landed, this is very simple logic, the landing part can be used with aspnet.identity that set, and the key is Authentication.authenticateasync ("Application") in the " Application ", remember, that's the cookie name you said before:

...AuthenticationType = "Application", //这里有个坑,先提醒下...

This is to match, otherwise the user logged in, to the OAuth controller here may still be considered to be not logged in.
If the user logs in, the identity here will have a value.

 var scopes = (Request.QueryString.Get("scope") ?? "").Split(‘ ‘);

This sentence is just to get the client application scopes, or permission (separated by a space feeling a bit strange, do not know is not the standard in OAuth2.0).

if (Request.HttpMethod == "POST"){    if (!string.IsNullOrEmpty(Request.Form.Get("submit.Grant")))    {        identity = new ClaimsIdentity(identity.Claims, "Bearer", identity.NameClaimType, identity.RoleClaimType);        foreach (var scope in scopes)        {            identity.AddClaim(new Claim("urn:oauth:scope", scope));        }        authentication.SignIn(identity);    }    if (!string.IsNullOrEmpty(Request.Form.Get("submit.Login")))    {        authentication.SignOut("Application");        authentication.Challenge("Application");        return new HttpUnauthorizedResult();    }}

Here, submit. The grant branch is the logic that handles authorization, in fact it is very intuitive to add claims to identity. So where did claims go? What's the use of it?
This needs to go back to see Resourceserver, the following is the key content:

其实Client访问ResourceServer的api接口的时候,除了AccessToken,不需要其他任何凭据。那么ResourceServer是怎么识别出用户登陆名的呢?关键就是claims-based identity 这套东西。其实所有的claims都加密存进了AccessToken中,而ResourceServer中的OAuthBearer中间件就是解密了AccessToken,获取了这些claims。这也是为什么之前强调AccessToken绝对不能泄露,对于ResourceServer来说,访问者拥有AccessToken,那么就是受信任的,颁发AccessToken的机构也是受信任的,所以对于AccessToken中加密的内容也是绝对相信的,所以,ResourceServer这边甚至不需要再去数据库验证访问者Client的身份。

It is mentioned here that the institution that issued the Accesstoken is also trusted, what does it mean? We see that Accesstoken is encrypted, so how do we decrypt it? The key is that a consistent MachineKeyis configured in the Authorizationserver project and the Resourceserver project's Web. config.
(Off-topic, there is an online MachineKey generator: MachineKey generator, here also mention, if you do not like configuration MachineKey, you can study how to rewrite Accesstoken and Refreshtoken encryption decryption process, Not much to say here, hint: There are several attributes in the oauthauthorizationserveroptions with the format suffix
The above-mentioned machinekey is the encryption and decryption key of the system's default Accesstoken and Refreshtoken.

Submit. The login branch does not say much, meaning that the user to change an account login.

Having written so much, the basic analysis is over, and let's see what else we need.

First, you need a custom authorize property for validating scopes in Resourceserver, and here are two points to note:

    1. Webapi authorize and MVC authorize not the same (at least MVC5, this is still two things, vnext to time scrutiny;
    2. How to dig out the defined claims from the user.identity of Resourceserver.

1th, the need to rewrite the method is not Authorizecore (the method name is forgotten, do not know if there is no write wrong), but Onauthorize (IBID., there is an empty vs verify the next change), and need to call base. Onauthorize.
2nd, as follows:

var claimsIdentity = User.Identity as ClaimsIdentity;claimsIdentity.Claims.Where (c => c.Type == "urn:oauth:scope").ToList();

Then, there is a resourceserver commonly used things, is the user information of the primary key, generally can be obtained from the User.Identity.GetUserId (), but this method is an extension method, requires using Microsoft.AspNet.Identity. Why is it possible to use it here? Is the claims contains the user information of the primary key, not the letter can be debugged under the check (note add claims that code, will login after the original claims also add in, here contains the user login name and user primary key UserID).

The practice will really progress

This is a lot of writing, the basic of their own tread on the hole should be written, there is no time to look back to see if there are missing. We'll be here today, over.

Additional

Follow-up practice found that because of the use of Owin middleware, Resourceserver relies on MICROSOFT.OWIN.HOST.SYSTEMWEB, publish the deployment of the time do not omit the DLL.

Personball ' s Blog

Original address: Use Owin middleware to build OAuth2.0 Authentication authorization server, thank the original author to share.

Build OAuth2.0 authentication Authorization server using Owin middleware

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.