DEV Community

Cover image for Setting up .NET Core Identity with PostgreSQL
Marian Salvan
Marian Salvan

Posted on • Edited on

Setting up .NET Core Identity with PostgreSQL

Introduction

One thing that a web application, or any other application for that matter, cannot do without is user authentication, authorization, and account management. In this series, I am going to exemplify how you can use .NET Core Identity to leverage existing capabilities without reinventing the wheel. This will be a step-by-step series, and in the first part, I will show you how to configure a simple .NET Core web API to use this framework.

For this tutorial, I am going to use .NET 9 and PostgreSQL as the database. Additionally, I will demonstrate how to use Docker Compose to run the application, although the tutorial can also be completed without Docker. Without further ado, let's jump into it.

Step 1 - Creating a new project

Create a .NET core API - use the default weather app. If you use Visual Studio your app configuration should look something like this:

Image description

Step 2 - Adding proper tools and packages

Open a terminal at root of you project and run the following commands:

  • PostgreSQL package:
    dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

  • AspNetCore.Identity package:
    dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore

  • Entity Framework tools (needed for running the database migration):
    dotnet tool install --global dotnet-ef

  • Entity Framework Design package (also needed for running the migration):
    dotnet add package Microsoft.EntityFrameworkCore.Design

Step 3 - Custom UserEntity

In most applications using the default IdentityUser class provided by .NET core Identity is not enough. Therefore, in this tutorial, we are going to extend this class and use it to create our database context.

using Microsoft.AspNetCore.Identity;

namespace DemoBackend.Data
{
    public class UserEntity : IdentityUser
    {
        //extend the IdentityUser with your custom additional properties
        public int Age { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4 - Creating the database context

The database context is using the custom UserEntity class defined at the previous step. Inside the OnModelCreating method we are seeding the roles table with 2 roles (which we are going to use later in this series for exemplifying the authorization).

using DemoBackend.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace DemoBackend.Data
{
    public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : IdentityDbContext<UserEntity>(options)
    {
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            //seeding data for roles - we will use these roles in the future
            var adminRoleId = "d1b6a7e1-8c4b-4b8a-9b1d-1a2b3c4d5e6f";
            var userRoleId = "e2c7b8f2-9d5c-4e8b-8a1d-2b3c4d5e6f7g";

            builder.Entity<IdentityRole>().HasData(
                new IdentityRole
                {
                    Id = adminRoleId,
                    Name = "Admin",
                    NormalizedName = "ADMIN"
                },
                new IdentityRole
                {
                    Id = userRoleId,
                    Name = "User",
                    NormalizedName = "USER"
                }
            );
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5 - Adding Identity configuration

Now that we have the database context, we need to configure the database connection and Identity settings. All these configurations are demonstrated in the following extension method:

using DemoBackend.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace DemoBackend.Extensions
{
    public static class DbConnectionExtensions
    {
        public static void AddIdentityDbContext(this IServiceCollection services, WebApplicationBuilder builder)
        {
            var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseNpgsql(connectionString));

            //this service is required by the DataProtectorTokenProvider used in ASP.NET Core Identity
            services.AddDataProtection();

            //Identity configuration
            services.AddIdentityCore<UserEntity>(options =>
            {
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireDigit = false;
                options.Password.RequireUppercase = false;
                options.Password.RequiredLength = 4;
            })
           .AddRoles<IdentityRole>()
           .AddEntityFrameworkStores<ApplicationDbContext>()
           .AddTokenProvider<DataProtectorTokenProvider<UserEntity>>(TokenOptions.DefaultProvider);

            //this is required to be able to use the UserManager service with the UserEntity
            services.TryAddScoped<UserManager<UserEntity>>();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6 - Running the project

You can either run the project using Docker Compose (in which case you need a properly configured Dockerfile), or you can simply run it as a normal web API (in this case, you will need PostgreSQL installed on your local machine). I prefer running it with Docker Compose because it eliminates the need to install any dependencies on my local machine and avoids potential installation issues.

Resources needed to run with docker compose

  1. Assure that you have Docker installed

  2. Dockerfile - if you created the project using the configuration from step 1 you should already have it

  3. docker-compose.yaml (placed at same level as you Dockerfile) - please assure that the Dockerfile exposes the proper port (8080, in this case).

version: '3.9'

services:
  webapi:
    image: demo-backend
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - db
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=Host=db;Database=postgres;Username=postgres;Password=test

  db:
    image: postgres:latest
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: test
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
Enter fullscreen mode Exit fullscreen mode

Resources needed to run without docker

  1. PotgresSQL installed locally

  2. Connection string inside the appsettings.json file

{
 "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=postgres;Username=postgres;Password=test"
  }
  //other configurations
}
Enter fullscreen mode Exit fullscreen mode

Step 7 - Add the proper configuration to your Program.cs

You should add the AddIdentityDbContext extension method defined above. Additionally, you can include the migration logic to run at application startup. This will automatically apply any pending migrations without requiring manual execution. Your Program.cs file should look like this:

using DemoBackend;
using DemoBackend.Extensions;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

//extension method defined at step 4
builder.Services.AddIdentityDbContext(builder);

var app = builder.Build();

// Apply migrations on startup
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<ApplicationDbContext>();
    if (context.Database.GetPendingMigrations().Any())
    {
        context.Database.Migrate();
    }
}

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
Enter fullscreen mode Exit fullscreen mode

Step 8 - Creating the migration

Now that you have everything setup, open a terminal at the root of your project and run the following command:

dotnet ef migrations add InitialMigration

This will create the Initial migration with the default Identity tables.

To apply this migration to your database, you can either run the project if you added the migration logic in your Program.cs file, or you can run it manually using the following command:

dotnet ef database update

Step 9 - Running the project

To run you project using docker compose, open a terminal at the root of you API and run the following command: docker compose up --build

This should start both your database instance and your API. If you added the migration logic to the Program.cs, it will also automatically apply the migration to the database specified in the Docker Compose webapi environment configuration.

Step 10 - Check if the Identity tables have been created properly

If everything is successful, you should have the tables created as shown below. For more information about these tables check this official documentation.

Image description

Conclusions

In this tutorial, we have learned how to set up .NET Core Identity for a .NET Core web API using PostgreSQL. In the next session, we will build on this tutorial by implementing token-based authentication.

Top comments (0)