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
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 {
const payload: JWTPayload = {
sub: user.id,
email: user.email,
name: user.name,
roles: user.roles,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
};
return await create({ alg: "HS256", typ: "JWT" }, payload, key);
}
export async function generateRefreshToken(): Promise {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
export async function verifyAccessToken(token: string): Promise {
try {
const payload = await verify(token, key);
return payload as JWTPayload;
} catch (error) {
console.error("JWT verification failed:", error.message);
return null;
}
}
export function decodeToken(token: string): JWTPayload | null {
try {
const [header, payload] = decode(token);
return payload as JWTPayload;
} catch {
return null;
}
}
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 {
try {
// Exchange code for access token
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: authConfig.oauth2.google.clientId,
client_secret: authConfig.oauth2.google.clientSecret,
code: code,
grant_type: "authorization_code",
redirect_uri: authConfig.oauth2.google.redirectUri
})
});
const tokenData = await tokenResponse.json();
if (!tokenData.access_token) return null;
// Get user information
const userResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
headers: { "Authorization": Bearer ${tokenData.access_token} }
});
const userData = await userResponse.json();
return {
id: userData.id,
email: userData.email,
name: userData.name,
picture: userData.picture
};
} catch (error) {
console.error("Google OAuth2 error:", error);
return null;
}
}
static async exchangeGithubCode(code: string): Promise {
try {
// Exchange code for access token
const tokenResponse = await fetch("https://github.com/login/oauth/access_token", {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
client_id: authConfig.oauth2.github.clientId,
client_secret: authConfig.oauth2.github.clientSecret,
code: code
})
});
const tokenData = await tokenResponse.json();
if (!tokenData.access_token) return null;
// Get user information
const userResponse = await fetch("https://api.github.com/user", {
headers: { "Authorization": Bearer ${tokenData.access_token} }
});
const userData = await userResponse.json();
return {
id: userData.id.toString(),
email: userData.email,
name: userData.name || userData.login
};
} catch (error) {
console.error("GitHub OAuth2 error:", error);
return null;
}
}
}
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();
const refreshTokens = new Map();
export class UserService {
static async createUser(userData: Omit): Promise {
const user: User = {
id: crypto.randomUUID(),
...userData,
createdAt: new Date()
};
users.set(user.id, user);
return user;
}
static async findById(id: string): Promise {
return users.get(id) || null;
}
static async findByEmail(email: string): Promise {
for (const user of users.values()) {
if (user.email === email) {
return user;
}
}
return null;
}
static async findByProvider(provider: string, providerId: string): Promise {
for (const user of users.values()) {
if (user.provider === provider && user.providerId === providerId) {
return user;
}
}
return null;
}
static async updateLastLogin(userId: string): Promise {
const user = users.get(userId);
if (user) {
user.lastLogin = new Date();
users.set(userId, user);
}
}
static async storeRefreshToken(token: string, userId: string): Promise {
const refreshToken: RefreshToken = {
token,
userId,
expiresAt: new Date(Date.now() + 7 24 60 60 1000), // 7 days
createdAt: new Date()
};
refreshTokens.set(token, refreshToken);
}
static async validateRefreshToken(token: string): Promise {
const refreshToken = refreshTokens.get(token);
if (!refreshToken || refreshToken.expiresAt < new Date()) {
if (refreshToken) {
refreshTokens.delete(token);
}
return null;
}
return refreshToken.userId;
}
static async revokeRefreshToken(token: string): Promise {
refreshTokens.delete(token);
}
static async revokeAllUserTokens(userId: string): Promise {
for (const [token, refreshToken] of refreshTokens.entries()) {
if (refreshToken.userId === userId) {
refreshTokens.delete(token);
}
}
}
}
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,
roles: ctx.user!.roles
},
timestamp: new Date().toISOString()
};
});
// Admin-only endpoint
router.get("/admin", authenticateToken, requireRoles("admin"), (ctx) => {
ctx.response.body = {
message: "This is an admin-only endpoint",
user: ctx.user!.email,
adminData: {
systemStatus: "operational",
activeUsers: 42,
serverUptime: "5 days, 12 hours"
}
};
});
// Moderator or admin endpoint
router.get("/moderate", authenticateToken, requireRoles("moderator", "admin"), (ctx) => {
ctx.response.body = {
message: "This endpoint requires moderator or admin role",
user: ctx.user!.email,
moderationData: {
pendingReports: 3,
flaggedContent: 8
}
};
});
// User profile management (users can only access their own data)
router.get("/users/:id", authenticateToken, async (ctx) => {
const requestedUserId = ctx.params.id;
const currentUserId = ctx.user!.sub;
// Users can only access their own profile unless they're admin
if (requestedUserId !== currentUserId && !ctx.user!.roles.includes("admin")) {
ctx.response.status = 403;
ctx.response.body = { error: "Access denied: You can only access your own profile" };
return;
}
const { UserService } = await import("../utils/users.ts");
const user = await UserService.findById(requestedUserId);
if (!user) {
ctx.response.status = 404;
ctx.response.body = { error: "User not found" };
return;
}
ctx.response.body = {
user: {
id: user.id,
email: user.email,
name: user.name,
roles: user.roles,
provider: user.provider,
createdAt: user.createdAt,
lastLogin: user.lastLogin
}
};
});
export default router;
Create the main application server
Set up the main Deno server with Oak framework, CORS handling, error management, and route registration.
import { Application, Router } from "https://deno.land/x/oak@v15.0.0/mod.ts";
import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";
import authRoutes from "./routes/auth.ts";
import apiRoutes from "./routes/api.ts";
const app = new Application();
const router = new Router();
// CORS configuration
app.use(oakCors({
origin: ["http://localhost:3000", "http://localhost:8000"], // Add your frontend URLs
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
}));
// Error handling middleware
app.use(async (ctx, next) => {
try {
await next();
} catch (error) {
console.error("Server error:", error);
ctx.response.status = 500;
ctx.response.body = {
error: "Internal server error",
message: Deno.env.get("NODE_ENV") === "development" ? error.message : "Something went wrong"
};
}
});
// Request logging middleware
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(${ctx.request.method} ${ctx.request.url.pathname} - ${ctx.response.status} - ${ms}ms);
});
// Health check endpoint
router.get("/health", (ctx) => {
ctx.response.body = {
status: "healthy",
timestamp: new Date().toISOString(),
uptime: Math.floor(performance.now() / 1000)
};
});
// API documentation endpoint
router.get("/", (ctx) => {
ctx.response.body = {
name: "Deno JWT Authentication API",
version: "1.0.0",
endpoints: {
auth: {
"GET /auth/google": "Get Google OAuth2 authorization URL",
"GET /auth/github": "Get GitHub OAuth2 authorization URL",
"GET /auth/google/callback": "Google OAuth2 callback",
"GET /auth/github/callback": "GitHub OAuth2 callback",
"POST /auth/refresh": "Refresh access token",
"POST /auth/logout": "Logout user",
"GET /auth/me": "Get current user profile"
},
api: {
"GET /api/public": "Public endpoint (no auth required)",
"GET /api/optional": "Optional auth endpoint",
"GET /api/protected": "Protected endpoint (auth required)",
"GET /api/admin": "Admin-only endpoint",
"GET /api/moderate": "Moderator/admin endpoint",
"GET /api/users/:id": "Get user profile"
},
utility: {
"GET /health": "Health check endpoint",
"GET /": "API documentation"
}
}
};
});
// Register routes
app.use(router.routes());
app.use(router.allowedMethods());
app.use(authRoutes.routes());
app.use(authRoutes.allowedMethods());
app.use(apiRoutes.routes());
app.use(apiRoutes.allowedMethods());
// Handle 404
app.use((ctx) => {
ctx.response.status = 404;
ctx.response.body = { error: "Not found" };
});
const port = parseInt(Deno.env.get("PORT") || "8000");
console.log(🚀 Server starting on http://localhost:${port});
console.log(📚 API documentation available at http://localhost:${port});
await app.listen({ port });
Create environment configuration
Set up environment variables for secure configuration management. Never commit secrets to version control.
# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key-change-in-production-minimum-256-bits
Google OAuth2
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/auth/google/callback
GitHub OAuth2
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_REDIRECT_URI=http://localhost:8000/auth/github/callback
Server Configuration
PORT=8000
NODE_ENV=development
Create startup script
Create a script to run your Deno application with the necessary permissions and environment loading.
#!/bin/bash
Load environment variables
export $(cat .env | grep -v '^#' | xargs)
Run Deno application with required permissions
deno run \
--allow-net \
--allow-env \
--allow-read \
--watch \
main.ts
chmod +x run.sh
Start the authentication server
Launch your Deno JWT authentication server and verify it's running correctly with proper permissions.
./run.sh
Verify your setup
Test your JWT authentication implementation by checking various endpoints and authentication flows.
# Check server health
curl http://localhost:8000/health
Test public endpoint
curl http://localhost:8000/api/public
Get Google OAuth2 authorization URL
curl http://localhost:8000/auth/google
Get GitHub OAuth2 authorization URL
curl http://localhost:8000/auth/github
Test protected endpoint (should return 401)
curl -H "Authorization: Bearer invalid-token" http://localhost:8000/api/protected
For complete OAuth2 testing, you'll need to configure your OAuth2 applications in the Google Console and GitHub Developer Settings, then use a web browser to complete the authorization flow.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| "JWT verification failed" errors | Wrong JWT secret or malformed token | Verify JWT_SECRET matches between token generation and verification |
| OAuth2 callback returns 400 error | Invalid client ID/secret or wrong redirect URI | Check OAuth2 provider configuration and redirect URI matching |
| "Token expired" on fresh tokens | Server time synchronization issues | Ensure server time is synchronized with NTP |
| CORS errors from frontend | Missing or incorrect CORS configuration | Add your frontend domain to the CORS origin list |
| "Access token required" on authenticated requests | Missing or malformed Authorization header | Include "Authorization: Bearer TOKEN" header |
| "Insufficient permissions" errors | User lacks required roles | Verify user roles and endpoint requirements |
Production considerations
Replace in-memory storage
The example uses in-memory user storage. For production, integrate with a proper database like PostgreSQL or MongoDB.
# Example database integration
deno add @deno/postgres
deno add mongodb
Implement rate limiting
Add rate limiting to prevent brute force attacks and API abuse, especially on authentication endpoints.
import { RateLimiter } from "https://deno.land/x/oak_rate_limit@v1.0.0/mod.ts";
export const authRateLimit = RateLimiter({
windowMs: 15 60 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: "Too many authentication attempts, please try again later"
});
Add request validation
Implement input validation using a schema validation library to ensure request data integrity and security.
deno add @valibot/valibot
You can extend this foundation by integrating with existing database tutorials like Configure Deno database connections to PostgreSQL and Redis with connection pooling for persistent storage, or enhance security by implementing additional measures from Set up Node.js application security with Helmet and rate limiting.
Next steps
- Deploy Deno applications with Docker containers and production optimization
- Implement Deno WebSocket authentication with JWT for real-time applications
- Configure Deno API rate limiting with Redis backend for production scaling
- Set up Deno microservices architecture with service discovery and load balancing
- Implement Deno session management with Redis cluster for scalable authentication
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Global variables
PROJECT_NAME="deno-jwt-auth"
PROJECT_DIR=""
CURRENT_USER=""
DENO_VERSION="latest"
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Install and configure Deno JWT authentication with OAuth2 integration"
echo ""
echo "Options:"
echo " -d, --dir DIR Project directory (default: \$HOME/deno-jwt-auth)"
echo " -u, --user USER User to run as (default: current user)"
echo " -h, --help Show this help message"
echo ""
echo "Example:"
echo " $0 --dir /opt/deno-jwt-auth --user deno"
}
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Cleanup function
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log_error "Installation failed. Cleaning up..."
if [ -d "$PROJECT_DIR" ]; then
rm -rf "$PROJECT_DIR"
log_info "Removed project directory: $PROJECT_DIR"
fi
fi
exit $exit_code
}
# Set trap for cleanup
trap cleanup ERR
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-d|--dir)
PROJECT_DIR="$2"
shift 2
;;
-u|--user)
CURRENT_USER="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
}
# Auto-detect distribution and package manager
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_success "Detected distribution: $PRETTY_NAME using $PKG_MGR"
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
}
# Check prerequisites
check_prerequisites() {
log_info "[1/8] Checking prerequisites..."
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
if ! command -v sudo &> /dev/null; then
log_error "This script requires root privileges or sudo"
exit 1
fi
SUDO="sudo"
else
SUDO=""
fi
# Set default values
if [ -z "$CURRENT_USER" ]; then
CURRENT_USER="${SUDO_USER:-$(whoami)}"
fi
if [ -z "$PROJECT_DIR" ]; then
PROJECT_DIR="/home/$CURRENT_USER/$PROJECT_NAME"
fi
# Check if user exists
if ! id "$CURRENT_USER" &>/dev/null; then
log_error "User $CURRENT_USER does not exist"
exit 1
fi
log_success "Prerequisites check completed"
}
# Install dependencies
install_dependencies() {
log_info "[2/8] Installing system dependencies..."
$SUDO $PKG_UPDATE
$SUDO $PKG_INSTALL curl unzip
log_success "System dependencies installed"
}
# Install Deno
install_deno() {
log_info "[3/8] Installing Deno runtime..."
# Install Deno for the target user
if [ "$CURRENT_USER" != "$(whoami)" ]; then
$SUDO -u "$CURRENT_USER" bash -c 'curl -fsSL https://deno.land/install.sh | sh'
else
curl -fsSL https://deno.land/install.sh | sh
fi
# Add Deno to PATH for the user
local bashrc_path="/home/$CURRENT_USER/.bashrc"
if ! grep -q '.deno/bin' "$bashrc_path" 2>/dev/null; then
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> "$bashrc_path"
chown "$CURRENT_USER:$CURRENT_USER" "$bashrc_path"
fi
log_success "Deno runtime installed"
}
# Create project structure
create_project_structure() {
log_info "[4/8] Creating project structure..."
# Create main project directory
$SUDO mkdir -p "$PROJECT_DIR"
# Create subdirectories
$SUDO mkdir -p "$PROJECT_DIR"/{middleware,routes,config,utils,types,tests}
# Set ownership
$SUDO chown -R "$CURRENT_USER:$CURRENT_USER" "$PROJECT_DIR"
# Set permissions
find "$PROJECT_DIR" -type d -exec chmod 755 {} \;
log_success "Project structure created at $PROJECT_DIR"
}
# Create configuration files
create_config_files() {
log_info "[5/8] Creating configuration files..."
# Create auth configuration
cat > "$PROJECT_DIR/config/auth.ts" << 'EOF'
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"
}
}
};
EOF
# Create TypeScript interfaces
cat > "$PROJECT_DIR/types/auth.ts" << 'EOF'
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;
}
EOF
# Create JWT utilities
cat > "$PROJECT_DIR/utils/jwt.ts" << 'EOF'
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<string> {
const payload: JWTPayload = {
sub: user.id,
email: user.email,
name: user.name,
roles: user.roles,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
};
return await create({ alg: "HS256", typ: "JWT" }, payload, key);
}
export async function verifyToken(token: string): Promise<JWTPayload | null> {
try {
const payload = await verify(token, key);
return payload as JWTPayload;
} catch {
return null;
}
}
EOF
# Create environment template
cat > "$PROJECT_DIR/.env.example" << 'EOF'
# JWT Configuration
JWT_SECRET=your-super-secret-jwt-key-change-in-production
# Google OAuth2
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/auth/google/callback
# GitHub OAuth2
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_REDIRECT_URI=http://localhost:8000/auth/github/callback
# Server Configuration
PORT=8000
HOST=localhost
EOF
# Set file permissions and ownership
chown -R "$CURRENT_USER:$CURRENT_USER" "$PROJECT_DIR"
find "$PROJECT_DIR" -type f -exec chmod 644 {} \;
chmod 600 "$PROJECT_DIR/.env.example"
log_success "Configuration files created"
}
# Create sample middleware and routes
create_sample_code() {
log_info "[6/8] Creating sample middleware and routes..."
# Create JWT middleware
cat > "$PROJECT_DIR/middleware/auth.ts" << 'EOF'
import { verifyToken } from "../utils/jwt.ts";
import type { JWTPayload } from "../types/auth.ts";
export interface AuthContext {
user?: JWTPayload;
}
export async function authMiddleware(
req: Request,
ctx: AuthContext
): Promise<Response | void> {
const authHeader = req.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 });
}
const token = authHeader.slice(7);
const payload = await verifyToken(token);
if (!payload) {
return new Response("Invalid token", { status: 401 });
}
ctx.user = payload;
}
EOF
# Create basic auth routes
cat > "$PROJECT_DIR/routes/auth.ts" << 'EOF'
import { generateAccessToken } from "../utils/jwt.ts";
import type { User, AuthResponse } from "../types/auth.ts";
export async function loginHandler(req: Request): Promise<Response> {
try {
const { email, password } = await req.json();
// Placeholder user validation - implement your own logic
const user: User = {
id: "1",
email,
name: "Test User",
roles: ["user"],
provider: "local",
createdAt: new Date(),
};
const accessToken = await generateAccessToken(user);
const refreshToken = crypto.randomUUID();
const response: AuthResponse = {
accessToken,
refreshToken,
user,
};
return new Response(JSON.stringify(response), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response("Login failed", { status: 400 });
}
}
EOF
chown -R "$CURRENT_USER:$CURRENT_USER" "$PROJECT_DIR"
find "$PROJECT_DIR" -type f -name "*.ts" -exec chmod 644 {} \;
log_success "Sample code created"
}
# Create systemd service
create_systemd_service() {
log_info "[7/8] Creating systemd service..."
cat > /tmp/deno-jwt-auth.service << EOF
[Unit]
Description=Deno JWT Authentication Service
After=network.target
[Service]
Type=simple
User=$CURRENT_USER
Group=$CURRENT_USER
WorkingDirectory=$PROJECT_DIR
Environment=PATH=/home/$CURRENT_USER/.deno/bin:\$PATH
ExecStart=/home/$CURRENT_USER/.deno/bin/deno run --allow-net --allow-env --allow-read main.ts
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
$SUDO mv /tmp/deno-jwt-auth.service /etc/systemd/system/
$SUDO chmod 644 /etc/systemd/system/deno-jwt-auth.service
$SUDO systemctl daemon-reload
log_success "Systemd service created"
}
# Verification
verify_installation() {
log_info "[8/8] Verifying installation..."
# Check if Deno is installed
if $SUDO -u "$CURRENT_USER" bash -c 'export PATH="$HOME/.deno/bin:$PATH" && deno --version' &>/dev/null; then
log_success "Deno runtime is working"
else
log_warning "Deno runtime verification failed"
fi
# Check project structure
if [ -d "$PROJECT_DIR" ] && [ -f "$PROJECT_DIR/config/auth.ts" ]; then
log_success "Project structure is valid"
else
log_warning "Project structure verification failed"
fi
# Check systemd service
Review the script before running. Execute with: bash install.sh