Implement Deno JWT authentication with OAuth2 integration for secure API development

Intermediate 45 min Apr 09, 2026 1,039 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up production-grade JWT authentication middleware in Deno with OAuth2 provider integration and role-based access control. Learn to secure API endpoints with proper token validation, user management, and enterprise-ready authentication flows.

Prerequisites

  • Deno runtime installed
  • Basic TypeScript knowledge
  • Understanding of JWT concepts
  • OAuth2 provider applications (Google/GitHub)

What this solves

Modern web applications require secure authentication mechanisms that can scale with growing user bases and integrate with existing identity providers. This tutorial shows you how to implement JWT (JSON Web Token) authentication in Deno applications with OAuth2 integration, enabling secure API development with role-based access control and session management.

Step-by-step installation

Install Deno runtime

Install the latest Deno runtime with security features enabled. Deno provides built-in support for modern web standards including JWT handling.

curl -fsSL https://deno.land/install.sh | sh
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
curl -fsSL https://deno.land/install.sh | sh
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Create project structure

Set up the directory structure for your Deno JWT authentication project with proper organization for middleware, routes, and configuration.

mkdir -p ~/deno-jwt-auth/{middleware,routes,config,utils,types}
cd ~/deno-jwt-auth

Create JWT configuration

Configure JWT settings including secret keys, expiration times, and OAuth2 provider settings. Use environment variables for sensitive configuration.

export interface AuthConfig {
  jwtSecret: string;
  jwtExpiration: string;
  refreshTokenExpiration: string;
  oauth2: {
    google: {
      clientId: string;
      clientSecret: string;
      redirectUri: string;
    };
    github: {
      clientId: string;
      clientSecret: string;
      redirectUri: string;
    };
  };
}

export const authConfig: AuthConfig = {
  jwtSecret: Deno.env.get("JWT_SECRET") || "your-super-secret-jwt-key-change-in-production",
  jwtExpiration: "1h",
  refreshTokenExpiration: "7d",
  oauth2: {
    google: {
      clientId: Deno.env.get("GOOGLE_CLIENT_ID") || "",
      clientSecret: Deno.env.get("GOOGLE_CLIENT_SECRET") || "",
      redirectUri: Deno.env.get("GOOGLE_REDIRECT_URI") || "http://localhost:8000/auth/google/callback"
    },
    github: {
      clientId: Deno.env.get("GITHUB_CLIENT_ID") || "",
      clientSecret: Deno.env.get("GITHUB_CLIENT_SECRET") || "",
      redirectUri: Deno.env.get("GITHUB_REDIRECT_URI") || "http://localhost:8000/auth/github/callback"
    }
  }
};

Define TypeScript interfaces

Create type definitions for users, JWT payloads, and OAuth2 responses to ensure type safety throughout your application.

export interface User {
  id: string;
  email: string;
  name: string;
  roles: string[];
  provider: 'local' | 'google' | 'github';
  providerId?: string;
  createdAt: Date;
  lastLogin?: Date;
}

export interface JWTPayload {
  sub: string; // user id
  email: string;
  name: string;
  roles: string[];
  iat: number;
  exp: number;
}

export interface RefreshToken {
  token: string;
  userId: string;
  expiresAt: Date;
  createdAt: Date;
}

export interface OAuth2UserInfo {
  id: string;
  email: string;
  name: string;
  picture?: string;
}

export interface AuthResponse {
  accessToken: string;
  refreshToken: string;
  user: User;
}

Create JWT utilities

Implement JWT token generation, validation, and refresh functionality with proper error handling and security measures.

import { create, verify, decode } from "https://deno.land/x/djwt@v3.0.2/mod.ts";
import { authConfig } from "../config/auth.ts";
import type { JWTPayload, User } from "../types/auth.ts";

const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
  "raw",
  encoder.encode(authConfig.jwtSecret),
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign", "verify"]
);

export async function generateAccessToken(user: User): Promise

Implement OAuth2 providers

Create OAuth2 integration handlers for Google and GitHub authentication with proper token exchange and user information retrieval.

import { authConfig } from "../config/auth.ts";
import type { OAuth2UserInfo } from "../types/auth.ts";

export class OAuth2Provider {
  static getGoogleAuthUrl(state: string): string {
    const params = new URLSearchParams({
      client_id: authConfig.oauth2.google.clientId,
      redirect_uri: authConfig.oauth2.google.redirectUri,
      response_type: "code",
      scope: "openid email profile",
      state: state
    });
    return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
  }

  static getGithubAuthUrl(state: string): string {
    const params = new URLSearchParams({
      client_id: authConfig.oauth2.github.clientId,
      redirect_uri: authConfig.oauth2.github.redirectUri,
      scope: "user:email",
      state: state
    });
    return `https://github.com/login/oauth/authorize?${params.toString()}`;
  }

  static async exchangeGoogleCode(code: string): Promise

Create JWT authentication middleware

Implement middleware to validate JWT tokens, extract user information, and enforce authentication requirements on protected routes.

import type { Context, Next } from "https://deno.land/x/oak@v15.0.0/mod.ts";
import { verifyAccessToken } from "../utils/jwt.ts";
import type { JWTPayload } from "../types/auth.ts";

declare module "https://deno.land/x/oak@v15.0.0/mod.ts" {
  interface Context {
    user?: JWTPayload;
  }
}

export async function authenticateToken(ctx: Context, next: Next) {
  const authHeader = ctx.request.headers.get("Authorization");
  const token = authHeader?.split(" ")[1]; // Bearer TOKEN

  if (!token) {
    ctx.response.status = 401;
    ctx.response.body = { error: "Access token required" };
    return;
  }

  const payload = await verifyAccessToken(token);
  if (!payload) {
    ctx.response.status = 401;
    ctx.response.body = { error: "Invalid or expired token" };
    return;
  }

  // Check token expiration
  if (payload.exp < Math.floor(Date.now() / 1000)) {
    ctx.response.status = 401;
    ctx.response.body = { error: "Token expired" };
    return;
  }

  ctx.user = payload;
  await next();
}

export function requireRoles(...roles: string[]) {
  return async (ctx: Context, next: Next) => {
    if (!ctx.user) {
      ctx.response.status = 401;
      ctx.response.body = { error: "Authentication required" };
      return;
    }

    const hasRole = roles.some(role => ctx.user!.roles.includes(role));
    if (!hasRole) {
      ctx.response.status = 403;
      ctx.response.body = { error: "Insufficient permissions" };
      return;
    }

    await next();
  };
}

export async function optionalAuth(ctx: Context, next: Next) {
  const authHeader = ctx.request.headers.get("Authorization");
  const token = authHeader?.split(" ")[1];

  if (token) {
    const payload = await verifyAccessToken(token);
    if (payload && payload.exp >= Math.floor(Date.now() / 1000)) {
      ctx.user = payload;
    }
  }

  await next();
}

Create user management utilities

Implement user storage, retrieval, and management functions. This example uses in-memory storage, but you should replace with a database in production.

import type { User, RefreshToken } from "../types/auth.ts";

// In-memory storage (replace with database in production)
const users = new Map

Create authentication routes

Implement API endpoints for OAuth2 login, token refresh, logout, and user profile management with proper error handling.

import { Router } from "https://deno.land/x/oak@v15.0.0/mod.ts";
import { OAuth2Provider } from "../utils/oauth2.ts";
import { generateAccessToken, generateRefreshToken } from "../utils/jwt.ts";
import { UserService } from "../utils/users.ts";
import { authenticateToken } from "../middleware/auth.ts";
import type { AuthResponse } from "../types/auth.ts";

const router = new Router({ prefix: "/auth" });

// Generate OAuth2 authorization URLs
router.get("/google", (ctx) => {
  const state = crypto.randomUUID();
  // Store state in session or cache for validation
  const authUrl = OAuth2Provider.getGoogleAuthUrl(state);
  ctx.response.body = { authUrl, state };
});

router.get("/github", (ctx) => {
  const state = crypto.randomUUID();
  const authUrl = OAuth2Provider.getGithubAuthUrl(state);
  ctx.response.body = { authUrl, state };
});

// OAuth2 callback handlers
router.get("/google/callback", async (ctx) => {
  const code = ctx.request.url.searchParams.get("code");
  const state = ctx.request.url.searchParams.get("state");

  if (!code) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Authorization code required" };
    return;
  }

  const userInfo = await OAuth2Provider.exchangeGoogleCode(code);
  if (!userInfo) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Failed to get user information" };
    return;
  }

  let user = await UserService.findByProvider("google", userInfo.id);
  if (!user) {
    user = await UserService.createUser({
      email: userInfo.email,
      name: userInfo.name,
      roles: ["user"],
      provider: "google",
      providerId: userInfo.id
    });
  }

  await UserService.updateLastLogin(user.id);

  const accessToken = await generateAccessToken(user);
  const refreshToken = await generateRefreshToken();
  await UserService.storeRefreshToken(refreshToken, user.id);

  const response: AuthResponse = {
    accessToken,
    refreshToken,
    user
  };

  ctx.response.body = response;
});

router.get("/github/callback", async (ctx) => {
  const code = ctx.request.url.searchParams.get("code");

  if (!code) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Authorization code required" };
    return;
  }

  const userInfo = await OAuth2Provider.exchangeGithubCode(code);
  if (!userInfo) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Failed to get user information" };
    return;
  }

  let user = await UserService.findByProvider("github", userInfo.id);
  if (!user) {
    user = await UserService.createUser({
      email: userInfo.email,
      name: userInfo.name,
      roles: ["user"],
      provider: "github",
      providerId: userInfo.id
    });
  }

  await UserService.updateLastLogin(user.id);

  const accessToken = await generateAccessToken(user);
  const refreshToken = await generateRefreshToken();
  await UserService.storeRefreshToken(refreshToken, user.id);

  const response: AuthResponse = {
    accessToken,
    refreshToken,
    user
  };

  ctx.response.body = response;
});

// Token refresh endpoint
router.post("/refresh", async (ctx) => {
  const body = await ctx.request.body({ type: "json" }).value;
  const refreshToken = body.refreshToken;

  if (!refreshToken) {
    ctx.response.status = 400;
    ctx.response.body = { error: "Refresh token required" };
    return;
  }

  const userId = await UserService.validateRefreshToken(refreshToken);
  if (!userId) {
    ctx.response.status = 401;
    ctx.response.body = { error: "Invalid or expired refresh token" };
    return;
  }

  const user = await UserService.findById(userId);
  if (!user) {
    ctx.response.status = 404;
    ctx.response.body = { error: "User not found" };
    return;
  }

  const newAccessToken = await generateAccessToken(user);
  const newRefreshToken = await generateRefreshToken();

  await UserService.revokeRefreshToken(refreshToken);
  await UserService.storeRefreshToken(newRefreshToken, user.id);

  ctx.response.body = {
    accessToken: newAccessToken,
    refreshToken: newRefreshToken
  };
});

// Logout endpoint
router.post("/logout", authenticateToken, async (ctx) => {
  const body = await ctx.request.body({ type: "json" }).value;
  const refreshToken = body.refreshToken;

  if (refreshToken) {
    await UserService.revokeRefreshToken(refreshToken);
  }

  ctx.response.body = { message: "Logged out successfully" };
});

// Get current user profile
router.get("/me", authenticateToken, async (ctx) => {
  const user = await UserService.findById(ctx.user!.sub);
  if (!user) {
    ctx.response.status = 404;
    ctx.response.body = { error: "User not found" };
    return;
  }

  ctx.response.body = { user };
});

export default router;

Create protected API routes

Implement example API endpoints with role-based access control to demonstrate how to secure different parts of your application.

import { Router } from "https://deno.land/x/oak@v15.0.0/mod.ts";
import { authenticateToken, requireRoles, optionalAuth } from "../middleware/auth.ts";

const router = new Router({ prefix: "/api" });

// Public endpoint (no authentication required)
router.get("/public", (ctx) => {
  ctx.response.body = {
    message: "This is a public endpoint",
    timestamp: new Date().toISOString()
  };
});

// Endpoint with optional authentication
router.get("/optional", optionalAuth, (ctx) => {
  const message = ctx.user 
    ? `Welcome back, ${ctx.user.name}!` 
    : "This endpoint works for both authenticated and anonymous users";
  
  ctx.response.body = {
    message,
    user: ctx.user || null,
    timestamp: new Date().toISOString()
  };
});

// Protected endpoint (authentication required)
router.get("/protected", authenticateToken, (ctx) => {
  ctx.response.body = {
    message: `Hello ${ctx.user!.name}, you are authenticated!`,
    user: {
      id: ctx.user!.sub,
      email: ctx.user!.email,
      r

Automated install script

Run this to automate the entire setup

Wil je dit niet zelf beheren?

Wij beheren infrastructuur voor bedrijven die afhankelijk zijn van uptime. Volledig beheerd, met één vast aanspreekpunt dat je omgeving kent.

U krijgt één vast aanspreekpunt dat uw omgeving kent

Rotterdam 06:18 · bereikbaar in een bericht, geen ticketformulier