Building secure web applications requires proper authorization between frontend and backend systems. In this post, we'll explore how to implement robust authorization between an Angular 18 frontend and a .NET Core API backend.
Setting Up the .NET Core API
First, let's set up our .NET Core API with JWT authentication:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add JWT Authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
// Add Authorization
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserAccess", policy => policy.RequireRole("User", "Admin"));
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other middleware...
app.UseAuthentication();
app.UseAuthorization();
// Other middleware...
}Creating a JWT Token Service
Next, let's create a service to generate JWT tokens:
public class TokenService : ITokenService
{
private readonly IConfiguration _configuration;
public TokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateJwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
};
// Add roles as claims
foreach (var role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}Implementing Protected API Endpoints
Now, let's create some protected API endpoints:
[ApiController]
[Route("api/[controller]")]
public class ResourcesController : ControllerBase
{
[HttpGet("public")]
public IActionResult GetPublicData()
{
return Ok(new { message = "This is public data" });
}
[HttpGet("user")]
[Authorize(Policy = "UserAccess")]
public IActionResult GetUserData()
{
return Ok(new { message = "This is protected user data" });
}
[HttpGet("admin")]
[Authorize(Policy = "AdminOnly")]
public IActionResult GetAdminData()
{
return Ok(new { message = "This is protected admin data" });
}
}Setting Up Angular 18 Authentication
On the Angular side, we need to set up authentication and handle JWT tokens:
// auth.service.ts
@Injectable({
providedIn: 'root'
})
private currentUserSubject = new BehaviorSubject<User | null>(null);
public currentUser$ = this.currentUserSubject.asObservable();
constructor(private http: HttpClient) {
// Check if we have a token in local storage
const token = localStorage.getItem('token');
if (token) {
const user = this.getUserFromToken(token);
this.currentUserSubject.next(user);
}
}
login(username: string, password: string): Observable<any> {
return this.http.post<any>(`${environment.apiUrl}/auth/login`, { username, password })
.pipe(
tap(response => {
// Save token to local storage
localStorage.setItem('token', response.token);
// Update current user
const user = this.getUserFromToken(response.token);
this.currentUserSubject.next(user);
})
);
}
logout(): void {
localStorage.removeItem('token');
this.currentUserSubject.next(null);
}
getToken(): string | null {
return localStorage.getItem('token');
}
isLoggedIn(): boolean {
return !!this.getToken();
}
hasRole(role: string): boolean {
const user = this.currentUserSubject.value;
return user?.roles.includes(role) ?? false;
}
private getUserFromToken(token: string): User | null {
try {
// Decode the JWT token
const decodedToken = jwt_decode(token);
return {
id: decodedToken.nameid,
username: decodedToken.unique_name,
email: decodedToken.email,
roles: Array.isArray(decodedToken.role) ? decodedToken.role : [decodedToken.role]
};
} catch (error) {
console.error('Error decoding token', error);
return null;
}
}
}Adding HTTP Interceptor for JWT
We'll add an HTTP interceptor to automatically add the JWT token to outgoing requests:
// auth.interceptor.ts
@Injectable()
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.authService.getToken();
if (token) {
const cloned = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(cloned);
}
return next.handle(req);
}
}Implementing Route Guards
Finally, let's implement route guards to protect Angular routes:
// auth.guard.ts
@Injectable({
providedIn: 'root'
})
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean | UrlTree {
if (this.authService.isLoggedIn()) {
// Check if route has data.roles and user has required role
const requiredRoles = route.data['roles'] as string[];
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
// Check if user has any of the required roles
const hasRequiredRole = requiredRoles.some(role =>
this.authService.hasRole(role)
);
if (hasRequiredRole) {
return true;
}
// User doesn't have required role, redirect to unauthorized page
return this.router.parseUrl('/unauthorized');
}
// Not logged in, redirect to login page with return url
return this.router.parseUrl(
`/login?returnUrl=${encodeURIComponent(state.url)}`
);
}
}Conclusion
By implementing proper authorization between your Angular 18 frontend and .NET Core API backend, you can ensure that your application's resources are protected and only accessible to authorized users. This approach uses industry-standard JWT tokens and role-based access control to provide a secure and scalable authorization system.
In future posts, we'll explore more advanced security features like refresh tokens, multi-factor authentication, and API rate limiting.
Happy coding!
