Authorizing your .NET Core MVC Core API requests with OpenIddict and Identity

 

OpenIddict is an excellent open-source library for dealing with OAuth and OpenID in the new MVC Core (previously known as MVC6) for .NET Core. At first, I dreaded having to relearn this process; OAuth Bearer Tokens in MVC5 was dicey and kind of annoying to get the hang of. But, OpenIddict is pretty straight forward.

Note that out-of-the-box, OpenIddict returns opaque/encrypted tokens, not JWT. However, you can enable JWT very simply shown in an example below.

If you want to skip to the end, here is a Gist of my Startup.cs file with CORS and OpenID enabled.

Setup OpenIddict-Core

Install Package

To use OpenIddict, the first thing you need to install is the OpenIddict Core package and the AspNet.Security.OAuth.Validation:

{
  "dependencies": {
    ...
    "OpenIddict": "1.0.0-*",
    "AspNet.Security.OAuth.Validation": "1.0.0-alpha1-final",
    ...
  },
}

Setup Middleware

Then, we need to modify our startup to hook OpenIddict into our middleware pipeline. In your Startup.cs, you should see something like this in the ConfigureServices(IServiceCollection services) method:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

This is the first spot we hook into to give OpenIddict Identity resources. Update to the following:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddOpenIddict();

Next, we need to add OpenIddict to the HTTP pipeline. Again in Startup.cs, you should see something like this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseMvc();
    }

Ordering matters!

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        
        app.UseOAuthValidation(); // enabled auth through bearer tokens

        app.UseMvc();

        app.UseOpenIddict(builder =>
        {
            // Lots of configuration options on builder.Options. 
            // For example, you can enable JWT tokens with:
            //    b.Options.UseJwtTokens();
            // We will stick with defaults for this tutorial, however.
        });
    }

Requesting tokens

Now try using Postman or a similar HTTP request tool. Here’s how to get your tokens.

The base URL for getting tokens is:

http://__yourdomain__/connect/token

MUST be with the Content-Type “application/x-www-form-urlencoded”. As such, you will need to send a URL encoded request body if you’re using a JavaScript XHR request – but we’ll get this. For now, let’s just use Postman or your tool of preference.

Getting access token

To get an access token, you need to send the following data to “/connect/token”:

{
    username: "",
    password: "",
    grant_type: "password"
}

The response match this interface:

interface IOpenIdToken {
    access_token: string;
    scope: string;
    refresh_token: string;
    expires_in: number;
    expires_at: number;

    // if bad request
    error: string;
    error_description: string;
}

Here are two example requests in Postman:

Successful request


Unauthorized request


You can now add the access_token to your “Authorization” header, with the value prefixed by “Bearer “. I.E:

Authorization: Bearer CfDJ8E2BjLxchpVOrXVMvbuOceoB-fxQqJeES9kImNIzPKuZ5pHtNmz8sDb_1i-FcitoxX2tp88Z8GBjxbIPXyjI3oVRyCBFh8fTD3SbqEZPWU6O59m8QPGdTbw3OByNkQ2tXG9TIa4coul3uqdHS6RzctRefmc2ZjoKQQhxDdURVxZbkI7L2mKTytqQcezMvZhJviN01M93O723wZkYlKDQVJep_KnEoWrIdeDtXsyA6EJBTOMxEFJYAW6IBMadSjDqHZU5BdIkj24Wy9HsnLTKQtazJ-v8JLQ2hmD_xoR1jUvwTfdw1c3OHxPdtLtVb1SPHTa43V71iyIxXFZe7vBna9d_UgBL4bSvUw8bOPQA80rjAtGqZBtlMX0A9o_BGXBLBcd4raw2xvYPOUnnA6sn85o3p9cnyb4ndCNkDF00z4uGkgaddviUxOWmTANkZ9N8_Yf18SO_zzFT-ijmktQ2XAFjG74q5l_gLrhPXCZsh7n8xRxPf_cxdKeEyF_RHeIa3iH_w23kIA_xC26JDo4ZtZrXlolev2Ft27DRutwb44BYXqivBr6WpVXHjiYeLsvWOapUe-hcACuK9GAUk9AclZjMnQaPiGl9SD6MlYauZFC9Va3wyY3h5blpukVrBCVtPfive5cDBuV8Lcde_5CREmM

Now your API requests will be authorized and your static User will be retrieved using the bearer token.

Getting a new access token using the refresh token

Did you notice the “refresh token”? If you’re not familiar with the idea of a refresh token, it is simply a way for the application to get a new access token after expiration without requiring the username and password. To get a new access token with the refresh token, the request is similar and also to “/connect/token”.

For example:

Now we have a new access token! Note that the refresh token is long-living, so it should be treated securely.

Setup cross-origin requests with the new AspNetCore.CORS

Install package

If you’re trying to make these requests through a different site, you’ll need to enable CORS, or you’ll see the always-fun “no ‘access-control-allow-___’ header is present on the requested resource” error. To do this, we need to install the new CORS package.

{
  "dependencies": {
    ...
    "Microsoft.AspNetCore.Cors": "1.0.0-rc2-final"
    ...
  },
}

Adding CORS middleware

Again in the startup, find the ConfigureServices(IServiceCollection services) method. At the top of the method, add the following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    ...

Now in Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory), add app.UseCors. For example:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseCors(builder =>
            // This will allow any request from any server. Tweak to fit your needs!
            // The fluent API is pretty pleasant to work with.
            builder.AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowAnyOrigin()
        );

        app.UseOAuthValidation();
        app.UseMvc();
        app.UseOpenIddict();
    }

Keep in mind that ordering matters when setting up the HTTP Request pipeline.

JavaScript requests

Requesting tokens through JavaScript XHR

must

{
    "grant_type": "password",
    "username": username,
    "password": password,
    "scope": "offline_access profile email"
}

should be sent a string value of:

grant_type=password&username=myusername&password=mypassword&scope=offline_access profile email

I wrote a small, probably not ready for production, method to do this conversion for me (in TypeScript):

private urlEncodeValues(data: any) {
    var str = "";
    for (var key in data) {
        if (data.hasOwnProperty(key)) {
            str += `${key}=${data[key]}&`;
        }
    }
    return str;
}

Requesting API data through JavaScript XHR

When making authorized API requests, you need to make sure to add the authorization header mentioned earlier in the post.

Creating user accounts through Web API

This is not OpenIddict’s responsibility, so you need to create your own controller to handle creating users. After the user is created, you can request tokens immediately. Here’s a quick example of my AccountController WebAPI:

[Route("api/account")]
public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;

    public AccountController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    [HttpPost]
    public async Task<IdentityResult> Register([FromBody]ApplicationUserDto dto)
    {
        var user = new ApplicationUser { UserName = dto.Username, Email = dto.Email };
        var result = await _userManager.CreateAsync(user, dto.Password);
        return result;
    }
}

An example request to this would be to the “/api/account” resource:

{
    "Username": "kerry",
    "Email": "ritter@kerryritter.com",
    "Password": "mygoodpassword"
}

All set!

You should now be able to register users, authorize users, make authorized requests to your web API.

If you have questions, please use OpenIddict-core’s Gitter chat. I unfortunately have not had time to keep up with OpenIddict releases and will not be of too much help.