Step 1: Install Required Packages
In your .NET backend, you will need some authentication packages to handle JWT tokens. You can install them using NuGet
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Step 2: Configure JWT Authentication in Startup.cs
In your Startup.cs
(or Program.cs
in .NET 6 and above), you will configure JWT Bearer authentication to protect the API routes.
csharpublic void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "your-issuer",
ValidAudience = "your-audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"))
};
});
services.AddAuthorization();
services.AddControllers();
}
Step 3: Generate Access and Refresh Tokens
You will need to generate JWT tokens in your backend when a user logs in. This involves issuing both an access token and a refresh token.
public class AuthService
{
private readonly string _secretKey = "your-secret-key";
private readonly string _issuer = "your-issuer";
private readonly string _audience = "your-audience";
public (string accessToken, string refreshToken) GenerateTokens(User user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("role", user.Role)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// Access token
var accessToken = new JwtSecurityToken(
_issuer,
_audience,
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds
);
// Refresh token (usually a long-lived token, can be stored in DB)
var refreshToken = Guid.NewGuid().ToString(); // Simple example of refresh token
return (new JwtSecurityTokenHandler().WriteToken(accessToken), refreshToken);
}
}
Step 4: Create a Token Endpoint
You will need an endpoint to handle login and issue both tokens.
csharp
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest model)
{
// Validate the user credentials (this is just an example)
var user = _userService.ValidateUser(model.Username, model.Password);
if (user == null)
{
return Unauthorized();
}
// Generate tokens
var (accessToken, refreshToken) = _authService.GenerateTokens(user);
return Ok(new { AccessToken = accessToken, RefreshToken = refreshToken });
}
Step 5: Endpoint to Refresh Tokens
Create an endpoint to refresh the access token using the refresh token. This usually involves verifying the refresh token and generating a new access token
[HttpPost("refresh")]
public IActionResult Refresh([FromBody] RefreshTokenRequest request)
{
// Verify the refresh token and generate a new access token
var newAccessToken = _authService.GenerateNewAccessToken(request.RefreshToken);
if (newAccessToken == null)
{
return Unauthorized();
}
return Ok(new { AccessToken = newAccessToken });
}
2. Frontend (Angular) Setup
Step 1: Install HTTP Client Module
Ensure that you have the HttpClientModule
imported into your Angular application.
typeimport { HttpClientModule } from '@angular/common/http';
Step 2: Create an Authenticati Service
In your Angular app, you need authentication service that will manage storing and refreshing tokens.
typescript
@Injectable({
providedIn: 'root })
export class AuthService {
private apiUrl = 'https://your-api-url.com';
constructor(private http: HttpClient, private router: Router) {}
login(username: string, password: string): Observable<any> {
return this.http.post(`${this.apiUrl}/login`, { username, password });
}
refreshToken(refreshToken: string): Observable<any> {
return this.http.post(`${this.apiUrl}/refresh`, { refreshToken });
}
storeTokens(accessToken: string, refreshToken: string): void {
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
}
getAccessToken(): string | null {
return localStorage.getItem('accessToken');
}
getRefreshToken(): string | null {
return localStorage.getItem('refreshToken');
}
logout(): void {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
this.router.navigate(['/login']);
}
}
Step 3: Interceptor for Adding Access Token to Requests
To ensure the access token is included in all requests to the backend, create an HTTP interceptor.
typescript
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const accessToken = this.authService.getAccessToken();
if (accessToken) {
const cloned = req.clone({
setHeaders: {
Authorization: `Bearer ${accessToken}`
}
});
return next.handle(cloned);
}
return next.handle(req);
}
}
Add the interceptor to your app.module.ts
:
typescript@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [
AuthService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule {}
Step 4: Handle Token Expiry and Refresh
In case the access token expires, you need to handle token refresh automatically. Modify the AuthInterceptor
to refresh the token if the request fails due to token expiration.
typescript
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService, private http: HttpClient) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const accessToken = this.authService.getAccessToken();
if (accessToken) {
const cloned = req.clone({
setHeaders: {
Authorization: `Bearer ${accessToken}`
}
});
return next.handle(cloned).pipe(
catchError((error) => {
if (error.status === 401) {
// Token expired, refresh the token
return this.refreshToken(req, next);
}
return throwError(error);
})
);
}
return next.handle(req);
}
refreshToken(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const refreshToken = this.authService.getRefreshToken();
return this.authService.refreshToken(refreshToken).pipe(
switchMap((response) => {
// Store new access token
this.authService.storeTokens(response.accessToken, response.refreshToken);
const clonedRequest = req.clone({
setHeaders: {
Authorization: `Bearer ${response.accessToken}`
}
});
return next.handle(clonedRequest);
}),
catchError((err) => {
this.authService.logout();
return throwError(err);
})
);
}
}
3. Token Expiry Flow
- User logs in using the
login()
method inAuthService
. The backend returns both the access and refresh tokens. - The access token is stored in local storage, and the refresh token is also stored.
- For any subsequent requests, the access token is attached in the Authorization header via the
AuthInterceptor
. - If the access token expires, the interceptor will automatically attempt to refresh the access token using the stored refresh token. If successful, the new access token is used in the request, and the user remains logged in.
- If refreshing the token fails (e.g., refresh token is invalid), the user is logged out.
This setup ensures that your Angular frontend and .NET backend properly handle access and refresh tokens for secure authentication and authorization.
No comments:
Post a Comment