Authentication is a core part of most web applications. In this article, we’ll walk through how an authentication workflow works in Angular, including login handling, redirect logic, and route protection using guards.
Overview
A typical Angular auth flow looks like this:
We will break this down into smaller pieces.
1. AuthService (Core of Authentication)
AuthService is responsible for managing login state.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private TOKEN_KEY = 'token';
login(token: string) {
localStorage.setItem(this.TOKEN_KEY, token);
}
logout() {
localStorage.removeItem(this.TOKEN_KEY);
}
isLoggedIn(): boolean {
return !!localStorage.getItem(this.TOKEN_KEY);
}
getToken() {
return localStorage.getItem(this.TOKEN_KEY);
}
}
Key idea:
- Store token in
localStorage - Check login status via
isLoggedIn()
2. Login Flow
After successful login, redirect user to dashboard:
onLogin() {
this.authService.login('fake-token'); // token from API
this.router.navigate(['/dashboard']);
}
What happens:
- Save token. What is token? I will discuss in another post.
- Redirect to protected route
3. Route Protection with Auth Guard
Angular provides Route Guards to control access.
Create Guard
ng generate guard auth
Implement Guard
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isLoggedIn()) {
return true;
}
return router.createUrlTree(['/login']);
};
Behavior:
- Logged in → allow access
- Not logged in → redirect to
/login
4. Guest Guard (Prevent Access When Logged In)
We also need to block logged-in users from accessing login pages.
export const guestGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isLoggedIn()) {
return router.createUrlTree(['/dashboard']);
}
return true;
};
5. Routing Configuration
export const routes = [
{
path: '',
component: HomeComponent,
canActivate: [guestGuard]
},
{
path: 'login',
component: LoginComponent,
canActivate: [guestGuard]
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authGuard]
}
];
6. Full Authentication Flow
Case 1: User not logged in
- Access
/dashboard - Redirect →
/login
Case 2: User logs in
- Save token.
- Redirect →
/dashboard
Case 3: Page reload
- Token still in
localStorage - Guard allows access
Case 4: Logged-in user accesses /login
- Redirect →
/dashboard
7. Bonus: Redirect Back After Login
Improve UX by returning user to original page.
In Guard:
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
In Login:
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard';
this.router.navigate([returnUrl]);
8. HTTP Interceptor (Attach Token & Handle Errors)
After setting up authentication and route guards, we still need a way to automatically attach the token to every API request. This is where Angular HTTP Interceptor comes in.
What is an Interceptor?
An interceptor sits between your app and every HTTP request:
It allows us to:
- Attach authentication headers
- Handle errors globally
- Avoid repeating logic in every API call
Implement Auth Interceptor
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
import { catchError, throwError } from 'rxjs';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const router = inject(Router);
const token = authService.getToken();
// Skip public APIs
const isPublicApi =
req.url.includes('/login') ||
req.url.includes('/register');
let clonedReq = req;
// Attach token if exists
if (token && !isPublicApi) {
clonedReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next(clonedReq).pipe(
catchError((error: HttpErrorResponse) => {
// Handle unauthorized error
if (error.status === 401) {
authService.logout();
if (!router.url.includes('/login')) {
router.navigate(['/login']);
}
}
return throwError(() => error);
})
);
};
Register Interceptor
In a standalone Angular app, interceptors are registered in main.ts:
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './core/interceptors/auth.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([authInterceptor])
)
]
});
How It Works
Every HTTP request goes through the interceptor
Token is automatically added to headers
If the server returns
401 Unauthorized:- User is logged out
- Redirected to login page
Why It Matters
Without an interceptor:
- You must manually attach tokens to every request
With an interceptor:
- Centralized logic
- Cleaner code
- Easier to maintain and scale
Updated Auth Flow
Conclusion
Angular provides a clean and powerful way to handle authentication using:
AuthServicefor state managementRouterfor navigationGuardsfor route protectionHTTP Interceptorfor handling API authentication
By combining these, you can build a scalable and production-ready authentication system.