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)
- User logs in (via username/password, Google, GitHub, etc.).
- Authorization Server validates and issues a JWT.
- The Client app stores the JWT (in memory or secure cookie).
- For each API request, the JWT is sent in the HTTP Header:
Authorization: Bearer <JWT_TOKEN>
- The API verifies the JWT (signature + expiry).
- 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
- User logs in via Angular →
/login
API.
- Node backend validates → returns JWT.
- Angular saves token → attaches to all requests.
- Accessing
/dashboard
calls backend with Authorization: Bearer token.
- Backend validates token → returns protected message.