OAuth2.0 Learning Note. Authorization Code Grant

Learning Note About Web Authentication and Authorize

1.we use Owin to implement the Authentication and Authorize.

we create a new Startup.cs file to replace the global.asax file. here is a general content of the startup.cs file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.OAuth;
using angularjsAuthentication.api.Providers;

[assembly:OwinStartup(typeof(angularjsAuthentication.api.Startup))]
namespace angularjsAuthentication.api
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            ConfigureOAuth(app);

            WebApiConfig.Register(config);
            app.UseWebApi(config);
        }

        public void ConfigureOAuth(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new SimpleAuthorizationServerProvider(),
                RefreshTokenProvider = new SimpleRefreshTokenProvider()
            };

            app.UseOAuthAuthorizationServer(OAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }
    }
}
  1. Some key class : OAuthAuthorizationServerOptions
    OAuthAuthorizationServerProvider

we have an important interface IOAuthAuthorizationServerProvider, the OAuthAuthorizationServerOptions provide a default implementation of this interface.
if we have any custom requirement, we can inherite it and override some methods.

2.1 For the first method OAuthAuthorizationServerProvider.ValidateClientAuthentication(), the key point, if validate pass, call context.Validate(), otherwise, call context.setErrors().

2.2 For this class, take care of these methods OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials
this method is reponsible for grantting access token to the request with grant_type as password, if success, call context.validate(token). generally, if a request arrives at token Endpoint with grant_type password, this method will be called.

these sub class AuthenticationTicket, ClaimsIdentity. AuthenticationProperties

2.3 OAuthAuthorizationServerProvider.GrantRefreshToken, called when a request to tokenendpoint with grant_type refresh_token. we can see the http api.

3. OAuth2

3.1 we have a lot of high quality articles descriping this protocol, here is just a link: link1, we can get a lot from cnblogs.
here we just make things simple, OAuth2 support four types of Authorization granttypes: Authorization Code Grant, Implicit Grant, Resource Owener Password Credentials Grant, Client Credential Grant. For each Authorization granttype, we make a note of each method called during a end2end test.

3.1 Resource Owener Password Credentials Grant

first, we request the access token, this method will be called OAuthAuthorizationServerProvider.ValidateClientAuthentication, this function is called to validate if the client is a registered client. if passed, OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials will be called. see the msdn .

secondly, if we provide the RefreshTokenProvider which implete the interface IAuthenticationTokenProvider, if user request an access token, the workflow will show like this: OAuthAuthorizationServerProvider.ValidateClientAuthentication -> OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials -> IAuthenticationTokenProvider.CreateAsync -> OAuthAuthorizationServerProvider.TokenEndpoint; if user try to refresh the access token, the workflow will like this: OAuthAuthorizationServerProvider.ValidateClientAuthentication ->
OAuthAuthorizationServerProvider.GrantRefreshToken -> IAuthenticationTokenProvider.ReceiveAsync -> OAuthAuthorizationServerProvider.TokenEndpoint

3.2 Authorization Code Grant

For the detail workflow we have a great doc. Here is a general sample code.

Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using Microsoft.Owin;
using Owin;
using Microsoft.Owin.Security.OAuth;
using angularjsAuthentication.api.Providers;
using System.Data.Entity;
using angularjsAuthentication.api.DataRepository;
using Microsoft.Owin.Security.Infrastructure;
using System.Collections.Concurrent;
using log4net;
using System.Web.Routing;

[assembly:OwinStartup(typeof(angularjsAuthentication.api.Startup))]
namespace angularjsAuthentication.api
{
    public class Startup
    {
        private static ILog Log = LogManager.GetLogger(typeof(Startup));
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            ConfigureOAuth(app);

            WebApiConfig.Register(config);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            app.UseWebApi(config);
            
            Database.SetInitializer(new configuration());
        }

        private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);

        public void ConfigureOAuth(IAppBuilder app)
        {
            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AuthorizeEndpointPath = new PathString("/OAuth/Authorize"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = new SimpleAuthorizationServerProvider(),
                RefreshTokenProvider = new SimpleRefreshTokenProvider(),
                AuthorizationCodeProvider= new AuthenticationTokenProvider()
                {
                    OnCreate = CreateAuthenticationCode,
                    OnReceive = ReceiveAuthenticationCode
                }
            };

            app.UseOAuthAuthorizationServer(OAuthServerOptions);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
        }

        private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
        {
            // context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
            context.SetToken("123");
            _authenticationCodes[context.Token] = context.SerializeTicket();
            Log.Info("Create a token with value: 123");
        }

        private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
        {
            string value;
            if(_authenticationCodes.TryRemove(context.Token,out value))
            {
                context.DeserializeTicket(value);
                Log.Info("Call at ReceiveAuthenticationCode");
            }
        }
    }
}

SimpleAuthorizationServerProvider.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System.Threading.Tasks;
using angularjsAuthentication.api.Entities;
using angularjsAuthentication.api.DataRepository;
using Microsoft.AspNet.Identity.EntityFramework;
using System.Security.Claims;
using log4net;

namespace angularjsAuthentication.api.Providers
{
    public class SimpleAuthorizationServerProvider:OAuthAuthorizationServerProvider
    {
        private static ILog Log = LogManager.GetLogger(typeof(SimpleAuthorizationServerProvider));
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId = string.Empty;
            string clientSecret = string.Empty;

            if(!context.TryGetBasicCredentials(out clientId,out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }
            Log.InfoFormat("SimpleAuthorizationServerProvider.ValidateClientAuthentication, client id: {0},client secrect {1}", clientId, clientSecret);
            context.Validated();
            return Task.FromResult<object>(null);

            //if (clientId==null)
            //{
            //    context.SetError("invalid_clientId", "clientId should be present.");
            //    return Task.FromResult<object>(null);
            //}

            //using (AuthRepository _repo = new AuthRepository())
            //{
            //    client = _repo.FindClient(clientId);
            //}

            //if(client==null)
            //{
            //    context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
            //}

            //if(client.ApplicatonType==Models.ApplicationTypes.NativeConfidential)
            //{
            //    if(string.IsNullOrWhiteSpace(clientSecret))
            //    {
            //        context.SetError("invalid_clientId", "Client secret should be sent.");
            //        return Task.FromResult<object>(null);
            //    }
            //    else
            //    {
            //        if(client.secrect!=Util.Help.GetHash(clientSecret))
            //        {
            //            context.SetError("invalid_clientId", "Client is inactive.");
            //            return Task.FromResult<object>(null);
            //        }
            //    }
            //}

            //if(!client.Active)
            //{
            //    context.SetError("invalid_clientId", "Client is inactive.");
            //    return Task.FromResult<object>(null);
            //}

            //context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
            //context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLiftTime.ToString());

            //context.Validated();
            //return Task.FromResult<object>(null);
        }

        public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
            if (allowedOrigin == null) allowedOrigin = "*";
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            //using (AuthRepository _repo = new AuthRepository())
            //{
            //    IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
            //    if(user==null)
            //    {
            //        context.SetError("invalid_grant", "The user name of password is incorrect");
            //        return;
            //    }
            //}
            Log.InfoFormat("SimpleAuthorizationServerProvider.GrantResourceOwnerCredentials with userName: {0}, Password: {1}", context.UserName, context.Password);

            var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
            identity.AddClaim(new Claim("sub", context.UserName));

            var props = new AuthenticationProperties(new Dictionary<string, string> {
                {
                    "as:client_id",(context.ClientId==null)?string.Empty:context.ClientId
                },
                {
                    "userName",context.UserName
                }
            });

            var ticket = new AuthenticationTicket(identity, props);
            context.Validated(ticket);
            return Task.FromResult<object>(null);
        }

        public override  Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            Log.InfoFormat("Call at SimpleAuthorizationServerProvider.ValidateClientRedirectUri");
            context.Validated("https://www.getpostman.com/oauth2/callback");
            return Task.FromResult<object>(null);
        }
        public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
        {
            Log.InfoFormat("grant the authorization directly in AuthorizeEndpoint");
            //var identity = new ClaimsIdentity(context.Options.AuthenticationType);
            //identity.AddClaim(new Claim(ClaimTypes.Name, "Name"));
            //identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
            //identity.AddClaim(new Claim("sub", "sub"));
            //context.OwinContext.Authentication.SignIn(identity);
            Log.Info("grant success");
            return Task.FromResult<object>(null);
        }

        public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
            var currentClient = context.ClientId;

            if(originalClient!=currentClient)
            {
                context.SetError("invalid_clientId", "Refresh token is issued to a different clientId");
                return Task.FromResult<object>(null);
            }

            var newIdentity = new ClaimsIdentity(context.Ticket.Identity);

            var newClaim = newIdentity.Claims.FirstOrDefault(c => c.Type == "newClaim");
            if(newClaim!=null)
            {
                newIdentity.RemoveClaim(newClaim);
            }
            newIdentity.AddClaim(new Claim("newClaim", "newValue"));

            var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
            context.Validated(newTicket);
            return Task.FromResult<object>(null);
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            context.Properties.Dictionary.ToList()
                .ForEach(e =>
                {
                    context.AdditionalResponseParameters.Add(e.Key, e.Value);
                });

            return Task.FromResult<object>(null);
        }

        public override Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
        {
            Log.Info("Call in GrantAuthorizationCode");
            var ticket = context.Ticket;
            context.Validated(ticket);
            return Task.FromResult<object>(null);
        }
    }
}

OAuthrize.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Security.Claims;
using log4net;

namespace angularjsAuthentication.api.Controllers.OAuth
{
    public class OAuthController : Controller
    {
        private static ILog Log = LogManager.GetLogger(typeof(OAuthController));
        // GET: OAuth
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Authorize()
        {
            var authentication = HttpContext.GetOwinContext().Authentication;
            if (Request.HttpMethod == "POST")
            {
                ClaimsIdentity identity = new ClaimsIdentity("Bearer", "myOAuthNameClaim", "myOAuthRoleClaim");
                identity.AddClaim(new Claim("Scope", "Name"));
                authentication.SignIn(identity);
            }
            Log.Info("Call at OAuthController.Authorize, create Identity with authentication type: myOAuthAuthentication, Name Claim: myOAuthNameClaim, Role Claim: myOAuthRoleClaim ");
            return View("Authroze");
        }
    }
}

SimpleRefreshTokenProvider.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using angularjsAuthentication.api.Entities;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System.Threading.Tasks;
using angularjsAuthentication.api.DataRepository;
using angularjsAuthentication.api.Util;
using System.Collections.Concurrent;

namespace angularjsAuthentication.api.Providers
{
    public class SimpleRefreshTokenProvider:IAuthenticationTokenProvider
    {
        private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);

        public Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            //var clientId = context.Ticket.Properties.Dictionary["as:client_id"];
            //if (string.IsNullOrEmpty(clientId))
            //    return;
            var refreshTokenId = Guid.NewGuid().ToString("n");
            var token = new RefreshToken()
            {
                Id = Help.GetHash(refreshTokenId),
                ClientId = "testClient",
                Subject = context.Ticket.Identity.Name,
                IssuedUtc = DateTime.UtcNow,
                ExpireUtc = DateTime.UtcNow.AddMinutes(100)
            };

            context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
            context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;

            token.ProtectedTicket = context.SerializeTicket();
            _authenticationCodes[token.Id] = token.ProtectedTicket;
            context.SetToken(refreshTokenId);
            return Task.FromResult<object>(null);
            //using (AuthRepository _repo = new AuthRepository())
            //{
            //    var refreshTokenLifetime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");

            //    var token = new RefreshToken()
            //    {
            //        Id = Help.GetHash(refreshTokenId),
            //        ClientId = clientId,
            //        Subject = context.Ticket.Identity.Name,
            //        IssuedUtc = DateTime.UtcNow,
            //        ExpireUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifetime))
            //    };

            //    context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
            //    context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;

            //    token.ProtectedTicket = context.SerializeTicket();

            //    var result = await _repo.AddRefreshTokenAsync(token);
            //    if(result)
            //    {
            //        context.SetToken(refreshTokenId);
            //    }
            //}
        }

        public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            //var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
            //context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            string hashedTokenId = Help.GetHash(context.Token);
            string value;
            if (_authenticationCodes.TryRemove(hashedTokenId, out value))
            {
                context.DeserializeTicket(value);
            }
            return Task.FromResult<object>(null);
            //using (AuthRepository _repo = new AuthRepository())
            //{
            //    var refreshToken = await _repo.FindRefreshTokenAsync(hashedTokenId);
            //    if(refreshToken!=null)
            //    {
            //        context.DeserializeTicket(refreshToken.ProtectedTicket);
            //        var result = await _repo.RemoveRefreshTokenAsync(hashedTokenId);
            //    }
            //}
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            var clientId = context.Ticket.Properties.Dictionary["as:client_id"];
            if (string.IsNullOrEmpty(clientId))
                return;

            var refreshTokenId = Guid.NewGuid().ToString("n");
            using (AuthRepository _repo = new AuthRepository())
            {
                var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");

                var token = new RefreshToken()
                {
                    Id = Help.GetHash(refreshTokenId),
                    ClientId = clientId,
                    Subject = context.Ticket.Identity.Name,
                    IssuedUtc = DateTime.UtcNow,
                    ExpireUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
                };

                context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
                context.Ticket.Properties.ExpiresUtc = token.ExpireUtc;

                token.ProtectedTicket = context.SerializeTicket();
                var result = _repo.AddRefreshToken(token);
                if (result)
                    context.SetToken(refreshTokenId);
            }
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            string hashedTokenId = Help.GetHash(context.Token);
            using (AuthRepository _repo = new AuthRepository())
            {
                _repo.RemoveRefreshToken(hashedTokenId);

            }
        }
    }
}

**In the OAuthrize Control, the key function call is below. Bearer is necessary.

ClaimsIdentity identity = new ClaimsIdentity("Bearer", "myOAuthNameClaim", "myOAuthRoleClaim");
                identity.AddClaim(new Claim("Scope", "Name"));
                authentication.SignIn(identity);
posted @ 2017-05-21 21:09  kongshu  阅读(1879)  评论(0编辑  收藏  举报