Introduction :#
Using two tokens—an access token and a refresh token—improves security and user experience. Access tokens are short-lived, limiting the impact of compromise, while refresh tokens are long-lived and used to obtain new access tokens, reducing exposure of long-term credentials. This approach allows refresh tokens to be stored securely and ensures session continuity, even if the access token expires.
Imagine you're visiting a theme park:
Access Token: This is like a single-ride ticket. It's valid for a short period (for one ride), and once you use it, you need a new one. If it gets lost or stolen, it only affects that one ride—it's not a big risk.
Refresh Token: This is like an all-day pass. Even though your single-ride ticket expires after each use, you can always go back to the ticket counter and get a new one using your all-day pass. The all-day pass is securely stored, and you only need to use it when you want a new ride ticket.
So, the access token is your short-term ticket, while the refresh token allows you to get a new access token without having to reauthenticate, ensuring a smooth experience throughout the day.
Initial Authentication :#
- The client sends a username and password to the server.
- The server validates these credentials and requests the token service to generate an Access Token and a Refresh Token.
- The server returns both tokens to the client.
Making API Requests :#
- The client uses the Access Token to make a request to the server.
- If the Access Token is valid, the server fulfills the request.
Access Token Expiration :#
- When the Access Token expires, the client sends the Refresh Token to the server to request a new Access Token.
- The server verifies the Refresh Token with the token service, which generates a new Access Token.
- The server returns the new Access Token to the client.
Continuing Requests :#
- The client can continue using the newly generated Access Token to make further requests.
Implements :#
Inside the AuthController we have the login request we are calling the all service and this is giving us one token. But now this time we want it to give us two tokens instead of one. So, let’s just go to the login request. Here we are authenticating our user with the help of the credentials that they are provided so, if all the credentials are good then we will be getting the authentication from here we will be getting the user from here . Here you can see we are generating tokens but currently we are only generating one token that is our access token.
To implement a login mechanism that generates both an access token and a refresh token, you've already made significant progress by modifying the JWTService
, AuthService
, AuthController
, and related DTOs. Here’s a quick summary and some pointers to ensure the system works as expected:
JWTService#
You added two methods:
createAccessToken()
: This generates a short-lived JWT (e.g., 10 minutes) containing user details likeemail
androles
.createRefreshToken()
: This generates a long-lived JWT (e.g., 6 months) without storing sensitive information, mainly used to obtain a new access token when the old one expires.
AuthService#
You modified the logIn()
method to:
- Authenticate the user.
- Generate both an access token and a refresh token using
JWTService
. - Return both tokens in the
LoginResponseDto
.
You also added a refreshToken()
method that:
- Verifies the refresh token.
- Generates a new access token if the refresh token is valid.
- Returns the new access token along with the existing refresh token.
To return these token you have to create a separate Dto that is LoginResponseDto.
AuthController#
You updated the logIn()
endpoint to:
- Call
authService.logIn()
to obtain both tokens. - Store the refresh token in an HTTP-only cookie, enhancing security by preventing client-side access via JavaScript.
You also added a refresh
endpoint to:
- Extract the refresh token from cookies.
- Use it to obtain a new access token through
authService.refreshToken()
.
Important Considerations :#
- Cookie Security: By setting the refresh token as an HTTP-only cookie, you protect it from XSS attacks. Consider also setting the
Secure
flag if your application uses HTTPS. - Refresh Token Rotation: If you want to enhance security further, you might implement refresh token rotation. Every time a refresh token is used to obtain a new access token, you would generate a new refresh token and invalidate the old one.
- Token Expiry Handling: Ensure that your frontend application is designed to handle token expiry by automatically using the refresh token to obtain a new access token without requiring user intervention.
- Logging Out: When logging out, ensure both access and refresh tokens are invalidated. This might involve deleting the refresh token cookie and potentially storing blacklisted tokens in a database if necessary.
Output :#
After run the application let’s see the outputs.
SignUp:
- When a new user signs up using the
/auth/signUp
endpoint, their credentials are stored securely in the database. If the email is unique, a new user record is created.
Login (with Two Tokens):
- The
/auth/logIn
endpoint is called with the user's credentials (email and password). - If the credentials are valid, two tokens are generated:
- Access Token: A short-lived token containing the user's ID, email, roles, and other claims. This token is used for authenticating the user's requests.
- Refresh Token: A long-lived token stored in an HTTP-only cookie. It is used to generate a new access token when the old one expires.
Inspecting the Tokens :#
- By pasting the tokens into jwt.io, you can inspect the contents of each token:
- Access Token: You'll see claims like
email
,roles
,subject
(which is typically the user's ID), and expiration time (exp
). This token is primarily used for authorization and authentication. - Refresh Token: This token is simpler, often containing only the user's ID (
subject
) and an expiration time. It doesn't carry any additional claims because it's only used for refreshing the access token.
- Access Token: You'll see claims like
Let’s explain how it works. Let’s go to the Cookies
Understanding the Cookies :#
- When a user logs in, the refresh token is stored in a secure, HTTP-only cookie. This means the token:
- Cannot be accessed or manipulated by JavaScript, protecting it from XSS attacks.
- Is only sent over HTTP requests, ensuring its security.
- Refresh:
- When the access token expires, the client calls
/auth/refresh
. - The server reads the refresh token from the cookie, validates it, and issues a new access token.
If the refresh token is not valid, the application should return an error message to inform the client. In this case, we are removing the refresh token from cookies, and upon sending the request, an error is displayed. This error is handled by our GlobalExceptionHandler.
What if Refresh Token is Compromised ?#
If a Refresh Token is stolen, someone else could use it to pretend to be you and keep your session going, which is dangerous. Here’s how to reduce this risk:
Try to Prevent It from Being Stolen
a. Store It Safely:
- Keep It Site-Specific: Store the Refresh Token in a way that it’s only sent to the same site where it was created. This helps protect it from attacks that trick your browser into sending it somewhere else.
- Hide It from Scripts: Store the Refresh Token in a place where it’s hidden from any programs running on your device, so they can’t steal it.
- Use Secure Connections: Only send the Refresh Token over secure connections (like HTTPS) so that no one can see it while it’s being sent over the internet.
- Always use secure connections (HTTPS) to send the Refresh Token, ensuring that no one can steal it while it’s being sent.
Use a Database for Extra Protection
a. Manage Sessions:
- Cancel Old Tokens: Keep track of Refresh Tokens in a database. If you log in again or ask for a new token, the old one is canceled, so even if someone stole the old token, they can’t use it anymore.
- Verify Validity: Each time a Refresh Token is used, check it against the database to make sure it’s still valid. If it’s been canceled or replaced, it won’t work.