1.we use Owin to implement the Authentication and Authorize.html
we create a new Startup.cs file to replace the global.asax file. here is a general content of the startup.cs file.angularjs
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()); } } }
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.web
2.1 For the first method OAuthAuthorizationServerProvider.ValidateClientAuthentication(), the key point, if validate pass, call context.Validate(), otherwise, call context.setErrors().api
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.app
these sub class AuthenticationTicket, ClaimsIdentity. AuthenticationPropertieside
2.3 OAuthAuthorizationServerProvider.GrantRefreshToken, called when a request to tokenendpoint with grant_type refresh_token. we can see the http api.post
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.ui
3.1 Resource Owener Password Credentials Grantthis
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 .spa
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);