Back to KB
Difficulty
Intermediate
Read Time
7 min

Bring Users Back to Where They Left Off After Session Expiry in Angular

By Codcompass Team··7 min read

You know the drill: someone is deep in a dashboard, tweaking filters, comparing sprints—and the session dies. They log in again and land on a generic home screen. All context gone.

This post walks through a small route retention pattern for Angular SPAs: persist the last meaningful URL when auth fails, then send the user there after a successful login. Explicit logout is handled separately so you do not “restore” a page the user chose to leave.

What we are solving

Scenario

Desired behavior

API returns 401 (expired token)

Remember current page → login → return

User hits a protected route while logged out

Remember intended URL → login → return

User clicks logout

Do not restore old page; use normal post-login routing

Storage is sessionStorage, not cookies: tab-scoped, cleared when the tab closes, and separate from auth tokens.

Architecture at a glance

sequenceDiagram
  participant User
  participant App
  participant RouteRetention
  participant API
  participant Login

  User->>App: Working on /app/reports/sprint-42
  App->>API: Request with expired token
  API-->>App: 401 Unauthorized
  App->>RouteRetention: saveReturnUrl()
  App->>App: clear auth session
  App->>User: Redirect to landing/login
  User->>Login: Signs in successfully
  Login->>RouteRetention: hasReturnUrl()?
  RouteRetention-->>Login: yes → /app/reports/sprint-42
  Login->>App: navigateByUrl(saved route)

Enter fullscreen mode Exit fullscreen mode

Three touchpoints cooperate:

  1. RouteRetentionService — save, read, clear, and navigate to the return URL.
  2. HttpInterceptor — on 401, save route before tearing down the session.
  3. AuthGuard — on blocked navigation, save the URL the user tried to open.
  4. Login component — after success, prefer the saved URL over default role-based routing.

The route retention service

A single injectable owns the returnUrl key and exclusion rules.

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class RouteRetentionService {
  private readonly RETURN_URL_KEY = 'returnUrl';

  constructor(private router: Router) {}

  saveReturnUrl(url?: string): void {
    try {
      const urlToSave = url ?? this.getCurrentUrl();

      if (this.shouldExcludeUrl(urlToSave)) {
        return;
      }

      sessionStorage.setItem(this.RETURN_URL_KEY, urlToSave);
    } catch (error) {
      console.error('Failed to save return URL:', error);
    }
  }

  getReturnUrl(): string | null {
    try {
      return sessionStorage.getItem(this.RETURN_URL_KEY);
    } catch (error) {
      return null;
    }
  }

  clearReturnUrl(): void {
    sessionStorage.removeItem(this.RETURN_URL_KEY);
  }

  hasReturnUrl(): boolean {
    const returnUrl = this.getReturnUrl();
    return !!(returnUrl && !this.shouldExcludeUrl(returnUrl));
  }

  async navigateToReturnUrl(defaultRoute = '/'): Promise<boolean> {
    co

🎉 Mid-Year Sale — Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register — Start Free Trial

7-day free trial · Cancel anytime · 30-day money-back