A simple and secure authentication system built using JSON Web Tokens (JWT).
This project demonstrates how to implement user authentication, authorization, and protected routes using JWTs — including a full refresh token flow with token rotation.
- User registration (sign up)
- User login (sign in)
- Password hashing
- JWT generation and verification
- Token-based authorization
- Refresh token flow with automatic token rotation
- Secure HTTP-only cookie storage
- Logout with token revocation
- C#
- Authentication: JSON Web Tokens (JWT)
- Database: SQL Server (Azure)
- Environment Management: dotnet user-secrets / Azure Key Vault
-
Clone the repository:
git clone https://github.com/alexfitzkane-02/JwtAuthentication.git
-
Navigate into the project folder:
cd JwtAuthentication -
Set up your secrets locally using dotnet user-secrets:
dotnet user-secrets init dotnet user-secrets set "ConnectionStrings:DefaultConnection" "your_connection_string" dotnet user-secrets set "Jwt:Key" "your_jwt_key"
-
Apply database migrations:
dotnet ef database update
-
Run the project:
dotnet run
{
"ConnectionStrings": {
"DefaultConnection": ""
},
"Jwt": {
"Key": "",
"Issuer": "your_issuer_port",
"Audience": "your_audience_port"
}
}Leave these blank — values are provided via dotnet user-secrets in development and Azure Key Vault in production. Never commit real credentials to source control.
- User logs in with valid credentials
- Server creates a short-lived JWT (30 minutes) and a long-lived refresh token (7 days)
- Both tokens are stored in secure HTTP-only cookies
- Client sends cookies automatically with each request
- Middleware verifies the JWT before allowing access to protected routes
- When the JWT expires, the client calls
/api/auth/refreshto get a new pair - On logout, both cookies are deleted and the refresh token is revoked in the database
Login
└─→ access_token cookie (30 min) + refresh_token cookie (7 days)
Access token expires
└─→ POST /api/auth/refresh
└─→ old refresh token revoked
└─→ new access_token + new refresh_token issued (token rotation)
Repeat until refresh token expires (7 days)
└─→ user must log in again
Logout
└─→ both cookies deleted
└─→ refresh token revoked in database
Token rotation means the refresh token is invalidated every time it is used and replaced with a new one. This ensures that a stolen refresh token can only be used once before it becomes invalid.
Creates a new user account with the Reader role.
Request body:
{
"email": "johndoe@example.com",
"password": "password123"
}Response 200 OK
Authenticates the user and sets JWT and refresh token cookies.
Request body:
{
"email": "johndoe@example.com",
"password": "password123"
}Response 200 OK:
{
"email": "johndoe@example.com",
"roles": ["Reader"]
}Cookies set:
access_token— HTTP-only, expires in 30 minutesrefresh_token— HTTP-only, expires in 7 days
Issues a new access token and refresh token using the existing refresh token cookie. The old refresh token is revoked immediately (token rotation).
Response 200 OK: "Token refreshed successfully."
Response 401 Unauthorized: Refresh token missing, expired, or revoked.
Revokes the refresh token and deletes both cookies.
Response 200 OK: "Logged out successfully."
- Understanding JWT-based authentication
- Securing APIs with HTTP-only cookies
- Implementing refresh token rotation
- Middleware-based route protection
- Backend authentication best practices
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.