Heads up… this post is old! For an updated version of this post, see Token Authentication in ASP.NET Core 2.0 – A Complete Guide on the Okta developer blog.

Security update: Make sure you read this security advisory if you handle JWTs in your code. The examples and sample code in this article have been updated to use the fixed version of the affected packages.

Token authentication is quickly becoming a de facto standard for modern single-page applications and mobile apps. Even traditional server-rendered applications and web APIs can take advantage of token authentication. The benefits are great: less server state to manage, better scalability, and a consistent identity and authentication mechanism across web and mobile clients.

If you need a refresher on how tokens work, read our overview of token authentication and JWTs. All clear? Great!

Token authentication in ASP.NET Core is a mixed bag. The ability to protect routes with Bearer header JWTs is included, but the ability to generate the tokens themselves has been removed and requires the use of custom middleware or external packages. Despite this, both MVC and Web API applications can benefit from using tokens for authentication, and it turns out it’s not very hard to set up.

How do you implement both sides of token authentication – token verification and token generation – on the new ASP.NET Core stack? I’ll break down the process step by step. All of the code in this post can be found on Github.

Cookies or Headers for Authentication?

First, some background. Authentication tokens (such as JWTs) are typically transmitted in the HTTP Authorization header, like this:

GET /foo Authorization: Bearer [token] 1 2 3 GET / foo Authorization : Bearer [ token ]

Tokens can also be transmitted via browser cookies. Which transport method you choose (headers or cookies) depends on your application and use case. For mobile applications, headers are the way to go.

For web applications, we recommend using HttpOnly cookies instead of HTML5 storage/headers, for better security against XSS attacks. It’s important to note that using cookies means that you need to protect your forms against CSRF attacks (by using ASP.NET Core’s AntiForgery features, for example).

Validating Tokens in ASP.NET Core

First, you’ll need to create a SecurityKey from your secret key. For this example, I’m creating a symmetrical key to sign and validate JWTs with HMAC-SHA256. You can do this in your Startup.cs file:

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

Validating JWTs in Headers

In your Startup class, you can use the UseJwtBearerAuthentication method in the Microsoft.AspNetCore.Authentication.JwtBearer package to require a valid JWT for your protected MVC or Web API routes:

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 }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 } ) ;

With this middleware added to your application pipeline, any routes protected with [Authorize] will require a JWT that passes the following validation requirements:

The signature matches your server’s secret key

The expiration date ( exp claim) has not passed

The not-before date ( nbf claim) has passed

The Issuer ( iss ) claim matches “ExampleIssuer”

The Audience ( aud ) claim matches “ExampleAudience”

If there is not a valid JWT in the Authorization header, or it fails these validation steps, the request will be rejected. If you’re not familiar with the JWT spec, the Issuer and Audience claims are optional. They are being used here to identify the application (issuer) and the client (audience).

Validating JWTs in Cookies

Out of the box, the ASP.NET Core cookie authentication middleware doesn’t support validating JWTs passed via cookies. You’ll need to create a custom ISecureDataFormat implementation that validates a JWT string.

Since you’re only validating tokens, not creating them, you only need to implement the Unprotect method. The heavy lifting can be handled by the JwtSecurityTokenHandler class in the System.IdentityModel.Tokens.Jwt namespace. The full custom formatter class looks like this:

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(); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 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 ( ) ; } } }

Wire this class up with UseCookieAuthentication in your Startup.cs file:

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) }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 ) } ) ;

That’s it!

If an incoming cookie named access_token contains a valid JWT, your protected MVC or Web API routes will be authorized. If you want, you can do additional validation of the JWT claims (or copy the JWT claims into the ClaimsPrincipal object) inside of CustomJwtDataFormat.Unprotect .

If you’re using cookies to transport your JWTs between the browser and the server, we recommend the following best practices for security:

Set the HttpOnly flag

Set the Secure flag as well, if your site is hosted on HTTPS (and it should be!)

Use the [ValidateAntiForgeryToken] attribute on any forms that accept a POST

That takes care of the validation side of token authentication, but what about generating the tokens themselves?

Generating Tokens in ASP.NET Core

Back in the ASP.NET 4.5 days, the UseOAuthAuthorizationServer middleware provided an endpoint that could easily generate tokens for your application. However, the ASP.NET Core team decided not to port it to ASP.NET Core.

Never fear! I’ll demonstrate how to write a simple token-generating middleware from scratch, and then conclude with a few links to pre-built solutions that could save you some time.

Writing a Simple Token Endpoint

First, you’ll need a simple POCO (plain ol’ CLR object) to hold a few options for the middleware class you’ll write. Create a new class called 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; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 you can build the middleware class itself. The basic pattern for ASP.NET Core middleware is this:

using System; 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); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using System ; 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 scaffolding creates a middleware class that can be added to the ASP.NET Core application pipeline and accepts a TokenProviderOptions instance as a parameter. When a request enters the pipeline, the Invoke method checks the request path and skips if it doesn’t match the exact path the middleware should be handling (such as /token or /api/token ). The token endpoint also shouldn’t respond to anything but a POST with a form-urlencoded body, so the request method and Content-Type are checked as well.

The heavy lifting happens in GenerateToken . This method needs to validate a user identity (given a username and password), and generate a signed token (JWT) to pass back to the client if that identity exists:

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, now.ToUnixTimeSeconds().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 })); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 , now . ToUnixTimeSeconds ( ) . 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 this method is just plumbing code. It uses the JwtSecurityToken class to build up a JWT payload, and then JwtSecurityTokenHandler writes it to an encoded string. You can store any custom claim you want in the payload by simply adding it to the claims array.

The task of validating a user’s identity given their username and password is further abstracted away to the GetIdentity method. In a real application, you’d plug this into your user database or an identity framework like ASP.NET Core Identity. For the purposes of this example, you can hardcode the lookup:

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); } 1 2 3 4 5 6 7 8 9 10 11 12 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 ) ; }

All done! You can wire the new middleware class up in Startup.cs by adding the middleware to your application pipeline:

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(); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 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 ( ) ; } } }

Once you’ve added it, fire up the application and use a tool like Fiddler or Postman to send a POST request to the new endpoint:

POST /token Content-Type: application/x-www-form-urlencoded username=TEST&password=TEST123 1 2 3 4 5 POST / token Content - Type : application / x - www - form - urlencoded username = TEST &password = TEST123

If the username and password are valid, you’ll get a token response:

200 OK Content-Type: application/json { "access_token": "eyJhb...", "expires_in": 300 } 1 2 3 4 5 6 7 8 200 OK Content - Type : application / json { "access_token" : "eyJhb..." , "expires_in" : 300 }

You can use a tool like jsonwebtoken.io to decode the JWT and see the payload.

That’s it! You now have a endpoint that issues JWTs for valid users. If you’re writing a mobile or single-page application or web API, you can store the JWT and send it in the Authorization header on subsequent requests. If you want to store the JWT in a browser cookie, you’ll need to make a small modification to the endpoint so that it adds a cookie to the response.

Other ASP.NET Core Authentication Solutions

This example works, but it’s simple. It doesn’t support refresh tokens or other methods of exchanging user credentials for an access token. There are some community-led efforts to build rich ASP.NET Core token authentication functionality:

AspNet.Security.OpenIdConnect.Server – Similar to the OAuth Authorization Server middleware for ASP.NET 4.x.

OpenIddict – Wraps OpenIdConnect.Server up into an easier-to-use package that plugs into ASP.NET Identity.

IdentityServer4 – A port of Thinktecture IdentityServer3 to .NET Core (currently in beta).

These solutions are much more powerful, but are also more complex to install and configure. If you want a powerful and simple solution, check out the Stormpath ASP.NET Core integration. This package will add all of Stormpath’s token management features to an ASP.NET Core project with two lines of code. You can learn more about Stormpath’s approach to token management with these resources:

Thanks for reading! Feel free to dig into the full code on Github. If you have any questions about token authentication, leave me a comment below.

By the way, I’ll be speaking on ASP.NET Core Token Authentication at KCDC in Kansas City in June 2016. If you’re in the area, come say hi!