Authentication on .NET Core REST API with Identity Server

Reading time ~6 minutes

Now that’s a long title!

You probably know .Net core and you probably know Identity Server. I hope you know REST too. That is not what this post is about.

What this post is about, is how to setup Identity Server to generate JWT tokens for our REST API calls to be secured. Furthermore, how to do this in .Net Core.

Yeah, I will need that long title…

Setup Identity Server

I won’t go into details on how to setup IS4. There is already a lot on information on the web about it including the official documentation.

What we need here is to access IS4 Admin panel, and create:

  • a new client (let’s say with ID clientid) – use the defaults
  • setup a secret on the client’s configuration (let’s say secret nooneknows)
  • setup a scope on the client’s configuration (let’s say scope insidelayer)
  • make sure the AccessTokenType is JWT and set a sensible AccessTokenLifetime (defaults to 3600 but I like 86400 for development)

Setup REST API

Again, I will not explain how to create a .NET Core REST API. The web is full of information about it, including example source code from microsoft itself. The purpose here is to help someone integrate authentication into an existing code base, so I think skipping this part is fair game.

How Authentication will work

So, we have our Rest API and we can use Postman or equivalent, to call some dummy controller on it. Cool.

What we will need is to tell the API server to expect a JWT token on all HTTP requests, more preciselly on the authorization header. Then, it needs to validate the token against the issuer of that token (Identity Server in this example). If the token validates, we allow the request to hit the controller code, otherwise its blocked, returning HTTP 401 Unauthorized Status code.

Nice.

Using it / Testing

I think it gives the reader a better view of what we aim to achive setting this section before the actual code. However, you’re free to read this page in any order you like.

First we need to get a token from IS4. We do that with the following HTTP call to IS4 /connect/token resource:

# from this it should be trivial to build the postman call
# dont use spaces, they are just there for readability
curl -X POST
     -H "Content-Type: application/x-www-form-urlencoded"
     -H "Cache-Control: no-cache"
     -d 'client_id=clientid       &
         scope=insidelayer        &
         client_secret=nooneknows &
         grant_type=client_credentials'
     "http://identity-server-url:8001/connect/token"

Note that for this to work the grant_type must be client_credentials. You can read the full story here: Grant Types. It took me some time to find that out.

The response will be something simple where you can extract the token and use it to call our API:

# from this it should be trivial to build the postman call
# dont use spaces, they are just there for readability
curl -X Get
     -H "Authorization: Bearer eyJh...the token is really long..."
     -H "Cache-Control: no-cache"
     "http://our-api-url/api/values"

Here, I am calling the ValuesController with a GET request, providing a JWT token. The server will validate it, accept it, execute the controller code and provide me with a HTTP 200 Status OK response with the data I (don’t) need. Any other case such as no token provided, the token does not validate, the token format is wrong, the token is expired and so on, the server will reply with HTTP 401 Unauthorized and no data. The controller code never gets hit.

Seems simple? It should be. To use, not to setup I mean…

From theory to code

So, we do all this by decorating all our contollers (all those we want to protect, at least) with the [Authorise] attribute. Like so:

    [Route("api/[controller]")]
    [Authorize]
    public class ValuesController : Controller {
        // (...) controller code
    }

The [Authorise] attribute can be used at the class level (applies to all public methods) or at the method level for finner control. There is also the [AllowAnonymus] attribute that does what the name says. I find that usefull to allow login method calls or if your API will allow some type of public/anonymus read only operations. Anyway, the internet explains all that much better, I just want to leave a remark about it.

Then, we need to tell the code how and where the “token issuer” is. We do that as follows:

using System.IdentityModel.Tokens.Jwt;

    public class Startup
    {
        // (...)

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            SetupAuthentication(app);
            // (...) rest of the code
        }

        private void SetupAuthentication(IApplicationBuilder app)
        {
            var auth = new AuthenticationConfiguration();
            Configuration.GetSection("authentication").Bind(auth);

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
            {
                RequireHttpsMetadata = auth.RequireHttps,
                Authority = auth.IdentityServerBaseUrl,
                AllowedScopes = auth.AllowedScopes,
                AutomaticAuthenticate = true,
                AutomaticChallenge = true
            });
        }

    }

We can see that we load configurations from the appsettings.config file, which you can read more about it here.

{
  "authentication": {
    "IdentityServerBaseUrl": "http://identity-server-url:8001",
    "AllowedScopes": [ "insidelayer" ],
    "RequireHttps":  false 
  }
}

All the magic happens in the framework method UseIdentityServerAuthentication. We just need to add some depencies for the code to compile:

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.1.0",
      "type": "platform"
    },

    "IdentityServer4.AccessTokenValidation": "1.0.2",
    "Microsoft.AspNetCore.Authorization": "1.1.0",
    "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0"
  }
}

Not just one dependency but 3 packages to our project.json file.

That is it. You can now build and run your API and call it with a valid/invalid token to see that you can/can’t get access to it.

Make it work on development: Policies

All of this is fine.

However, when we are developing the API code we do not want to worry about generating tokens and tokens expiring and so on. I needed a simple way to disable authentication for the development enviroment.

I could comment out code but that brings more trouble to the table than it solves because now I have to remember not to commit this commented code and which code to comment out if I stop to work on this for a few days (read hours). Sharing this “commented code” knowledge among several team members is also, hum, stupid.

If this was not .NET core we could inherite from the Authorize attribute class and role our own with an IF inside. However, .NET Core developers do not like that messing around and do not allow it anymore. What they allow is for you to create your own Policy and use that instead. Once I knew this, it was peanuts to get the code working. Finding this out was not so easy. Google floods me with pre-dot NET core knowledge, and it took some digging.

So, we create our policy according to the environment, like so:

public class Startup
{
    private readonly IHostingEnvironment _env;

    public Startup(IHostingEnvironment env)
    {
        // ...
        _env = env;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        BypassAuthenticationForDevelopment(services);
        // ...
    }

    private void BypassAuthenticationForDevelopment(IServiceCollection services)
    {
        if (_env.IsDevelopment())
        {
            services.AddAuthorization(options => options.AddPolicy("AllowDevCalls", policy => policy.RequireAssertion(c => true)));
        }
        else
        {
            services.AddAuthorization(options => options.AddPolicy("AllowDevCalls", policy => policy.RequireAuthenticatedUser()));
        }
    }
}

And we update all of our Authorize attributes to use the following policy:


    [Authorize(Policy="AllowDevCalls")]

The above works with some dependency injection magic that the .Net framework provides us. I encapsulated some code in a private method for readability purposes only.

Notes on security

You may notice that I am using HTTP to reach Identity Server and setting RequireHttps to false which are both obvious mistakes. I use HTTP to have a simpler development environment and as an example, but all this must use HTTPS to be taken seriously.

Additionally, setting the AccessTokenLifettime to big values (as I did) is not a good practice. Setting these times is a somewhat tricky business as it is always a compromise between security and usability. Invalidating tokens too often will required IS4 to be queried more often. If this leads to a user login, then the user experience is basically crap.

There are some options like refresh tokens and such, but again, this is a topic on itself and one that is heavily dependent on the problem at hand.

Sources

I.E.: Not all of this comes from my own ideas:

Glossary

Term Description
IS4 Identity Server v4
DI Dependency Injection
JWT JSON Web Token

Name my Dot

About .netcore naming Continue reading

Access IIS Express from another machine

Published on March 07, 2017

HTTPS All The Things

Published on January 05, 2017