How to use identity to secure a Web API backend for single page apps

December 19, 2023 by Anuraj

AspNetCore Security Identity

This post is about how to use Identity to secure a Web API backend for SPAs such as Angular, React, and Vue apps. Unlike ASP.NET Core MVC, Web API projects doesn’t support the --auth Individual option while creating the Web API project from dotnet SDK. We have to manually add the nuget package references. So first we need to create a web api project, we can do that using the following command - ` dotnet new webapi –name Weatherforecast.Api –output Weatherforecast`. Next we need to add reference of different NuGet packages. I am using Sql Server as the backend.

dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

The first NuGet package is for getting the identity related objects. The other two for database interactions and for creating and running migrations.

Once the NuGet packages added, we can create the DbContext class which should be inheriting from IdentityDbContext. Here is the implementation.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace WeatherForecast.Api;

public class WeatherForecastDbContext(DbContextOptions<WeatherForecastDbContext> options) 
    : IdentityDbContext<IdentityUser>(options)
{
}

And then we can modify the Program.cs file like this. First we need to configure the DbContext and then set the Identity Api endpoints, like this.

var connectionString = builder.Configuration.GetConnectionString("WeatherForecastDbConnection");
builder.Services.AddDbContextPool<WeatherForecastDbContext>(options => options.UseSqlServer(connectionString));

builder.Services.AddAuthorization();
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<WeatherForecastDbContext>();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.MapIdentityApi<IdentityUser>();

Also we need to update the appsettings.json with the Sql server connection string. Now we have completed the configuration. Next we can create migrations using the command dotnet ef migrations add InitialMigrations, then create database and apply migrations using dotnet ef database update command.

Now we are ready to run the web api. To protect the Api endpoints, we can use RequireAuthorization() extension method. To protect the /weatherforecast we can do like this.

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecastItem
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi().RequireAuthorization();

For controller based projects, we can use the [Authorize] attribute as well. The RequireAuthorization() method can accept claims or roles if we want to control the access based on specific claims or role.

Here is the screenshot of the swagger UI.

Screenshot of the swagger UI

Here is the complete Program.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("WeatherForecastDbConnection");
builder.Services.AddDbContextPool<WeatherForecastDbContext>(options => options.UseSqlServer(connectionString));

builder.Services.AddAuthorization();
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<WeatherForecastDbContext>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.MapIdentityApi<IdentityUser>().WithTags("Identity");

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecastItem
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi().WithTags("WeatherForecast")
.RequireAuthorization();

app.Run();

record WeatherForecastItem(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

public class WeatherForecastDbContext(DbContextOptions<WeatherForecastDbContext> options) 
    : IdentityDbContext<IdentityUser>(options)
{
}

This way we can configure ASP.NET Core Identity to protect a Backend API. This method works well with both Cookie based and Token based authentication models.

Happy Programming.

Copyright © 2024 Anuraj. Blog content licensed under the Creative Commons CC BY 2.5 | Unless otherwise stated or granted, code samples licensed under the MIT license. This is a personal blog. The opinions expressed here represent my own and not those of my employer. Powered by Jekyll. Hosted with ❤ by GitHub