guides

ASP.NET Core Identity med separat database

Opsæt Identity i egen database og projektdata i en separat database.

1) Hvad er idéen?

ASP.NET Core Identity er Microsofts login-system til .NET. Det kan håndtere brugere, passwords, roller, claims, password hashing og login-flow.

Normalt kan Identity-tabellerne ligge i samme database som resten af projektet. Men i større projekter kan det være mere ryddeligt at splitte det op.

  • AuthDb bruges til Identity-tabeller som brugere, roller og login.
  • ProjectDb bruges til projektets egne data, for eksempel produkter, opgaver, noter eller scanninger.

Fordelen er, at authentication og projektdata ikke bliver blandet sammen. Det gør projektet nemmere at forstå og nemmere at vedligeholde.

2) Installer pakker

Først installerer vi de NuGet-pakker, som projektet skal bruge. EF Core bruges til databaserne, Identity bruges til brugere, og JWT bruges til token-baseret login i API'et.

Terminal

bash · 4 lines

bash
1dotnet add package Microsoft.EntityFrameworkCore.SqlServer
2dotnet add package Microsoft.EntityFrameworkCore.Design
3dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
4dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Hvad bruges pakkerne til?

  • Microsoft.EntityFrameworkCore.SqlServerforbinder EF Core til SQL Server.
  • Microsoft.EntityFrameworkCore.Designbruges når vi laver migrations fra terminalen.
  • Microsoft.AspNetCore.Identity.EntityFrameworkCoregør at Identity kan gemme brugere via EF Core.
  • Microsoft.AspNetCore.Authentication.JwtBearergør at API'et kan validere JWT tokens.

3) Connection strings

Nu opretter vi to connection strings. Det er her vi fortæller appen, hvor databaserne ligger.

Det vigtige er, at AuthConnection og ProjectConnection peger på hver sin database.

appsettings.json

json · 12 lines

json
1{
2 "ConnectionStrings": {
3 "AuthConnection": "Server=localhost;Database=AuthDb;Trusted_Connection=True;TrustServerCertificate=True;",
4 "ProjectConnection": "Server=localhost;Database=ProjectDb;Trusted_Connection=True;TrustServerCertificate=True;"
5 },
6
7 "Jwt": {
8 "Key": "THIS_IS_A_LONG_SECRET_KEY_FOR_DEVELOPMENT_ONLY",
9 "Issuer": "MyApi",
10 "Audience": "MyApiUsers"
11 }
12}

Husk

  • AuthConnection bruges af AuthDbContext.
  • ProjectConnection bruges af ProjectDbContext.
  • Jwt:Key skal være længere og mere sikker i et rigtigt projekt.

4) ApplicationUser

Identity har allerede en standard brugerklasse, som hedder IdentityUser. Den indeholder felter som id, email, username, password hash og security stamp.

Hvis vi vil gemme ekstra information på brugeren, laver vi vores egen klasse, der arver fra IdentityUser.

Models/Auth/ApplicationUser.cs

csharp · 8 lines

csharp
1using Microsoft.AspNetCore.Identity;
2
3namespace MyApi.Models.Auth;
4
5public class ApplicationUser : IdentityUser
6{
7 public string DisplayName { get; set; } = "";
8}

Her tilføjer vi kun DisplayName, men du kan senere tilføje flere felter, hvis projektet har brug for det.

5) AuthDbContext

AuthDbContext er den database-context, som kun skal bruges til Identity.

Derfor arver den fra IdentityDbContext<ApplicationUser> i stedet for almindelig DbContext.

Data/AuthDbContext.cs

csharp · 13 lines

csharp
1using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
2using Microsoft.EntityFrameworkCore;
3using MyApi.Models.Auth;
4
5namespace MyApi.Data;
6
7public class AuthDbContext : IdentityDbContext<ApplicationUser>
8{
9 public AuthDbContext(DbContextOptions<AuthDbContext> options)
10 : base(options)
11 {
12 }
13}

Når vi laver migration for denne context, laver EF Core Identity-tabeller som AspNetUsers, AspNetRoles og AspNetUserRoles.

6) ProjectDbContext

ProjectDbContext er projektets normale database. Det er her dine egne modeller skal ligge.

I dette eksempel bruger vi bare en simpel Product model, så guiden er nem at genbruge.

Models/Project/Product.cs

csharp · 7 lines

csharp
1namespace MyApi.Models.Project;
2
3public class Product
4{
5 public int Id { get; set; }
6 public string Name { get; set; } = "";
7}
Data/ProjectDbContext.cs

csharp · 14 lines

csharp
1using Microsoft.EntityFrameworkCore;
2using MyApi.Models.Project;
3
4namespace MyApi.Data;
5
6public class ProjectDbContext : DbContext
7{
8 public ProjectDbContext(DbContextOptions<ProjectDbContext> options)
9 : base(options)
10 {
11 }
12
13 public DbSet<Product> Products => Set<Product>();
14}

Senere kan du udskifte Product med dine egne modeller, for eksempel Scan, Recipeeller Note.

7) Program.cs

I Program.cs samler vi hele opsætningen. Her registrerer vi begge databaser, Identity og JWT authentication.

Program.cs

csharp · 64 lines

csharp
1using System.Text;
2using Microsoft.AspNetCore.Authentication.JwtBearer;
3using Microsoft.AspNetCore.Identity;
4using Microsoft.EntityFrameworkCore;
5using Microsoft.IdentityModel.Tokens;
6using MyApi.Data;
7using MyApi.Models.Auth;
8
9var builder = WebApplication.CreateBuilder(args);
10
11builder.Services.AddControllers();
12
13builder.Services.AddDbContext<AuthDbContext>(options =>
14{
15 options.UseSqlServer(
16 builder.Configuration.GetConnectionString("AuthConnection"));
17});
18
19builder.Services.AddDbContext<ProjectDbContext>(options =>
20{
21 options.UseSqlServer(
22 builder.Configuration.GetConnectionString("ProjectConnection"));
23});
24
25builder.Services
26 .AddIdentity<ApplicationUser, IdentityRole>()
27 .AddEntityFrameworkStores<AuthDbContext>()
28 .AddDefaultTokenProviders();
29
30var jwtKey = builder.Configuration["Jwt:Key"] ?? "";
31var keyBytes = Encoding.UTF8.GetBytes(jwtKey);
32
33builder.Services
34 .AddAuthentication(options =>
35 {
36 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
37 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
38 })
39 .AddJwtBearer(options =>
40 {
41 options.TokenValidationParameters = new TokenValidationParameters
42 {
43 ValidateIssuer = true,
44 ValidateAudience = true,
45 ValidateIssuerSigningKey = true,
46 ValidateLifetime = true,
47 ValidIssuer = builder.Configuration["Jwt:Issuer"],
48 ValidAudience = builder.Configuration["Jwt:Audience"],
49 IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
50 };
51 });
52
53builder.Services.AddAuthorization();
54
55var app = builder.Build();
56
57app.UseHttpsRedirection();
58
59app.UseAuthentication();
60app.UseAuthorization();
61
62app.MapControllers();
63
64app.Run();

Hvad sker der i Program.cs?

  • AddDbContext<AuthDbContext> kobler Identity-databasen på.
  • AddDbContext<ProjectDbContext> kobler projektets database på.
  • AddIdentity aktiverer ASP.NET Core Identity.
  • AddEntityFrameworkStores<AuthDbContext>fortæller Identity, at brugere skal gemmes i AuthDbContext.
  • AddAuthentication og AddJwtBearer gør API'et klar til JWT login.
  • UseAuthentication skal stå før UseAuthorization.

8) Lav migrations til hver database

Fordi projektet har to DbContext klasser, skal EF Core vide, hvilken database migrationen skal laves til.

Derfor bruger vi --context, når vi laver og kører migrations.

Terminal

bash · 7 lines

bash
1dotnet ef migrations add InitAuthDb --context AuthDbContext --output-dir Migrations/AuthDb
2
3dotnet ef migrations add InitProjectDb --context ProjectDbContext --output-dir Migrations/ProjectDb
4
5dotnet ef database update --context AuthDbContext
6
7dotnet ef database update --context ProjectDbContext

Typisk fejl

Hvis du glemmer --context, kan EF Core blive i tvivl om hvilken database den skal bruge.

9) Beskyt en controller med login

Når JWT authentication er sat op, kan vi beskytte endpoints med [Authorize].

I eksemplet bruger controlleren ProjectDbContext, fordi products hører til projektets data — ikke Identity-databasen.

Controllers/ProductsController.cs

csharp · 26 lines

csharp
1using Microsoft.AspNetCore.Authorization;
2using Microsoft.AspNetCore.Mvc;
3using Microsoft.EntityFrameworkCore;
4using MyApi.Data;
5using MyApi.Models.Project;
6
7namespace MyApi.Controllers;
8
9[ApiController]
10[Route("api/[controller]")]
11[Authorize]
12public class ProductsController : ControllerBase
13{
14 private readonly ProjectDbContext db;
15
16 public ProductsController(ProjectDbContext db)
17 {
18 this.db = db;
19 }
20
21 [HttpGet]
22 public async Task<ActionResult<List<Product>>> GetProducts()
23 {
24 return await db.Products.ToListAsync();
25 }
26}

10) Mental model

Den nemmeste måde at tænke på strukturen er sådan her:

  • Login, brugere og roller går gennem AuthDbContext.
  • Appens egne data går gennem ProjectDbContext.
  • Controllers vælger den context, de har brug for.
  • Migrations køres separat for hver context.

11) Kort opsummering

  • AuthDbContext styrer Identity-tabeller.
  • ProjectDbContext styrer projektets egne tabeller.
  • Begge contexts får hver sin connection string.
  • Identity bruger AddEntityFrameworkStores<AuthDbContext>.
  • Migrations skal køres med --context.