Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AuthenticationDemo

AuthenticationDemo is a .NET 10 learning project that shows how JWT access tokens and refresh tokens work together in an ASP.NET Core Web API and a Blazor Web App.

Project Overview

JWT authentication lets an API trust a signed token instead of keeping server-side session state for every request. An access token is short-lived and sent to protected API endpoints. A refresh token is longer-lived and used only to get a new access token when the old one expires.

Both are used because short-lived access tokens reduce the damage from token theft, while refresh tokens keep the user from logging in again every few minutes. This sample stores refresh tokens in SQL Server so they can be revoked and rotated.

Architecture

AuthenticationDemo.slnx
src/
  AuthenticationDemo.Api      ASP.NET Core Web API, Identity, EF Core, JWT, Swagger
  AuthenticationDemo.Client   Blazor Web App that logs in and calls protected APIs
  AuthenticationDemo.Shared   DTOs shared by API and client
tests/
  AuthenticationDemo.Tests    Unit tests for token behavior

The API keeps authentication rules in services, database access in a repository, Identity data in EF Core, and HTTP concerns in controllers and middleware.

Technologies Used

  • .NET 10 and C# 14 for the application platform.
  • ASP.NET Core Web API for authentication and protected endpoints.
  • ASP.NET Core Identity for password hashing, users, and roles.
  • EF Core with SQL Server LocalDB for persistence.
  • JWT bearer authentication for stateless API authorization.
  • Blazor Web App for the frontend sample.
  • Swagger/Swashbuckle for interactive API testing with Bearer tokens.
  • xUnit for focused unit tests.

Authentication Flow

Registration

sequenceDiagram
User->>API: POST /api/auth/register
API->>Identity: Create user and hash password
Identity->>DB: Save user
API-->>User: 201 Created
Loading

Login

sequenceDiagram
User->>API: POST /api/auth/login
API->>Identity: Validate email and password
API->>DB: Save refresh token
API-->>User: Access token + refresh token
Loading

Refresh Token

sequenceDiagram
User->>API: POST /api/auth/refresh
API->>API: Validate expired JWT signature
API->>DB: Check refresh token is active
API->>DB: Revoke old token and save replacement
API-->>User: New access token + new refresh token
Loading

Logout

sequenceDiagram
User->>API: POST /api/auth/logout
API->>DB: Revoke refresh token
API-->>User: Logged out
Loading

JWT Explained

A JWT has three parts:

  • Header: token type and signing algorithm.
  • Payload: claims such as user id, email, name, roles, and expiration.
  • Signature: proof that the API signed the token and it was not changed.

The API validates issuer, audience, signing key, and expiration for protected endpoints. The refresh endpoint validates the expired token signature while ignoring lifetime so it can safely read the user id.

Refresh Tokens Explained

Refresh tokens are random 64-byte values stored in the database. Each token has Expires, Created, Revoked, ReplacedByToken, IsExpired, IsRevoked, and IsActive. When refresh succeeds, the old token is revoked and a new refresh token is saved. This is refresh-token rotation.

For simplicity, the Blazor sample stores tokens in browser local storage. That is easy to inspect while learning, but it is vulnerable to XSS. A production browser app should strongly consider HttpOnly, Secure, SameSite cookies for refresh tokens.

Database Schema

erDiagram
ApplicationUser ||--o{ RefreshToken : owns
ApplicationUser ||--o{ AspNetUserRoles : has
AspNetRoles ||--o{ AspNetUserRoles : assigned
ApplicationUser {
  string Id
  string Email
  string FirstName
  string LastName
  string PasswordHash
}
RefreshToken {
  int Id
  string UserId
  string Token
  datetime Expires
  datetime Created
  datetime Revoked
  string ReplacedByToken
}
Loading

API Documentation

POST /api/auth/register

Request:

{
  "firstName": "Demo",
  "lastName": "User",
  "email": "demo@example.com",
  "password": "Pass123$"
}

Responses: 201 Created, 400 Bad Request.

POST /api/auth/login

Request:

{
  "email": "admin@example.com",
  "password": "Pass123$"
}

Response:

{
  "accessToken": "jwt",
  "refreshToken": "random-token",
  "expiresIn": 900
}

Responses: 200 OK, 401 Unauthorized.

POST /api/auth/refresh

Request:

{
  "accessToken": "expired-or-current-jwt",
  "refreshToken": "active-refresh-token"
}

Responses: 200 OK, 401 Unauthorized.

POST /api/auth/logout

Request:

{
  "refreshToken": "active-refresh-token"
}

Responses: 200 OK.

Demo Endpoints

  • GET /api/demo/anonymous: no token required.
  • GET /api/demo/authenticated: any authenticated user.
  • GET /api/demo/role: User or Admin role.
  • GET /api/demo/admin: Admin role only.
  • GET /api/demo/profile: returns current user claims.

Blazor Frontend

Pages:

  • Home: calls a protected endpoint and logs out.
  • Register: creates a new user.
  • Login: signs in and stores tokens.
  • Profile: displays the current user, roles, claims, and token expiration.
  • Admin: demonstrates role-based authorization.
  • Unauthorized: shown when the user is not allowed.

ApiAuthenticationStateProvider reads the access token claims. AuthApiClient attaches the Bearer token, attempts refresh after 401 Unauthorized, stores the rotated tokens, and clears tokens on logout.

Running the Project

  1. Install the .NET 10 SDK and SQL Server LocalDB.
  2. Restore packages:
dotnet restore AuthenticationDemo.slnx
  1. Apply the EF migration:
dotnet ef database update --project src\AuthenticationDemo.Api --startup-project src\AuthenticationDemo.Api
  1. Run the API:
dotnet run --project src\AuthenticationDemo.Api
  1. Run the Blazor app in another terminal:
dotnet run --project src\AuthenticationDemo.Client
  1. Open Swagger at the API URL plus /swagger.

Seeded users:

  • admin@example.com / Pass123$
  • user@example.com / Pass123$

Testing with Swagger

  1. Call POST /api/auth/login.
  2. Copy the accessToken.
  3. Click Swagger Authorize.
  4. Paste the token as the Bearer value.
  5. Call GET /api/demo/authenticated or GET /api/demo/admin.
  6. Call POST /api/auth/refresh with the access token and refresh token.
  7. Call POST /api/auth/logout with the latest refresh token.

Testing with Blazor

  1. Open the Blazor URL.
  2. Go to Login and use one of the seeded users.
  3. Open Profile to inspect the current user and token expiration.
  4. Open Admin. The normal user should be rejected; the admin user should succeed.
  5. Use Home to call a protected endpoint and logout.

Security Best Practices

  • Use HTTPS for all token traffic.
  • Keep access tokens short-lived.
  • Store refresh tokens server-side and rotate them on every refresh.
  • Revoke refresh tokens on logout.
  • Generate refresh tokens with cryptographic randomness.
  • Hash passwords with ASP.NET Core Identity.
  • Validate JWT issuer, audience, signing key, and expiration.
  • Use EF Core parameterized queries to avoid SQL injection.
  • Treat local storage as educational only; XSS can expose tokens.
  • Use HttpOnly, Secure, SameSite cookies when building a production browser flow.
  • Configure CORS for known client origins only.
  • Keep production secrets outside appsettings.json.

Common Problems

  • 401 Unauthorized: missing, expired, or invalid access token.
  • 403 Forbidden: authenticated but missing the required role.
  • Invalid Refresh Token: token was revoked, expired, rotated, or belongs to another user.
  • Database migration issues: verify LocalDB is installed and the connection string is correct.
  • CORS errors: ensure the Blazor URL is listed in the API CORS policy.
  • Clock skew: this sample uses zero clock skew, so machine time must be accurate.

Future Improvements

  • Email verification.
  • MFA.
  • OAuth and OpenID Connect.
  • Google, Microsoft, or GitHub login.
  • HttpOnly cookie refresh-token flow.
  • Redis or distributed token storage.
  • Replay attack family revocation.
  • OpenIddict or IdentityServer integration.

About

JWT access and refresh token reference application using ASP.NET Core Identity, Blazor, SQL Server, token rotation, and tests.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages