How to implement authentication in Angular

Authentication provides secure user access control, protecting routes and resources from unauthorized users while managing login sessions and tokens. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented authentication systems in enterprise Angular applications throughout my 12 years of frontend development since 2014. The most effective approach is creating an authentication service with route guards to protect routes and HTTP interceptors for token management. This method centralizes authentication logic, provides reusable guards, and automatically handles token injection for API requests.

Create authentication service with login, logout, and token management functionality.

// src/app/services/auth.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { BehaviorSubject, Observable, tap } from 'rxjs'
import { Router } from '@angular/router'

interface User {
  id: string
  email: string
  name: string
}

interface AuthResponse {
  user: User
  token: string
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private currentUserSubject = new BehaviorSubject<User | null>(null)
  public currentUser$ = this.currentUserSubject.asObservable()

  private readonly TOKEN_KEY = 'auth_token'
  private readonly USER_KEY = 'current_user'

  constructor(
    private http: HttpClient,
    private router: Router
  ) {
    this.loadStoredUser()
  }

  private loadStoredUser(): void {
    const storedUser = localStorage.getItem(this.USER_KEY)
    if (storedUser) {
      this.currentUserSubject.next(JSON.parse(storedUser))
    }
  }

  login(email: string, password: string): Observable<AuthResponse> {
    return this.http.post<AuthResponse>('/api/auth/login', { email, password })
      .pipe(
        tap(response => {
          this.setSession(response)
        })
      )
  }

  logout(): void {
    localStorage.removeItem(this.TOKEN_KEY)
    localStorage.removeItem(this.USER_KEY)
    this.currentUserSubject.next(null)
    this.router.navigate(['/login'])
  }

  private setSession(authResponse: AuthResponse): void {
    localStorage.setItem(this.TOKEN_KEY, authResponse.token)
    localStorage.setItem(this.USER_KEY, JSON.stringify(authResponse.user))
    this.currentUserSubject.next(authResponse.user)
  }

  getToken(): string | null {
    return localStorage.getItem(this.TOKEN_KEY)
  }

  isAuthenticated(): boolean {
    const token = this.getToken()
    return token !== null
  }

  getCurrentUser(): User | null {
    return this.currentUserSubject.value
  }
}

Create authentication guard to protect routes:

// src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core'
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'
import { AuthService } from '../services/auth.service'

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    if (this.authService.isAuthenticated()) {
      return true
    }

    this.router.navigate(['/login'], {
      queryParams: { returnUrl: state.url }
    })
    return false
  }
}

Create login component:

// src/app/components/login/login.component.ts
import { Component } from '@angular/core'
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'
import { Router, ActivatedRoute } from '@angular/router'
import { AuthService } from '../../services/auth.service'
import { CommonModule } from '@angular/common'

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <div class='login-container'>
      <form [formGroup]='loginForm' (ngSubmit)='onSubmit()'>
        <h2>Login</h2>

        <div class='form-group'>
          <label for='email'>Email</label>
          <input
            id='email'
            type='email'
            formControlName='email'
            [class.error]='email?.invalid && email?.touched'
          />
          <div *ngIf='email?.invalid && email?.touched' class='error-message'>
            <span *ngIf='email?.errors?.["required"]'>Email is required</span>
            <span *ngIf='email?.errors?.["email"]'>Invalid email format</span>
          </div>
        </div>

        <div class='form-group'>
          <label for='password'>Password</label>
          <input
            id='password'
            type='password'
            formControlName='password'
            [class.error]='password?.invalid && password?.touched'
          />
          <div *ngIf='password?.invalid && password?.touched' class='error-message'>
            <span *ngIf='password?.errors?.["required"]'>Password is required</span>
            <span *ngIf='password?.errors?.["minlength"]'>
              Password must be at least 6 characters
            </span>
          </div>
        </div>

        <div *ngIf='errorMessage' class='alert alert-error'>
          {{ errorMessage }}
        </div>

        <button
          type='submit'
          [disabled]='loginForm.invalid || isLoading'
        >
          {{ isLoading ? 'Logging in...' : 'Login' }}
        </button>
      </form>
    </div>
  `,
  styles: [`
    .login-container {
      max-width: 400px;
      margin: 2rem auto;
      padding: 2rem;
      border: 1px solid #ddd;
      border-radius: 8px;
    }
    .form-group {
      margin-bottom: 1rem;
    }
    label {
      display: block;
      margin-bottom: 0.5rem;
    }
    input {
      width: 100%;
      padding: 0.5rem;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    input.error {
      border-color: #dc3545;
    }
    .error-message {
      color: #dc3545;
      font-size: 0.875rem;
      margin-top: 0.25rem;
    }
    button {
      width: 100%;
      padding: 0.75rem;
      background: #007bff;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
  `]
})
export class LoginComponent {
  loginForm: FormGroup
  isLoading = false
  errorMessage = ''

  constructor(
    private fb: FormBuilder,
    private authService: AuthService,
    private router: Router,
    private route: ActivatedRoute
  ) {
    this.loginForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
    })
  }

  get email() {
    return this.loginForm.get('email')
  }

  get password() {
    return this.loginForm.get('password')
  }

  onSubmit(): void {
    if (this.loginForm.invalid) {
      return
    }

    this.isLoading = true
    this.errorMessage = ''

    const { email, password } = this.loginForm.value

    this.authService.login(email, password).subscribe({
      next: () => {
        const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard'
        this.router.navigate([returnUrl])
      },
      error: (error) => {
        this.errorMessage = error.error?.message || 'Login failed. Please try again.'
        this.isLoading = false
      },
      complete: () => {
        this.isLoading = false
      }
    })
  }
}

Configure protected routes:

// src/app/app.routes.ts
import { Routes } from '@angular/router'
import { LoginComponent } from './components/login/login.component'
import { DashboardComponent } from './components/dashboard/dashboard.component'
import { AuthGuard } from './guards/auth.guard'

export const routes: Routes = [
  { path: 'login', component: LoginComponent },
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard]
  },
  {
    path: 'profile',
    loadComponent: () => import('./components/profile/profile.component')
      .then(m => m.ProfileComponent),
    canActivate: [AuthGuard]
  },
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: '**', redirectTo: '/dashboard' }
]

Here the AuthService manages authentication state using BehaviorSubject for reactive user updates across components. The login method sends credentials to API and stores token and user data in localStorage. The isAuthenticated method checks token existence for route guard decisions. The AuthGuard implements CanActivate interface to protect routes from unauthorized access. The guard redirects unauthenticated users to login with returnUrl query parameter for post-login navigation. The LoginComponent uses reactive forms with validators for email format and password length. The form submission disables button during loading and displays error messages from API responses.

Best Practice Note:

This is the authentication pattern we use in CoreUI for Angular admin templates with protected routes and centralized auth management. Implement HTTP interceptor to automatically attach tokens to API requests and handle 401 responses for token expiration, add refresh token mechanism for seamless session renewal without requiring re-login, and store sensitive tokens in httpOnly cookies instead of localStorage when working with same-origin APIs for enhanced XSS protection.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to Disable Right Click on a Website Using JavaScript
How to Disable Right Click on a Website Using JavaScript

How to Use JavaScript setTimeout()
How to Use JavaScript setTimeout()

JavaScript Template Literals: Complete Developer Guide
JavaScript Template Literals: Complete Developer Guide

What Does javascript:void(0) Mean?
What Does javascript:void(0) Mean?

Answers by CoreUI Core Team