.NET 5: How to authenticate & authorise API's correctly
Tags: security, dotnet, cybersecurity, beginners
Published: 2021-04-01 05:21:53

In over 11 years of my experience I have seen so many API's that have major security flaw. They either lack a proper setup of Authentication or Authorisation or both. The developers might feel okay since these endpoints are usually not public. But it is a huge security loop hole which anyone can easily target. 

To better understand security for API's let's create a demo project for FBI. There will be an Admin who can enrol FBI Agents and change their clearance levels. Secondly FBI Agents with *Clearance Level 1* will be able to access public files and agents with *Clearance Level 2* will be able to access pubic & classified files.

First some theory! Not Interested? [Take me to the code](#project-setup).

## Authentication
Our Agent has successfully cleared all his exams; time to enrol him. In order to do that he will provide his documents and in return will get his badge. 

In the above scenario *providing documents* is like login where once verified he will be provided with a token (badge). This process is called *Authentication*. It determines whether agents are who they claim to be. 

We are going to use Json Web Tokens (JWT) Bearer tokens for authentication. *Bearer tokens* are a type of tokens generated by servers which contain details of the claims/roles of a user trying to login. Bearer tokens are mostly structured tokens like *JWT*. [Read here](https://jwt.io/introduction) to know more about JWT.


## Authorisation
Now since the FBI Agent has got his badge he can enter the FBI building. He is also able to access public files, but when trying to access classified files he gets *[401](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401)* error. 

This is because FBI Agent is not *authorised* to access classified files. *Authorisation* determines what agents can and cannot access.

As mentioned above the JWT Bearer token contains claims/roles. Based on it, our server decides whether to give access to a private resource or not. 

## Access Flow

![Access Flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1pcf6w2cjhtj9hgzm8v.jpg)
 

As you can see in the above diagram on successful login the server returns a Bearer token. The client uses the bearer token in subsequent calls to access a private resource. 

These are the two main concepts that we are going to implement in our article. 

Enough with the theory, show me some code!

## Project Setup <a name="project-setup"></a>
Create a new project by executing the command **dotnet new webapi --name FBI** from your cli. It will create a project with a sample WeatherForecast api. 

Why work on WeatherForecast when we can work on FBI. Go ahead and delete *WeatherForecast.cs* file. 

Add dependencies by executing the commands
```
dotnet add package Microsoft.IdentityModel.Tokens --version 6.9.0
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 5.0.4
```

In *ConfigureServices* function in your *Startup.cs* file add the below code. 
 
```
var TokenValidationParameters = new TokenValidationParameters
{
    ValidIssuer = "https://fbi-demo.com",
    ValidAudience = "https://fbi-demo.com",
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ")),
    ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
```
We are defining the parameters for validating a token. Make sure that the length of the string for generating SymmetricSecurityKey is 32.

Next, setup the services to add authentication for API's.

```
services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(cfg =>
    {
        cfg.TokenValidationParameters = TokenValidationParameters;
    });
```

The *AddAuthentication* method registers services required by authentication services. It also configures JWT Bearer Authentication as the default scheme.

The *AddJwtBearer* enables JWT-bearer authentication and setting the TokenValidationParameters defined above.

Now let's add some Authorisation claims for our *Agent* & *Admin*.

```
services.AddAuthorization(cfg =>
    {
        cfg.AddPolicy("Admin", policy => policy.RequireClaim("type", "Admin"));
        cfg.AddPolicy("Agent", policy => policy.RequireClaim("type", "Agent"));
        cfg.AddPolicy("ClearanceLevel1", policy => policy.RequireClaim("ClearanceLevel", "1", "2"));
        cfg.AddPolicy("ClearanceLevel2", policy => policy.RequireClaim("ClearanceLevel", "2"));
    });
```

The *AddAuthorization* method registers services required for authorisation. We are also adding claims for *Admin*, *Agent*, *ClearanceLevel1* and *ClearanceLevel2* by calling *AddPolicy*. A claim is a name value pair that represents what the subject is. Since clearance level 2 can also access clearance level 1 we have put *"1", "2"* in ClearanceLevel1. You can read more about claims *[here](https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-5.0)*.

Lastly in the *Configure* method add the below line just above *app.UseAuthorization();*

```
app.UseAuthentication();
```


## Admin Controller
Rename your file *WeatherForecastController.cs* to *AdminController.cs*. Do change the class name and constructor names as well. Finally, remove everything except the constructor. 

```
using Microsoft.AspNetCore.Mvc;

namespace FBI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class AdminController : ControllerBase
    {
        public AdminController() { }
    }
}

```

### Login API
Let's create a login API for Admin so that she can get a token to perform other tasks. 

```
[HttpPost]
[Route("[action]")]
public IActionResult Login([FromBody] User User)
{
    // TODO: Authenticate Admin with Database
    // If not authenticate return 401 Unauthorized
    // Else continue with below flow

    var Claims = new List<Claim>
            {
                new Claim("type", "Admin"),
            };

    var Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ"));

    var Token = new JwtSecurityToken(
        "https://fbi-demo.com",
        "https://fbi-demo.com",
        Claims,
        expires: DateTime.Now.AddDays(30.0),
        signingCredentials: new SigningCredentials(Key, SecurityAlgorithms.HmacSha256)
    );

    return new OkObjectResult(new JwtSecurityTokenHandler().WriteToken(Token));
}
```

In the above code *User* is a model with properties *Username* & *Password*. We are also creating an object of *JwtSecurityToken* using configurations that we have used in *Startup.cs* file. The token is then converted to string and returned in an OkObjectResult.

You can now open Swagger and execute the API to see a bearer token. A bearer token will be returned as you can see below. 

![Bearer Token Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aedozuzics30gunpp3b5.png)

Keep the token handy since we are going to use it in the next section. You can also visit https://jwt.io to analyse your token.

### Generate Badge API
Generating badge for an Agent is a sensitive task and should only be Authorised by an *Admin*. We are going to add an *Authorize* attribute for the *GenerateBadge* api.

```
[HttpPost]
[Route("[action]")]
[Authorize(Policy = "Admin")]
public IActionResult GenerateBadge([FromBody] Agent Agent)
{
    var Claims = new List<Claim>
    {
        new Claim("type", "Agent"),
        new Claim("ClearanceLevel", Agent.ClearanceLevel.ToString()),
    };

    var Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SXkSqsKyNUyvGbnHs7ke2NCq8zQzNLW7mPmHbnZZ"));

    var Token = new JwtSecurityToken(
        "https://fbi-demo.com",
        "https://fbi-demo.com",
        Claims,
        expires: DateTime.Now.AddDays(30.0),
        signingCredentials: new SigningCredentials(Key, SecurityAlgorithms.HmacSha256)
    );

    return new OkObjectResult(new JwtSecurityTokenHandler().WriteToken(Token));
}
```
Here Agent is a model with properties *Name* as string and *ClearanceLevel* as int.

Now when you go back to swagger and try to execute *GenerateBadge* api it will give you 401 Unauthorised response. Since we have not passed the bearer token we are getting this error. 

To be able to add the Authorize header in Swagger change the *services.AddSwaggerGen* as below:

```
services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "FBI", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Description = "Please enter JWT with Bearer into field",
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement {
    { new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer"}
            },
        new string[] {}
    }
    });
});
```

When you refresh Swagger in your browser you will notice an *Authorize* button on the right side above the list of apis.

Click on the newly added *Authorize* button in Swagger which will open up a dialog. We need to mention what type of token it is. So first enter *Bearer* in the field then a space and then the token generated from the */Admin/Login* api from previous section. 

![Authorization Header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ks0jtoy39ogcpa61s71.png)

Click on the header to lock in the token. Now you are all set. When you execute the *GenerateBadge* api again you will get a token (analogous to badge). Keep this token handy, since we require in next section. Also make sure to **pass ClearanceLevel as 1** for now.

## Agent Controller

Create a new file *AgentController.cs* with below content.

```
using Microsoft.AspNetCore.Mvc;

namespace FBI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    [Authorize(Policy = "Agent")]
    public class AgentController : ControllerBase
    {
        public AgentController() { }
    }
}
```
As you can see above we are authorising the whole controller for Agent's access only. So even Admin won't be able to access the API's we are going to create.

### Access Records API's

Let's add the api's to access both public and classified files. 

```
[HttpGet]
[Route("[action]")]
[Authorize(Policy = "ClearanceLevel1")]
public ActionResult<String> AccessPublicFiles()
{
    return new OkObjectResult("Public Files Accessed");
}

[HttpGet]
[Route("[action]")]
[Authorize(Policy = "ClearanceLevel2")]
public ActionResult<String> AccessClassifiedFiles()
{
    return new OkObjectResult("Classified Files Accessed");
}
```

We have added *Authorize* attribute's for both API's such that public files can be accessed by *ClearanceLevel1* and classified files can be accessed by *ClearanceLevel2*.

If you try to access these API's with the Admin token you will get 403 Forbidden error. So go ahead and click on the *Authorize* button again and click on *logout*. Then, get the token from the above step and paste in the field with *Bearer* as a prefix i.e. *Bearer <agent token>*. 

Now when you access */Agent/AccessPublicFiles* api you will see response 200 with message *Public Files Accessed*. But when you try the classified api you get 403 Forbidden error.

## Changing Clearance Level
Fast forward 3 years and our *Agent's* performance has been mind bogglingly good. Management has now decided to promote him to ClearanceLevel2. 

The *Agent* goes to the *Admin* and asks her to provide a token/badge with Clearance Level 2.

The *Admin* calls the */Admin/Login* api to generate his own token first. She then enters it in the *Authorize* dialog. 

*/Admin/GenerageBadge* api is then called by Admin with value 2 in the ClearanceLevel. This generates a new token/badge which she then hands over to *Agent*.

The *Agent* enters this token/badge in the *Authorize* dialog and when he now calls */Agent/AccessClassifiedFiles* he is pleased to see the result *Classified Files Accessed*.

## Conclusion
You can find the whole project [here](https://github.com/shenanigan/fbi-demo) on github.

API security is extremely important and shouldn't be taken lightly even if it's for internal use only. Setup Authentication and Authorisation and you are halfway there. 

There are other other security measures you can security against DDoS attacks, accepting API's from a particular IP or domain only etc.




How did you like the article? What are the other security measures do you usually take? Any feedbacks or comments?

You can checkout out more tutorials [here](https://arjavdave.com).