Learn Angular - Security & Auth
Episode 12 of 24

Learn Angular - Security & Auth

This episode covers security and authentication: JWT authentication patterns, protecting routes with auth guards, secure token storage and HTTP calls, and role-based authorization and permission checks.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Most applications store user data — and that data must be protected. Authentication verifies who the user is, while authorization determines what they're allowed to access.

Episode 12 covers JWT authentication patterns, protecting routes with auth guards, secure token storage and HTTP calls, and role-based authorization and permission checks. Security isn't an add-on feature — it's part of the architecture.

Authentication Patterns with JWT

The JWT Authentication Flow

JWT (JSON Web Token) is a token containing verified claims. The common flow: the user logs in, the server returns an access token (and usually a refresh token), the application stores it, then sends it in the header of every request.

JSAuthentication service with JWT
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
 
@Injectable({ providedIn: 'root' })
export class AuthService {
  private readonly http = inject(HttpClient);
  readonly pengguna = signal<Pengguna | null>(null);
 
  login(email: string, password: string): void {
    this.http.post<LoginResponse>('/api/login', { email, password })
      .subscribe({
        next: (res) => {
          this.simpanToken(res.accessToken);
          this.pengguna.set(res.pengguna);
        },
        error: (err) => console.error('Login gagal', err),
      });
  }
}

login sends the credentials, then stores the access token and the user state. After this, the application knows the user is authenticated and can show the appropriate UI.

Storing Tokens

Token storage determines the level of security:

  • localStorage: easy to use, but vulnerable to XSS — any JavaScript on the page can read the token.
  • httpOnly cookie: can't be read by JavaScript, so it's immune to XSS, but requires CSRF handling.
  • In-memory: the most secure against XSS, but lost when the page refreshes.

The modern best practice: keep the access token in memory and the refresh token in an httpOnly cookie. This avoids XSS while preserving the session.

Protecting Routes with Auth Guards

A Basic Guard

The guard from episode 9 is used to protect routes that require login:

JSAuth guard with redirect
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
 
export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);
 
  if (auth.pengguna() !== null) {
    return true;
  }
  return router.createUrlTree(['/login'], { queryParams: { kembali: router.url } });
};

Attach the guard to a protected route: { path: 'profil', component: ProfilComponent, canActivate: [authGuard] }. When a user who isn't logged in tries to open that route, they're redirected to the login page with a note of the original page.

A Guard for Data Not Yet Loaded

Sometimes the token exists, but the user data hasn't been loaded yet. authGuard can wait for the profile update to finish:

JSA guard that waits for user data
export const authGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);
  const status = auth.muatProfil();
  return status ? true : router.createUrlTree(['/login']);
};

Here muatProfil checks the local token and loads the user data if needed. The guard only returns true once the authentication state is truly certain.

Token Storage and Secure HTTP Calls

Adding the Token to Requests

Every API request must carry the token. The centralized place for this is an HTTP interceptor:

JSInterceptor injecting a Bearer token
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';
 
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);
  const token = auth.getToken();
 
  if (!token) {
    return next(req);
  }
 
  const reqBaru = req.clone({
    setHeaders: { Authorization: `Bearer ${token}` },
  });
  return next(reqBaru);
};

authInterceptor adds the Authorization: Bearer <token> header to every request. The interceptor is registered in app.config.ts via provideHttpClient(withInterceptors([authInterceptor])). All requests are authenticated automatically without changing code in every service.

Handling Expired Tokens

When an access token expires, the server returns status 401. Handle it centrally: refresh the token via an endpoint, retry the failed request, or redirect the user to the login page. This pattern keeps the session alive without forcing users to log in repeatedly.

Role-based Authorization and Permission Checks

Storing Roles and Permissions

Role-based access control (RBAC) checks a user's role before granting access:

JSCheck role and permission
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
 
export const roleGuard: CanActivateFn = () => {
  const auth = inject(AuthService);
  const router = inject(Router);
 
  if (auth.pengguna()?.role === 'admin') {
    return true;
  }
  return router.createUrlTree(['/forbidden']);
};

roleGuard only allows users with the admin role. The forbidden page shows an access-denied message. Combine auth and role guards for areas that need both.

Permissions in the UI and in the Backend

Don't just hide admin buttons in the UI — permissions must also be verified in the backend. The UI hides or disables actions the user isn't allowed to perform, while the backend still validates every request. These two layers protect the application from direct manipulation through the API.

Wrap Up

Key takeaways:

  • JWT is the authentication standard: access token in the header, refresh token in an httpOnly cookie.
  • localStorage is vulnerable to XSS; store tokens as securely as possible.
  • Functional auth guards protect routes and redirect users to login.
  • Interceptors inject tokens into all requests centrally.
  • Handle status 401 centrally for token refresh and retry.
  • RBAC checks roles, and authorization is always re-verified in the backend.

In the next episode, episode 13, we'll cover secure web practices — preventing XSS, CSRF, and injection, applying content security policy and secure headers, sanitizing HTML and handling safe URLs, and client-side security best practices.

Learn Angular - Security & Auth | Learn Angular