Implementing Secure Authentication in Modern Web Apps: OAuth 2.0 & JWT with Angular and Node.js

Leader posted 5 min read

Authentication and authorization are at the heart of secure web applications. In today’s connected world, APIs, mobile apps, and microservices need a standardized and secure way to handle identity. That’s where OAuth 2.0 and JWT come in.

This blog will help you understand what they are, how they work, and how to implement them in your application.


1. What is OAuth 2.0?

OAuth 2.0 is an authorization framework that allows apps to securely access resources on behalf of a user.
Instead of sharing passwords, apps exchange tokens.

Example:

  • You log in to a new app using “Sign in with Google”.
  • The app doesn’t get your Google password.
  • Instead, it gets a token that lets it read your profile/email (with your permission).

Key OAuth 2.0 Roles

  • Resource Owner → The user.
  • Client → The application requesting access.
  • Authorization Server → Issues tokens (Google, Auth0, Okta, etc.).
  • Resource Server → The API or service that accepts tokens.

2. What is JWT (JSON Web Token)?

A JWT is a secure way of representing claims between two parties.
It is widely used in OAuth 2.0 as the format for access tokens.

A JWT is just a string with 3 parts:

header.payload.signature
  • Header → Algorithm & type (e.g., HS256, JWT).
  • Payload → Claims (user ID, roles, expiry).
  • Signature → Ensures integrity (no tampering).

Example JWT payload:

{
  "sub": "1234567890",
  "name": "angular_development",
  "role": "admin",
  "exp": 1716239022
}

3. OAuth 2.0 + JWT Flow (Simplified)

  1. User logs in (via username/password, Google, GitHub, etc.).
  2. Authorization Server validates and issues a JWT.
  3. The Client app stores the JWT (in memory or secure cookie).
  4. For each API request, the JWT is sent in the HTTP Header:
Authorization: Bearer <JWT_TOKEN>
  1. The API verifies the JWT (signature + expiry).
  2. If valid → access granted.

4. Implementing OAuth 2.0 + JWT (Step-by-Step)

Let’s walk through a practical example using Node.js (Express), but the same applies to Angular, React, Go, Python, or Java backends.

a) Install Dependencies

npm install express jsonwebtoken passport passport-jwt

b) Configure JWT Strategy

const jwt = require("jsonwebtoken");
const express = require("express");
const app = express();

const SECRET = "super-secret-key";

// Generate JWT on login
app.post("/login", (req, res) => {
  // Normally, you'd validate username & password
  const user = { id: 1, name: "Sunny", role: "admin" };
  const token = jwt.sign(user, SECRET, { expiresIn: "1h" });
  res.json({ token });
});

// Middleware to protect routes
function authenticateToken(req, res, next) {
  const authHeader = req.headers["authorization"];
  const token = authHeader && authHeader.split(" ")[1];
  if (!token) return res.sendStatus(401);

  jwt.verify(token, SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

// Protected route
app.get("/dashboard", authenticateToken, (req, res) => {
  res.json({ message: `Welcome ${req.user.name}, you are ${req.user.role}` });
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));

5. Best Practices

  • ✅ Use short-lived tokens (e.g., 15 mins) + refresh tokens.
  • ✅ Store tokens securely (HTTP-only cookies > localStorage).
  • ✅ Use HTTPS everywhere.
  • ✅ Use a trusted authorization server (Auth0, Okta, AWS Cognito, Keycloak).
  • ✅ Rotate and revoke tokens when needed.

6. When to Use OAuth 2.0 + JWT

  • Single Sign-On (SSO)
  • Microservices where stateless JWT tokens simplify auth.
  • Mobile Apps where OAuth provides secure delegated access.
  • Public APIs where third-party apps need controlled access.

Summary

OAuth 2.0 + JWT gives you a secure, scalable, and modern authentication system.
Instead of handling passwords directly, your app deals with tokens — making it safer and easier to integrate with external identity providers like Google, GitHub, or Microsoft.

Full stack Example-

1. Backend Setup (Node.js + Express + JWT)

This backend will:

  • Authenticate user (dummy login for now).
  • Issue a JWT token.
  • Protect routes using authenticateToken middleware.

Step 1: Initialize Project

mkdir jwt-auth-backend && cd jwt-auth-backend
npm init -y
npm install express jsonwebtoken cors body-parser

Step 2: Create server.js

const express = require("express");
const jwt = require("jsonwebtoken");
const bodyParser = require("body-parser");
const cors = require("cors");

const app = express();
app.use(bodyParser.json());
app.use(cors());

const SECRET = "super-secret-key";

// Login route
app.post("/login", (req, res) => {
  const { username, password } = req.body;

  // Dummy authentication
  if (username === "admin" && password === "password") {
    const user = { id: 1, username: "admin", role: "admin" };
    const token = jwt.sign(user, SECRET, { expiresIn: "1h" });
    return res.json({ token });
  }
  res.status(401).json({ message: "Invalid credentials" });
});

// Middleware to validate JWT
function authenticateToken(req, res, next) {
  const authHeader = req.headers["authorization"];
  const token = authHeader && authHeader.split(" ")[1];
  if (!token) return res.sendStatus(401);

  jwt.verify(token, SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

// Protected route
app.get("/dashboard", authenticateToken, (req, res) => {
  res.json({ message: `Welcome ${req.user.username}, role: ${req.user.role}` });
});

app.listen(4000, () => console.log("✅ Backend running on http://localhost:4000"));

Run it:

node server.js

2. Frontend Setup (Angular)

This frontend will:

  • Provide login form.
  • Store JWT token.
  • Call backend protected APIs.

Step 1: Create Angular App

ng new jwt-auth-frontend --routing --style=scss
cd jwt-auth-frontend
npm install @auth0/angular-jwt

Step 2: Create Auth Service

src/app/services/auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private apiUrl = 'http://localhost:4000';
  private jwtHelper = new JwtHelperService();

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

  login(username: string, password: string) {
    return this.http.post<{ token: string }>(`${this.apiUrl}/login`, { username, password });
  }

  saveToken(token: string) {
    localStorage.setItem('token', token);
  }

  getToken() {
    return localStorage.getItem('token');
  }

  isLoggedIn(): boolean {
    const token = this.getToken();
    return !!token && !this.jwtHelper.isTokenExpired(token);
  }

  logout() {
    localStorage.removeItem('token');
    this.router.navigate(['/login']);
  }
}

Step 3: Add HTTP Interceptor

This will automatically attach JWT to every API request.

src/app/interceptors/auth.interceptor.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    if (token) {
      req = req.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    return next.handle(req);
  }
}

Register it in app.module.ts:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth.interceptor';

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

Step 4: Login Component

src/app/login/login.component.ts

import { Component } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  template: `
    <h2>Login</h2>
    <form (ngSubmit)="onLogin()">
      <input [(ngModel)]="username" name="username" placeholder="Username" required />
      <input [(ngModel)]="password" name="password" placeholder="Password" type="password" required />
      <button type="submit">Login</button>
    </form>
  `
})
export class LoginComponent {
  username = '';
  password = '';

  constructor(private authService: AuthService, private router: Router) {}

  onLogin() {
    this.authService.login(this.username, this.password).subscribe({
      next: (res) => {
        this.authService.saveToken(res.token);
        this.router.navigate(['/dashboard']);
      },
      error: () => alert('Invalid credentials')
    });
  }
}

Step 5: Dashboard Component

src/app/dashboard/dashboard.component.ts

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-dashboard',
  template: `
    <h2>Dashboard</h2>
    <p>{{ message }}</p>
  `
})
export class DashboardComponent implements OnInit {
  message = '';

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get<{ message: string }>('http://localhost:4000/dashboard')
      .subscribe(res => this.message = res.message);
  }
}

Step 6: Routing

src/app/app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AuthService } from './services/auth.service';

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'dashboard', component: DashboardComponent },
  { path: '**', redirectTo: 'login' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

End-to-End Flow

  1. User logs in via Angular → /login API.
  2. Node backend validates → returns JWT.
  3. Angular saves token → attaches to all requests.
  4. Accessing /dashboard calls backend with Authorization: Bearer token.
  5. Backend validates token → returns protected message.

If you read this far, tweet to the author to show them you care. Tweet a Thanks

A well-structured breakdown of OAuth 2.0 and JWT — it simplifies concepts that often confuse beginners. The full-stack example nicely connects theory with practice, making secure authentication feel far more approachable for real-world apps.

More Posts

CSRF Token in Web Development: Secure Your React, Next.js, Django & Laravel Apps

Raj Aryan - Jun 9

How to set up TypeScript with Node.js and Express

Sunny - Jul 13

How to set up TypeScript with Node.js and Express (2025)

Sunny - Jun 6

Node.js & Express.js: The Ultimate, Comprehensive, and Extremely Detailed Guide for Developers

Hanzla Baig Dev - Feb 27

Strengthening Web Security with HTTP Headers in Express.js

mariohhd - Aug 19
chevron_left