Implementing Authentication with JWT and OAuth 2.0
10/12/2023
15 min read
Tridip Dutta
Security

Implementing Authentication with JWT and OAuth 2.0

Complete guide to implementing secure authentication systems using JSON Web Tokens and OAuth 2.0 with practical examples and security considerations.

Authentication
JWT
OAuth
Security

Implementing Authentication with JWT and OAuth 2.0

In the modern web, secure and scalable authentication is crucial. Users expect seamless sign-ins, robust security, and smooth integration with third-party services. Two powerful technologies that enable this are JSON Web Tokens (JWT) and OAuth 2.0.

This post will walk you through understanding, implementing, and securing authentication with JWT and OAuth 2.0, providing practical code examples and best practices to safeguard your applications.


Understanding Authentication: JWT and OAuth 2.0

What is JWT?

JSON Web Tokens (JWT) are compact, URL-safe tokens used to securely transmit information between parties as a JSON object. They consist of three parts:

  1. Header — specifies the algorithm and token type.
  2. Payload — contains claims (user data and metadata).
  3. Signature — ensures the token's integrity.

JWTs are commonly used for stateless authentication in APIs and web apps.

Key Advantages:

  • Self-contained tokens with user data
  • Stateless authentication (no server-side session needed)
  • Easy to use with mobile, SPA, and microservices

What is OAuth 2.0?

OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on HTTP services, like Google, Facebook, or GitHub.

Unlike authentication, OAuth 2.0 focuses on delegated access:

  • Users authorize apps without sharing passwords.
  • Apps receive access tokens to interact with APIs on behalf of users.
  • Common flows include Authorization Code, Implicit, Client Credentials, and Resource Owner Password Credentials.

OAuth 2.0 is often combined with OpenID Connect (OIDC) to provide authentication on top of authorization.


Implementing JWT Authentication

Basic Workflow

  1. User logs in with credentials.
  2. Server validates and issues a signed JWT.
  3. Client stores the token (localStorage or HTTP-only cookie).
  4. Client sends JWT in Authorization header for protected routes.
  5. Server verifies token before granting access.

Example with Node.js and Express

import express from 'express'
import jwt from 'jsonwebtoken'
import bodyParser from 'body-parser'

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

const SECRET_KEY = 'your-secret-key'

// Mock user database
const users = [{ id: 1, username: 'user1', password: 'pass123' }]

// Login route
app.post('/login', (req, res) => {
  const { username, password } = req.body
  const user = users.find(u => u.username === username && u.password === password)

  if (!user) return res.status(401).json({ message: 'Invalid credentials' })

  const token = jwt.sign({ id: user.id, username: user.username }, SECRET_KEY, { 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_KEY, (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}` })
})

app.listen(3000, () => console.log('Server running on port 3000'))

Tips:

  • Use strong secret keys and rotate them periodically.
  • Store tokens securely (HTTP-only cookies preferred for web).
  • Set appropriate expiration times and refresh tokens if needed.

Implementing OAuth 2.0 Authentication

OAuth 2.0 Authorization Code Flow Example (with GitHub)

  1. Redirect users to GitHub’s authorization URL with your app’s client ID.
  2. User grants permission and is redirected back with a code.
  3. Exchange the code for an access token via server-side request.
  4. Use the access token to fetch user data from GitHub API.

Basic Flow Example

import express from 'express'
import axios from 'axios'
import dotenv from 'dotenv'

dotenv.config()

const app = express()

const CLIENT_ID = process.env.GITHUB_CLIENT_ID
const CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET

app.get('/auth/github', (req, res) => {
  const redirectUri = 'http://localhost:3000/auth/github/callback'
  res.redirect(`https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${redirectUri}&scope=user`)
})

app.get('/auth/github/callback', async (req, res) => {
  const code = req.query.code

  try {
    const tokenResponse = await axios.post(
      `https://github.com/login/oauth/access_token`,
      {
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
        code,
      },
      { headers: { Accept: 'application/json' } }
    )

    const accessToken = tokenResponse.data.access_token

    // Use access token to get user info
    const userResponse = await axios.get('https://api.github.com/user', {
      headers: { Authorization: `token ${accessToken}` },
    })

    res.json(userResponse.data)
  } catch (error) {
    res.status(500).json({ error: 'Authentication failed' })
  }
})

app.listen(3000, () => console.log('Server running on port 3000'))

Security Considerations

  • Secure token storage: Avoid localStorage for sensitive tokens, prefer HTTP-only cookies.
  • Use HTTPS: Always serve auth endpoints over HTTPS.
  • Token expiration: Implement token expiration and refresh tokens to limit damage from leaks.
  • Validate scopes: Restrict access by scopes and permissions.
  • CSRF protection: For cookie-based tokens, implement CSRF prevention.
  • Proper logout: Invalidate tokens or clear cookies upon logout.

JWT vs OAuth 2.0: When to Use What?

Use CaseRecommended Approach
Simple stateless authJWT
Third-party delegated accessOAuth 2.0
Combining auth & authorizationOAuth 2.0 + OpenID Connect
Microservices communicationJWT with appropriate scopes

Conclusion

Implementing secure authentication is a foundational step in building trustworthy applications. JWTs provide a simple, stateless way to manage user sessions, while OAuth 2.0 allows delegated access and social logins with enhanced security.

Understanding their strengths and correctly implementing best practices will keep your users safe and your app scalable.


Resources


Secure authentication is the backbone of modern apps. Follow the blog for more deep dives into security, best practices, and scalable architectures.

TD

About Tridip Dutta

Creative Developer passionate about creating innovative digital experiences and exploring AI. I love sharing knowledge to help developers build better apps.