In this post, I’ll show how I set up ASP.NET5 (vNext / ASP.NET Core / MVC6 / ASP.NET MVC Core) to use JWT tokens.
29 Jan 2016 UPDATE: The Github repo has been updated to include an AngularJS login.
First up, let me say I’m not expert on any of this, but if you see any room for improvements / errors, please let me know.
TLDR: If you don’t want to read the whole thing, you can download the finished code from: https://github.com/capesean/openiddict-test
The website I will be using this on is an AngularJS front end (although that’s not really relevant), which I hope to one day build mobile apps for, so JWT tokens seems like the logical choice. My understanding is that I’d be using the Resource Owner Flow of OAuth2; and that my authorization server (the thing that creates the token) and my resource server (the thing that validates the token and returns the data – i.e. the API) would be in the same application. Also, users wouldn’t (initially) be authorizing other apps/websites/etc. (i.e. “clients”) to use their data (resources) – so the typical authorization aspect that you see in many OAuth2 examples (“Application X wants to access your data – Accept / Deny?”) didn’t apply. Basically, I just wanted a typical login (i.e. cookies) system for round trips to the API, but without the cookie – i.e. with JWTs!
Note that I use Entity Framework and SQL Server for data access/storage, and I’ll be using ASP.NET Identity v3.
First I needed to choose which existing software to use to generate the JWT tokens. Previously, in ASP.NET4, I had used an OWIN implementation that I based on the fantastic blog of Taiseer Joudeh – at www.bitoftech.net. It involved quite a lot of coding so I was hoping for something in v5 that had less heavy lifting.
I looked at Identity Server v3 first, and got reasonably far, but it also got quite tricky to understand, as you have to look at an example for using Identity, and then another example for Entity Framework, and then another example for ASP.NET5, and then another example for JWT tokens, etc – and then try pull them all together. I gave up when I read something about Entity Framework 7 not yet being fully (natively?) supported.
The other option I was reading about was AspNet.Security.OpenIdConnect.Server (ASOS). But again, this looked quite tricky to pull together. then I stumbled onto the lesser known OpenIddict software, which is based on ASOS, but does most of the heavy lifting for you. This is what I ended up choosing.
There are installation instructions on the OpenIddict home page. Note that at the time of writing, you have to use RC2 builds, so you’ll need to run:
You also have to create a Nuget.Config file in the root of your application, with the following contents:
|
<?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <add key="aspnet-contrib" value="https://www.myget.org/F/aspnet-contrib/api/v2" /> <add key="AspNetVNext" value="https://www.myget.org/F/aspnetvnext/api/v2" /> <add key="AzureAd Nightly" value="http://www.myget.org/F/azureadwebstacknightly/" /> <add key="NuGet" value="https://api.nuget.org/v3/index.json" /> <add key="DotNetCore" value="https://www.myget.org/F/dotnet-core/" /> </packageSources> </configuration> |
At the time of writing, these installation instructions mirror what’s on the OpenIddict home page. From here on we’ll start to deviate, though, as we don’t want the default configuration, which seems to be more about authorizing other applications/clients.
First up, we add hosting.json to the project with the following contents:
|
{ "server": "Microsoft.AspNet.Server.Kestrel" } |
My project name is openiddict-test, so in project.json we set the following commands:
|
"commands": { "web": "openiddict-test", "ef": "EntityFramework.Commands" }, |
Note that the web command is the project name, not the namespace name (I originally had it as openiddicttest and the damned thing wouldn’t start).
The new static void main syntax in Startup.cs is now as follows:
|
public static void Main(string[] args) { var application = new WebApplicationBuilder() .UseConfiguration(WebApplicationConfiguration.GetDefault(args)) .UseStartup<Startup>() .Build(); application.Run(); } |
In the config file (in my case, in appsettings.json), we have the database connection string and some logging settings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
{ "ApplicationInsights": { "InstrumentationKey": "" }, "Data": { "DefaultConnection": { "ConnectionString": "Server=localhost;Database=openiddict-test;Trusted_Connection=True;MultipleActiveResultSets=true" } }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Verbose", "System": "Information", "Microsoft": "Information" } } } |
Lastly (before we get into the real code), in project.json, make sure you have the following dependencies:
|
"OpenIddict.Core": "1.0.0-*", "OpenIddict.EF": "1.0.0-*", "Microsoft.AspNet.Authentication.JwtBearer": "1.0.0-*" |
We don’t actually need the whole of OpenIddict, we just need the core for generating the tokens, and the EF for the entity Entity Framework aspects, so I’ve just included these two, although you could include the main package if you like. We’re also going to be using the Microsoft JwtBearer package for validating the tokens, so include that now too. Notice that all dependencies are using the “-*” versioning option.
The Real Code
This is where it starts to get interesting.
First we need the User model, so this is the ApplicationUser.cs file:
|
using Microsoft.AspNet.Identity.EntityFramework; namespace openiddicttest.Models { // Add profile data for application users by adding properties to the ApplicationUser class public class ApplicationUser : IdentityUser { public string GivenName { get; set; } } } |
Note that we’re deriving from IdentityUser as you normally would if using ASP.NET Identity. We’re also adding a custom field called GivenName, which we will include in the JWT token.
Similarly, here is the very simple ApplicationRole.cs file:
|
using Microsoft.AspNet.Identity.EntityFramework; namespace openiddicttest.Models { public class ApplicationRole : IdentityRole { } } |
It simply derives from IdentityRole.
Then we need a DbContext, so the below is from a file ApplicationDbContext.cs:
|
using Microsoft.Data.Entity; using OpenIddict; using OpenIddict.Models; namespace openiddicttest.Models { public class ApplicationDbContext : OpenIddictContext<ApplicationUser, Application, ApplicationRole, string> { protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } } } |
Note that we’re deriving from OpenIddictContext and we’re using an override with 4 parameters: the ApplicationUser, the Application class, the ApplicationRole, and string. Although we’re not going to be using the Application class, we still need to use this signature otherwise the roles won’t get populated in the JWT token.
Now, here is the ConfigureServices method from the Startup.cs:
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
|
public void ConfigureServices(IServiceCollection services) { // add the config file which stores the connection string var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json"); builder.AddEnvironmentVariables(); Configuration = builder.Build(); // add entity framework using the config connection string services.AddEntityFramework() .AddSqlServer() .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"])); // add identity: note the AddOpenIddictCore call services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders() .AddOpenIddictCore<Application>(config => config.UseEntityFramework()); // assuming you have an api... services.AddMvc(); // for seeding the database with the demo user details services.AddTransient<IDatabaseInitializer, DatabaseInitializer>(); services.AddScoped<OpenIddictManager<ApplicationUser, Application>, CustomOpenIddictManager>(); } |
- Lines 4-7 are standard for building the configuration options.
- Lines 10-13 adds Entity Framework and configures the connection string from the configuration file.
- Lines 16-19 adds Identity. Notice Line 19 being the first OpenIddict line of code, which also tells OpenIddict to use Entity Framework.
- Line 22 adds MVC, for the API.
- Line 25 adds the Database Initializer, which is simply to seed the database with some data.
- Line 26 adds a Custom OpenIddict Manager. The OpenIddict Manager will override the token generation method so we can add the custom field (GivenName) to the JWT token.
Let’s look at the CustomOpenIddictManager.cs:
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
|
using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using openiddicttest.Models; using OpenIddict.Models; using System; namespace openiddicttest { public class CustomOpenIddictManager : OpenIddict.OpenIddictManager<ApplicationUser, Application> { public CustomOpenIddictManager(IServiceProvider services) : base(services) { } public override async Task<ClaimsIdentity> CreateIdentityAsync(ApplicationUser user, IEnumerable<string> scopes) { var claimsIdentity = await base.CreateIdentityAsync(user, scopes); // add custom fields var claim = new Claim(ClaimTypes.GivenName, user.GivenName); claim.Properties.Add("destination", "id_token token"); claimsIdentity.AddClaim(claim); return claimsIdentity; } } } |
This class simply inherits from OpenIddict.OpenIddictManager<ApplicationUser, Application> and then overrides CreateIdentityAsyncCreateIdentityAsync. It creates the claimsIdentity from the base method, and then adds the Given Name claim to the claimsIdentity, and sets the destination property of the claim to both id_token and token. This simply means that when the token is requested, the Given Name claim should be returned in the token.
Back in the Startup.cs file, we now look at the Configure method:
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
|
public void Configure(IApplicationBuilder app, IDatabaseInitializer databaseInitializer) { var factory = app.ApplicationServices.GetRequiredService<ILoggerFactory>(); factory.AddConsole(); factory.AddDebug(); app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear()); // to serve up index.html app.UseDefaultFiles(); app.UseStaticFiles(); // don't use identity as this is a wrapper for using cookies, not needed //app.UseIdentity(); app.UseOpenIddictCore(builder => { // tell openiddict you're wanting to use jwt tokens builder.Options.UseJwtTokens(); // NOTE: for dev consumption only! for live, this is not encouraged! builder.Options.AllowInsecureHttp = true; builder.Options.ApplicationCanDisplayErrors = true; }); // use jwt bearer authentication app.UseJwtBearerAuthentication(options => { options.AutomaticAuthenticate = true; options.AutomaticChallenge = true; options.RequireHttpsMetadata = false; options.Audience = "http://localhost:58292/"; options.Authority = "http://localhost:58292/"; }); // assuming you have an api... app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); // seed the database databaseInitializer.Seed(); } |
- Lines 3-5 set up logging.
- Line 7 has something to do with IIS, I won’t pretend to know what.
- Line 11 & 10 allow static files to be served (index.html) and lets the index.html page be served as the default page (index.html will be our test javascript page).
- Note we don’t use the identity line on line 14 as this is just a wrapper for cookies.
- Lines 16-23 configures OpenIddictCore – telling it to use JWT tokens, to allow non-HTTPS requests, and to display errors (these last two are obviously development settings).
- Lines 26-33 configures the JWT middleware that will validate the token and thus ensure that the Authorize attribute on our WebApi methods is honoured. Note that the audience and authority are the same, but more importantly the audience must match the resource value sent in the token request payload (on the index.html page, which we will see later). Also note that you will have to put in the correct values for your environment here and on index.html, and that this should ideally be stored in the config file.
- Lines 36-41 configures MVC.
- Line 44 seeds the database.
I’m not going to cover the Database Initialiser as that just seeds the database with a user and a role. You can view the source on Github if you’re interested.
The API endpoint we will be calling to test the token is in TestController.cs:
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
|
using Microsoft.AspNet.Authorization; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Mvc; using openiddicttest.Models; using System.Security.Claims; using System.Threading.Tasks; [Authorize] public class TestController : Controller { private ApplicationDbContext _context; private UserManager<ApplicationUser> _userManager; public TestController(ApplicationDbContext context, UserManager<ApplicationUser> userManager) { _context = context; _userManager = userManager; } [Route("api/test"), HttpGet] public async Task<IActionResult> Get() { var user = await _userManager.FindByIdAsync(User.GetUserId()); if (user == null) return Ok("No user / not logged in");// if Authorize is not applied return Ok(user); } } |
This is a pretty standard controller, with these notes:
- On line 8 we are using the Authorize attribute to ensure that only authorized (logged in) users can access this data.
- On line 9 we’re inherting from the MVC Controller class.
- On lines 11-18 we’re instantiating the controller object, using Dependency Injection to get the ApplicationDbContext and the UserManager, which we store in local properties.
- Line 20 specifies the Route (api/test) and that it’s a GET request.
- Line 23-26 gets the user that is logged in. If there’s no user it returns a simple text message (there always will be a user if the Authorize attribute is in place on line 7, but you can remove the attribute and call the url to test it’s working). If there is a user, the user object gets returned.
Basically, this API url (http://localhost:58292/api/test) will return the user if logged in, or it will return 401 (Unauthorized).
That is really all there is to it, on the server side.
Client Side Testing Code
This post is not meant to cover what happens on the client side – it could be an AngularJS website, or a mobile app, or whatever. But to test that the server is working, below is the index.html test page:
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8" /> </head> <body> <h1>Get JWT token & refresh token from OpenIddict</h1> <div> <label>username</label> <input id="username" value="user@test.com" /> </div> <div> <label>password</label> <input id="password" value="P2ssw0rd!" /> </div> <br /> <div> <button id="buttonToken">Get Token</button> <button id="buttonAPI">Get API Data</button> </div> <h4>API Response</h4> <pre style="max-width:100%;white-space: normal;overflow-wrap:break-word;" id="apiresponse"></pre> <h4>Token Response</h4> <pre style="max-width:100%;white-space: normal;overflow-wrap:break-word;" id="response"></pre> <h4>Decoded Access Token</h4> <pre style="max-width:100%;white-space: normal;overflow-wrap:break-word;" id="token"></pre> <script> var baseUrl = window.location.protocol + "//" + window.location.host + "/"; document.getElementById("buttonToken").addEventListener("click", getToken, false); document.getElementById("buttonAPI").addEventListener("click", getAPI, false); function getAPI() { var data = document.getElementById("response").innerText; document.getElementById("apiresponse").innerText = "loading..."; if (data === "") { document.getElementById("apiresponse").innerText = "No token" return; } var response = JSON.parse(data); var xhr = new XMLHttpRequest(); xhr.onload = function (e) { console.log(xhr.status); console.log(xhr.response); document.getElementById("apiresponse").innerText = xhr.status + ": " + xhr.response; } xhr.open("GET", baseUrl + "api/test"); xhr.setRequestHeader("Authorization", "Bearer " + response.access_token); xhr.send(); } function getToken() { var uid = document.getElementById("username").value; var pwd = document.getElementById("password").value; document.getElementById("response").innerText = "loading..."; document.getElementById("token").innerText = "loading..."; document.getElementById("apiresponse").innerText = ""; var xhr = new XMLHttpRequest(); xhr.onload = function (e) { console.log(xhr.status); console.log(xhr.response); document.getElementById("response").innerText = xhr.response; var decodedToken = decodeToken(JSON.parse(xhr.response).access_token); document.getElementById("token").innerText = JSON.stringify(decodedToken); var response_data = JSON.parse(xhr.response); } var client_id = "openiddict-test"; var client_secret = "secret"; xhr.open("POST", baseUrl + "connect/token"); var data = { username: uid, password: pwd, grant_type: "password", // specify the resource, to match the audience in the jwt bearer middleware resource: "http://localhost:58292/", // offline_access: indicate refresh token is required // profile: include custom fields // email: include email address scope: "offline_access profile email" }; var body = ""; for (var key in data) { if (body.length) { body += "&"; } body += key + "="; body += encodeURIComponent(data[key]); } xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(body); } function decodeToken(token) { var parts = token.split('.'); if (parts.length !== 3) { throw new Error('JWT must have 3 parts'); } var decoded = urlBase64Decode(parts[1]); if (!decoded) { throw new Error('Cannot decode the token'); } return JSON.parse(decoded); } function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: { break; } case 2: { output += '=='; break; } case 3: { output += '='; break; } default: { throw 'Illegal base64url string!'; } } return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js } </script> </body> </html> |
- Lines 9-21 are the form fields for logging in, and the buttons for logging in (getting the token) and getting the api data (using the token).
- Lines 22-27 are the outputs of the results of the two button clicks.
- Lines 29-32 sets up the base url and the button click event handlers.
- Lines 58-104 gets the token (logs the user in). It basically creates an XMLHttpRequest that when loaded, will output the token to the controls in lines 22-27. You can ignore the client_id and client_secret lines. Note that the url being called is the base url plus connect/token. Note the grant_type, the resource (which, as mentioned ealier, must match the settings in Startup.cs), and the scope (offline_access is required to get the refresh token).
- Lines 34-56 access the API url. It creates an XMLHttpRequest that will log the results to the controls in lines 22-27. The API url is simply the base url plus api/test. Note that the access token is sent as a Bearer token (“Bearer ” + access_token) in the Authorization header.
- Lines 106-132 simply decode the token so the contents can be shown on the page.
If all goes well, you should see the result of the token request (login) and the data request (api call) as shown in the screenshot below.
The code is at Github: https://github.com/capesean/openiddict-test
Good luck!

I must thank Kevin Chalet, aka Pinpoint / PinPointTownes for all his assistance. Kevin is the author of OpenIddict and had endless patience with me while I was fumbling my way around.