JWT and OIDC
Install the Microsoft.AspNetCore.Authentication.JwtBearer NuGet Package.
In the Program.cs:
builder.Services.AddAuthentication().AddJwtBearer(options =>{ options.RequireHttpsMetadata = !builder.Environment.IsDevelopment();});builder.Services.AddAuthorization();Go to a terminal in your project’s working directory and run:
dotnet user-jwts createdotnet user-jwts create -n bob@aol.comdotnet user-jwts create -n sue@aol.com --role Admin --role Bossdotnet user-jwts create -n carl@aol.com --role Reader --valid-for 1095d(the last example makes the token valid for three years.)
This will update your appsettings.development.json:
{ "Authentication": { "Schemes": { "Bearer": { "ValidAudiences": [ "http://localhost:35191", "https://localhost:0", "http://localhost:5008" ], "ValidIssuer": "dotnet-user-jwts" } } }}Add To Swagger / OpenAPI
builder.Services.AddSwaggerGen(options =>{ options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "JWT Authorization header with bearer token", Name = "Authorization", In = ParameterLocation.Header, Scheme = "Bearer" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme }, Scheme = "oauth2", Name = "Bearer ", In = ParameterLocation.Header }, [] } });});In an HTTP File
# For more info on HTTP files go to https://aka.ms/vs/httpfile@host=localhost@port=5008
# dotnet user-jwts create -n carl@aol.com --role Reader --valid-for 1095d@carlToken=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImNhcmxAYW9sLmNvbSIsInN1YiI6ImNhcmxAYW9sLmNvbSIsImp0aSI6IjhmYzQ1ZTY0Iiwicm9sZSI6IlJlYWRlciIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjM1MTkxIiwiaHR0cHM6Ly9sb2NhbGhvc3Q6MCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTAwOCJdLCJuYmYiOjE3MTQ1NjQwMzcsImV4cCI6MTgwOTE3MjAzNywiaWF0IjoxNzE0NTY0MDM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.V2TtyKtrxbyL5iRf2gax8122TPwgEspb0k7DZXEPgok
# dotnet user-jwts create -n sue@aol.com --role Admin --role Boss@sueToken=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1ZUBhb2wuY29tIiwic3ViIjoic3VlQGFvbC5jb20iLCJqdGkiOiJkYTVhYzY3ZiIsInJvbGUiOlsiQWRtaW4iLCJCb3NzIl0sImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjM1MTkxIiwiaHR0cHM6Ly9sb2NhbGhvc3Q6MCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTAwOCJdLCJuYmYiOjE3MTQ1NjUyMDAsImV4cCI6MTcyMjUxNDAwMCwiaWF0IjoxNzE0NTY1MjAwLCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.3ksg9uDVqypqSnnZcheYrE_9bKEs7tZd1166tL4ViNwGET http://{{host}}:{{port}}/tacosAuthorization: {{sueToken}}
###
# This will get a 403 - Forbidden (we know who you are, and no.)GET http://{{host}}:{{port}}/tacosAuthorization: {{carlToken}}
###
# This will get a 401 - Unauthorized (We don't know who you are)GET http://{{host}}:{{port}}/tacosAccessing Claims in Middleware (Wolverine)
public static class UserDetectionMiddleware{ public static async Task<(UserData?, ProblemDetails)> Load(ClaimsPrincipal principal,IDocumentSession session) { var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim is not null) { var savedUser = await session.Query<User>().Where(u => u.Sub == userIdClaim.Value).SingleOrDefaultAsync(); if (savedUser is null) { var id = Guid.NewGuid(); session.Events.StartStream(id, new UserCreated(userIdClaim.Value)); await session.SaveChangesAsync(); return (new UserData(id, userIdClaim.Value), WolverineContinue.NoProblems); } else { return (new UserData(savedUser.Id, userIdClaim.Value), WolverineContinue.NoProblems); } } return (null, new ProblemDetails { Detail = "No User", Status = 400 }); }}Adding Authorization Policies
See Policy-based authorization in ASP.NET Core | Microsoft Learn
builder.Services.AddAuthorization(options =>{ options.AddPolicy("IsAdmin", policy => policy.RequireRole("Admin"));});Use with Minimal API:
app.MapGet("/tacos", () =>{ return "Delicious";}).RequireAuthorization("IsAdmin");For Controller Methods, you can add the Authorize attribute:
[Authorize(Roles = "Admin", Policy = "IsCool")]Stubbing with Alba
api.WithClaim(new Claim(ClaimTypes.NameIdentifier ,"jeff@hypertheory.com")); // adds "sub" claim api.WithClaim(new Claim(ClaimTypes.Role, "Admin")); api.WithClaim(new Claim(ClaimTypes.Role, "User"));
use the mock OAuth2 / OIDC server from here: [navikt/mock-oauth2-server: A scriptable/customizable web server for testing HTTP clients using OAuth2/OpenID Connect or applications with a dependency to a running OAuth2 server (i.e. APIs requiring signed JWTs from a known issuer) (github.com)](https://github.com/navikt/mock-oauth2-server)
```ymlservices: auth: image: ghcr.io/navikt/mock-oauth2-server:2.1.9 ports: - 9090:8080Add the Microsoft.AspNetCore.Authentication.JwtBearer NuGet.
Configuring the Auth:
In your appsettings.Development.json:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "Authentication": {
"Schemes": { "Bearer": { "MetadataAddress": "http://localhost:9090/default/.well-known/openid-configuration", "RequireHttpsMetadata": false, "ValidateAudience": false } } }}In Program.cs
builder.Services.AddAuthentication().AddJwtBearer();builder.Services.AddAuthorization();
// after build
app.UseAuthentication();app.UseAuthorization();Example Endpoint:
app.MapGet("/sayhi", (ClaimsPrincipal principal) => { var user = principal.FindFirstValue(ClaimTypes.NameIdentifier); return $"Hello, {user}"; }) .WithOpenApi().RequireAuthorization();}Generating Tokens with a Bash Script
#!/bin/bash
# Check if name argument is providedif [ -z "$1" ]; then echo "Usage: $0 <name> [role1] [role2] ..." echo "Example: $0 boss@company.com SoftwareCenter Admin" exit 1fi
NAME=$1shift # Remove the first argument (name) so we can process the rest as roles
echo "Generating token for $NAME"
# Detect OS and set clipboard commandif [[ "$OSTYPE" == "darwin"* ]]; then CLIP_CMD="pbcopy"elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then CLIP_CMD="clip"else CLIP_CMD="xclip -selection clipboard" # Linux fallbackfi
# Build the dotnet command with rolesCMD="dotnet user-jwts create -n $NAME"
# Add each role as a --role argumentfor role in "$@"; do CMD="$CMD --role $role"done
# Execute the command and copy token to clipboardeval $CMD | grep 'Token:' | awk '{print $2}' | $CLIP_CMD
echo "Token for $NAME is in your clipboard."