Authentication, Authorization and Security

Connected vehicles and diagnostic back-ends are regulated targets (ISO/SAE 21434, UNECE R155 cybersecurity).

Concept

Authentication vs Authorization Authentication — *who are you?* The result is a ClaimsPrincipal with claims (name, roles, plant, scopes).

Authorization — *what may you do?* Decided from the claims: roles (RequireRole("Engineer")), policies (combinations of requirements: role + claim + custom logic), resource-based checks.

Unauthenticated → 401 Unauthorized (with a WWW-Authenticate header); authenticated but not allowed → 403 Forbidden.

Browser apps (server-rendered, Blazor Server); mark them HttpOnly, Secure, SameSite JWT bearer tokens

APIs and SPAs/mobile clients: Authorization: Bearer <token>; signed (not encrypted), short-lived, validated for issuer, audience, lifetime and signature

Delegated *authorization*: a client obtains access tokens from an authorization server OpenID Connect

*Authentication* on top of OAuth 2.0 (ID tokens) — Entra ID, Auth0, Keycloak ASP.NET Core Identity

User store, password hashing, lockout, 2FA, external logins for your own user database Threat / topic Input validation

Validate type, length, range, format at every boundary; reject by default SQL injection

Parameterized queries / EF Core (Module 49) Encode output (HtmlEncoder, Razor encodes by default), Content-Security-Policy

Antiforgery tokens for cookie-authenticated forms; SameSite cookies

Never store plain text or fast hashes; use PasswordHasher<T> (PBKDF2, salted, versioned), Argon2 or bcrypt; constant-time comparison Secrets management

User secrets in development, a vault in production; never in Git, logs or URLs

UseHttpsRedirection, HSTS, valid certificates; TLS 1.2+ Token security

Short lifetimes, refresh tokens, validate everything, never put secrets in JWT payloads (they are readable) Sensitive data & logging

Redact tokens, passwords, personal data before logging dotnet list package --vulnerable, Dependabot, update promptly (Module 46) Least privilege

Minimal DB permissions, scoped tokens, separate service accounts

Why It Matters

Connected vehicles and diagnostic back-ends are regulated targets (ISO/SAE 21434, UNECE R155 cybersecurity). A single injection flaw, leaked key or missing authorization check can let an attacker flash ECUs or read customer data.

Syntax

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o => o.TokenValidationParameters = ...);
builder.Services.AddAuthorizationBuilder().AddPolicy("CanFlash", p => p.RequireRole("Engineer").RequireClaim("plant", "PUNE"));
app.UseAuthentication(); app.UseAuthorization();
app.MapPost("/ecus/{code}/flash", ...).RequireAuthorization("CanFlash");
string hash = new PasswordHasher<User>().HashPassword(user, password);

Example

52.1 JWT authentication, roles, policies, and core security techniques

The program uses the Microsoft.AspNetCore.Authentication.JwtBearer package. It issues its own tokens with a random key (in real systems an identity provider issues them), then calls protected endpoints with different tokens.

#:sdk Microsoft.NET.Sdk.Web
#:package [email protected]
#:property PublishAot=false
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;

// The signing key would come from a secret store (user-secrets, Key Vault), never from source code
byte[] keyBytes = RandomNumberGenerator.GetBytes(32);
var signingKey = new SymmetricSecurityKey(keyBytes);
const string Issuer = "https://auth.example.local", Audience = "diag-api";

var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.WebHost.UseUrls("http://127.0.0.1:0");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(o => o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidIssuer = Issuer, ValidAudience = Audience, IssuerSigningKey = signingKey,
        ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true,
        ClockSkew = TimeSpan.Zero,
 
…

Expected Result

No token → 401 with a WWW-Authenticate: Bearer challenge; a valid viewer token → 403 because the CanFlash policy requires the Engineer role.

The expired and tampered tokens were rejected with precise reasons (the expiry time is the run's own clock).

The decoded payload shows that JWT claims are readable by anyone who holds the token — sign, don't hide.

Two hashes of the same password differ because each has a random salt, yet both verify.

GET /public/health (anonymous)   -> 200 OK  ok
GET /me (no token)               -> 401 Unauthorized  [Bearer]
GET /me (engineer)               -> 200 OK  asha roles=[Engineer]
POST flash (engineer)            -> 200 OK  flashing VCU
POST flash (viewer)              -> 403 Forbidden
GET /me (expired token)          -> 401 Unauthorized  [Bearer error="invalid_token", error_description="The token expired at '09/30/2026 03:06:32'"]
GET /me (tampered signature)     -> 401 Unauthorized  [Bearer error="invalid_token", error_description="The signature key was not found"]
JWT header: HS256; the payload is only Base64Url-encoded, NOT encrypted - anyone can read the claims:
  http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name = asha
  plant = PUNE
  http://schemas.microsoft.com/ws/2008/06/identity/claims/role = Engineer
same password, different hashes (random salt): True; length 84
verify correct: Success, wrong: Failed
PBKDF2 derived 32 bytes; constant-time compare equal: True
HTML-encoded: &lt;script&gt;alert(&#x27;x&#x27;)&lt;/script&gt;
redacted for logs: Server=db;User Id=diag;Password=***;

Common Mistakes

Confusing 401 and 403, or authentication and authorization.

Hard-coded signing keys and connection strings in source code.

Long-lived tokens without revocation; accepting tokens without validating audience/issuer/lifetime.

Storing passwords with SHA-256 or MD5 (fast hashes are brute-forced easily).

Building HTML by concatenating user input (XSS).

Logging full requests including Authorization headers.

Checking authorization only in the UI, not on the server.

Best Practices

Use a proven identity provider (Entra ID, Auth0, Keycloak, Duende) with OpenID Connect; don't invent protocols.

Policies over scattered role checks; authorize on every endpoint by default (FallbackPolicy).

Secrets in vaults, rotated; HTTPS everywhere with HSTS.

Encode output, validate input, parameterize SQL.

Threat-model new features (STRIDE) and scan dependencies continuously.

Interview Relevance

Very high: "authentication vs authorization?", "401 vs 403?", "how does JWT validation work?", "OAuth vs OpenID Connect?", "how should passwords be stored?", "how do you prevent XSS, CSRF and SQL injection?", "where do you store secrets?".

Practice Questions

Add a FallbackPolicy that requires authentication for every endpoint except those marked AllowAnonymous().

Write a custom authorization requirement "may flash only during working hours" with an IAuthorizationHandler.

Add refresh tokens: a long-lived opaque token that can be exchanged for a new short-lived JWT.

Store a connection string with dotnet user-secrets set and read it through configuration.

Quick Quiz

Q1. (Multiple choice) A valid token for a user who lacks the required role gets: A) 400 B) 401 C) 403 D) 404

Answer: C (verified: the viewer received 403 Forbidden).

Q2. (True/False) The claims in a signed JWT are encrypted and cannot be read without the key.

Answer: False. They are only Base64Url-encoded (verified: the payload was decoded without the key). The signature only prevents *modification*.

Q3. (Predict the output) hasher.HashPassword(u, "x") == hasher.HashPassword(u, "x")

Answer: False — each hash has a random salt (verified), but VerifyHashedPassword succeeds for both.

Q4. (Find the bug) app.UseAuthorization(); app.UseAuthentication();

Answer: Authentication must run first; otherwise authorization sees an anonymous user and every protected endpoint returns 401.

Coding Challenge

Write HashPassword(string password) and Verify(string password, string stored) using PBKDF2 (SHA-256, 600 000 iterations, 16-byte salt, 32-byte key) with the format iterations.salt.hash in Base64 and a constant-time comparison.

using System.Security.Cryptography;

string stored = HashPassword("S3cure!Bench");
Console.WriteLine($"format ok: {stored.Split('.').Length == 3}, starts with iterations: {stored.StartsWith("600000.")}");
Console.WriteLine($"correct password: {Verify("S3cure!Bench", stored)}, wrong password: {Verify("s3cure!bench", stored)}");

static string HashPassword(string password)
{
    const int iterations = 600_000;
    byte[] salt = RandomNumberGenerator.GetBytes(16);
    byte[] hash = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, 32);
    return $"{iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}";
}

static bool Verify(string password, string stored)
{
    string[] parts = stored.Split('.');
    int iterations = int.Parse(parts[0]);
    byte[] salt = Convert.FromBase64String(parts[1]);
    byte[] expected = Convert.FromBase64String(parts[2]);
    byte[] actual = Rfc2898DeriveBytes.Pbkdf2(password, salt, iterations, HashAlgorithmName.SHA256, expected.Length);
    return CryptographicOperations.FixedTimeEquals(actual, expected);
}