Detailed description of ASP. NET Core Token certification, asp. nettoken

Source: Internet
Author: User

Detailed description of ASP. NET Core Token certification, asp. nettoken

Token Authentication has become a de facto standard for SPA and mobile apps. Even traditional B/S applications can use its advantages. The advantage is very clear: very few server-side data management, scalability, can be separated by a separate authentication server and application server.

If you are not familiar with tokens, read this article (overview of token authentication and token TS)

Token Authentication is integrated in asp.net core. This includes the routing function that protects Bearer Jwt, but removes the part that generates and verifies the token. These can be customized or implemented using a third-party library, MVC and Web api projects can use Token Authentication, and it is very simple. The code can be downloaded in the (source code) step by step.

ASP. NET Core token Verification

First, background knowledge: the authentication token, such as the token ts, is passed through the http Authentication Header, for example:

GET /fooAuthorization: Bearer [token]

Tokens can be cookies in the browser. The transfer method is header or cookies depends on the application and actual situation. For mobile apps, headers is used. For web, cookies are recommended in html5 storage to prevent xss attacks.

Asp.net core is very easy to verify the token ts token, especially when you pass the token through the header.

1. Generate a SecurityKey. In this example, I generate a symmetric key to verify that mongots is encrypted by HMAC-SHA256 in startup. cs:

// secretKey contains a secret passphrase only your server knowsvar secretKey = "mysupersecret_secretkey!123";var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

Verify the TS passed in the header

In Startup. cs, use the UseJwtBearerAuthentication method in Microsoft. AspNetCore. Authentication. JwtBearer to obtain the valid jwt of the protected api or mvc route.

var tokenValidationParameters = new TokenValidationParameters{  // The signing key must match!  ValidateIssuerSigningKey = true,  IssuerSigningKey = signingKey,  // Validate the JWT Issuer (iss) claim  ValidateIssuer = true,  ValidIssuer = "ExampleIssuer",  // Validate the JWT Audience (aud) claim  ValidateAudience = true,  ValidAudience = "ExampleAudience",  // Validate the token expiry  ValidateLifetime = true,  // If you want to allow a certain amount of clock drift, set that here:  ClockSkew = TimeSpan.Zero};app.UseJwtBearerAuthentication(new JwtBearerOptions{  AutomaticAuthenticate = true,  AutomaticChallenge = true,  TokenValidationParameters = tokenValidationParameters});

Through this middleware, any [Authorize] request requires a valid jwt:

The signature is valid;

Expiration time;

Effective time;

Issuer declaration equals "ExampleIssuer"

The subscriber statement is "ExampleAudience"

If it is not a valid JWT, request termination, issuer declaration, and subscriber Declaration, they are used to identify the application and client.

Verify external ts in cookies

Cookies in ASP. NET Core do not support passing jwt. You need to customize the ISecureDataFormat interface class. Now, you only need to verify the token, not to generate them. You only need to implement the Unprotect method. The others are handed over to the System. IdentityModel. Tokens. Jwt. JwtSecurityTokenHandler class for processing.

using System;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Http.Authentication;using Microsoft.IdentityModel.Tokens; namespace SimpleTokenProvider{  public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>  {    private readonly string algorithm;    private readonly TokenValidationParameters validationParameters;     public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)    {      this.algorithm = algorithm;      this.validationParameters = validationParameters;    }     public AuthenticationTicket Unprotect(string protectedText)      => Unprotect(protectedText, null);     public AuthenticationTicket Unprotect(string protectedText, string purpose)    {      var handler = new JwtSecurityTokenHandler();      ClaimsPrincipal principal = null;      SecurityToken validToken = null;       try      {        principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);         var validJwt = validToken as JwtSecurityToken;         if (validJwt == null)        {          throw new ArgumentException("Invalid JWT");        }         if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))        {          throw new ArgumentException($"Algorithm must be '{algorithm}'");        }         // Additional custom validation of JWT claims here (if any)      }      catch (SecurityTokenValidationException)      {        return null;      }      catch (ArgumentException)      {        return null;      }       // Validation passed. Return a valid AuthenticationTicket:      return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");    }     // This ISecureDataFormat implementation is decode-only    public string Protect(AuthenticationTicket data)    {      throw new NotImplementedException();    }     public string Protect(AuthenticationTicket data, string purpose)    {      throw new NotImplementedException();    }  }}

Call in startup. cs

var tokenValidationParameters = new TokenValidationParameters{  // The signing key must match!  ValidateIssuerSigningKey = true,  IssuerSigningKey = signingKey,   // Validate the JWT Issuer (iss) claim  ValidateIssuer = true,  ValidIssuer = "ExampleIssuer",   // Validate the JWT Audience (aud) claim  ValidateAudience = true,  ValidAudience = "ExampleAudience",   // Validate the token expiry  ValidateLifetime = true,   // If you want to allow a certain amount of clock drift, set that here:  ClockSkew = TimeSpan.Zero}; app.UseCookieAuthentication(new CookieAuthenticationOptions{  AutomaticAuthenticate = true,  AutomaticChallenge = true,  AuthenticationScheme = "Cookie",  CookieName = "access_token",  TicketDataFormat = new CustomJwtDataFormat(    SecurityAlgorithms.HmacSha256,    tokenValidationParameters)});

If the request contains a cookie named access_token that is verified as a valid JWT, the request returns the correct result. If necessary, you can add an additional jwt chaims, or copy jwt chaims to ClaimsPrincipal in CustomJwtDataFormat. in the Unprotect method, the token is verified, and the token is generated in asp.net core.

Generate Tokens using ASP. NET Core

In asp.net 4.5, this UseOAuthAuthorizationServer middleware can easily generate tokens, but it is canceled in asp.net core. Below is a simple token generation middleware. Finally, there are links to several ready-made solutions, for your choice.

Simple token Generation Node

First, generate the POCO storage middleware option. Generate class: TokenProviderOptions. cs

using System;using Microsoft.IdentityModel.Tokens; namespace SimpleTokenProvider{  public class TokenProviderOptions  {    public string Path { get; set; } = "/token";     public string Issuer { get; set; }     public string Audience { get; set; }     public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5);     public SigningCredentials SigningCredentials { get; set; }  }}

Now add a middleware. The middleware class of asp.net core is generally like this:

using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;using System.Threading.Tasks;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Options;using Newtonsoft.Json;namespace SimpleTokenProvider{  public class TokenProviderMiddleware  {    private readonly RequestDelegate _next;    private readonly TokenProviderOptions _options;    public TokenProviderMiddleware(      RequestDelegate next,      IOptions<TokenProviderOptions> options)    {      _next = next;      _options = options.Value;    }    public Task Invoke(HttpContext context)    {      // If the request path doesn't match, skip      if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))      {        return _next(context);      }      // Request must be POST with Content-Type: application/x-www-form-urlencoded      if (!context.Request.Method.Equals("POST")        || !context.Request.HasFormContentType)      {        context.Response.StatusCode = 400;        return context.Response.WriteAsync("Bad request.");      }      return GenerateToken(context);    }  }}

This middleware class accepts TokenProviderOptions as the parameter. When there is a request and the request path is the set path (token or api/token), The Invoke method is executed, the token node only applies to POST requests and contains the form-urlencoded Content Type (Content-Type: application/x-www-form-urlencoded). Therefore, you need to check the Content Type before calling.

The most important thing is GenerateToken. This method needs to verify the user's identity, generate jwt, and return it to jwt:

private async Task GenerateToken(HttpContext context){  var username = context.Request.Form["username"];  var password = context.Request.Form["password"];   var identity = await GetIdentity(username, password);  if (identity == null)  {    context.Response.StatusCode = 400;    await context.Response.WriteAsync("Invalid username or password.");    return;  }   var now = DateTime.UtcNow;   // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.  // You can add other claims here, if you want:  var claims = new Claim[]  {    new Claim(JwtRegisteredClaimNames.Sub, username),    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),    new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64)  };   // Create the JWT and write it to a string  var jwt = new JwtSecurityToken(    issuer: _options.Issuer,    audience: _options.Audience,    claims: claims,    notBefore: now,    expires: now.Add(_options.Expiration),    signingCredentials: _options.SigningCredentials);  var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);   var response = new  {    access_token = encodedJwt,    expires_in = (int)_options.Expiration.TotalSeconds  };   // Serialize and return the response  context.Response.ContentType = "application/json";  await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));}

Most of the Code is official. JwtSecurityToken class generates jwt. JwtSecurityTokenHandler encodes jwt. You can add any chaims to claims. It is only a simple authentication to verify the user's identity. The actual situation is definitely not the case. You can integrate the identity framework or others. For this instance, it is simply hard-coded:

private Task<ClaimsIdentity> GetIdentity(string username, string password){  // DON'T do this in production, obviously!  if (username == "TEST" && password == "TEST123")  {    return Task.FromResult(new ClaimsIdentity(new System.Security.Principal.GenericIdentity(username, "Token"), new Claim[] { }));  }   // Credentials are invalid, or account doesn't exist  return Task.FromResult<ClaimsIdentity>(null);}

Add a method to generate timestamp using DateTime:

public static long ToUnixEpochDate(DateTime date)  => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);

Now you can add this middleware to startup. cs:

using System.Text;using Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Hosting;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;using Microsoft.IdentityModel.Tokens; namespace SimpleTokenProvider{  public partial class Startup  {    public Startup(IHostingEnvironment env)    {      var builder = new ConfigurationBuilder()        .AddJsonFile("appsettings.json", optional: true);      Configuration = builder.Build();    }     public IConfigurationRoot Configuration { get; set; }     public void ConfigureServices(IServiceCollection services)    {      services.AddMvc();    }     // The secret key every token will be signed with.    // In production, you should store this securely in environment variables    // or a key management tool. Don't hardcode this into your application!    private static readonly string secretKey = "mysupersecret_secretkey!123";     public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)    {      loggerFactory.AddConsole(LogLevel.Debug);      loggerFactory.AddDebug();       app.UseStaticFiles();       // Add JWT generation endpoint:      var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));      var options = new TokenProviderOptions      {        Audience = "ExampleAudience",        Issuer = "ExampleIssuer",        SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),      };       app.UseMiddleware<TokenProviderMiddleware>(Options.Create(options));       app.UseMvc();    }  }}

We recommend that you use chrome's postman:

POST /tokenContent-Type: application/x-www-form-urlencodedusername=TEST&password=TEST123

Result:
OK

Content-Type: application/json
 
{
"Access_token": "eyJhb ...",
"Expires_in": 300
}

You can use the jwt tool to view the generated jwt content. If you are developing a mobile app or a single-page app, you can store jwt in the header of subsequent requests. If you need to store it in cookies, you need to modify the code, you need to add the returned jwt string to the cookie.
Test:

Other solutions

The following are mature projects that can be used in actual projects:

  • AspNet. Security. OpenIdConnect. Server-ASP. NET 4.x verification middleware.
  • OpenIddict-add OpenId verification on identity.
  • IdentityServer4-. NET Core-certified middleware (now a test version ).

The following article gives you a better understanding of certification:

  • Overview of Token Authentication Features
  • How Token Authentication Works in Stormpath
  • Use existing ts the Right Way!

The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.

Related Article

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.