Simple Authentication and Authorisation in ASP.NET Core Web API
Web API often needs to be protected from malicious users. This article aims to show how simple authentication and authorisation can be implemented in a sample ASP.NET Core Web API app (“sample app”).
I have used the following tools to build the sample app:
- Visual Studio Code (my favourite code editor)
- SQL Server (it was run inside a Docker container because the sample app was built in macOS)
- EntityFramework Core (a good ORM that came with ASP.NET Core)
How the Simple Authentication and Authorisation Works
With authentication and authorisation implemented, my sample ASP.NET Core Web API app only accepts HTTP request with valid JSON web token (JWT). A database table is used to store the list of approved users.
Below is a brief illustration of how the sample app works:
- A client app sends an HTTP request to the sample app which resides in a server to authenticate the user.
- The sample app uses the data received to check if there is a matching entry in the database. If there is a matching user in the database, the app will send a message with an OK result and a JSON web token (JWT) back to the client app. The JWT also includes various claims which can be used to determine the user permission to certain endpoints.
- After receiving the JWT, the client app will include this token in the header of each subsequent HTTP request to the sample app.
Steps to Implement Authentication
Doesn’t it look easy? It is indeed not difficult to implement the above-mentioned mechanism in a Web API app. The steps I have taken to create this sample app is shown below.
Step 1: Add User table in database
First, we need a database to store the information of permitted users. Here I used EntityFramework Core to add a table called User
in my SQL Server database with a couple of simple steps:
- Create a domain class
User
with some properties such asUserName
,Password
, andToken
. - Use the bash commands
dotnet ef migrations add [migration name]
anddotnet ef database update
to create the table in the database. After the database is created using the migrations, I then seeded theUsers
table with some entries.
Step 2: Get Secret key and prepare authentication at StartUp class
A secret key is used to encrypt the JWT. It can be stored inside our appsettings.json
file. We need to retrieve this value in our application. To achieve this, I made use of the Configuration
class. First I created a IdentitySettings
domain model with a single propertySecret
:
public class IdentitySettings
{
public string Secret { get; set; }
}
which would be used to map the Secret
from the appsettings.json
file.
"IdentitySettings":{
"Secret":"THIS IS A SECRET KEY"
},
I also needed to instantiate this object in StartUp
class along with codes necessary for authentication:
Lastly, I enabled authentication in the Configure
method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
...
}
Step 3: Implementing AuthService
I created a new interface IAuthService
and implement it with AuthService
:
This AuthService
class contains a method called Authenticate
which would be used to verify the incoming user detail against the database before generating a token string which would eventually sent back to the sender (the client app).
Step 4: Listen to authentication request in UsersController
Since this sample app follows an MVC architecture pattern, a controller was used to ensure the request was received and directed to appropriate services. In my case, a UserController
was used to listen to this request and AuthService
was used to get the job done.
[Authorize]
[Route("/api/users")]
public class UsersController : Controller
{
... [AllowAnonymous]
[HttpPost("auth")]
public IActionResult Authenticate([FromBody] User user)
{
var validUser = authService.Authenticate(user.UserName); if(validUser == null)
return BadRequest(); return Ok(validUser);
}
}
Step 5: Authorisation and claims
To this stage, the authentication is considered done. Controller or endpoints can now be protected from invalid or unregconised users. Optionally, we could make this app better by adding the customised authorisation. For example, only user with role as admin could work with the DocumentsController
. In my sample app, I used claim-based authorisation for HTTP requests:
[Authorize("IsAdmin")]
[Route("api/documents")]
public class DocumentsController : Controller
{
...
}
which required authorisation options to be declared in StartUp
class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("IsAdmin", policy =>
{
policy.RequireClaim(ClaimTypes.Role, "admin");
});
});
}
and this claim was read from database and written to the token in AuthService
class Authenticate
method:
var tokenDescriptor = new SecurityTokenDescriptor
{
... Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Role, user.Role)
} ...
}
This sample Web API app shows how authentication and authorisation can be easily implemented without using any third-party library. Certainly, this sample only verified user with just the UserName
. In real world application, password must be used for identity verification.