Let me show you how to set up OAuth2 authentication on Alpine Linux! OAuth2 lets users log in to your app using their existing accounts from Google, GitHub, or other providers. It’s like having a universal key that works across many doors!
🤔 What is OAuth2?
OAuth2 is a secure way for users to log in without sharing passwords with your app. Instead of creating yet another username and password, users can click “Login with Google” and they’re in! It’s safer because your app never sees their actual password.
Why use OAuth2?
- No password storage needed
- Users trust big providers
- Easier user onboarding
- Better security
- Social features access
🎯 What You Need
Before starting, you’ll need:
- Alpine Linux installed
- Web server running
- Developer accounts (Google/GitHub)
- Basic programming knowledge
- About 30 minutes
📋 Step 1: Install Required Tools
Let’s get the tools we need:
# Update packages
apk update
# Install Node.js for our example app
apk add nodejs npm
# Install Python (alternative option)
apk add python3 py3-pip
# Install development tools
apk add git curl openssl
# Install Redis for sessions
apk add redis
rc-service redis start
rc-update add redis
# Create project directory
mkdir -p /var/www/oauth2-app
cd /var/www/oauth2-app
📋 Step 2: Set Up OAuth2 with Google
First, let’s configure Google OAuth2:
# 1. Go to Google Cloud Console
# https://console.cloud.google.com
# 2. Create new project or select existing
# 3. Enable Google+ API
# 4. Create OAuth2 credentials
# - Application type: Web application
# - Authorized redirect URIs: http://localhost:3000/auth/google/callback
# 5. Save your credentials
cat > .env << 'EOF'
# Google OAuth2 Credentials
GOOGLE_CLIENT_ID=your-client-id-here
GOOGLE_CLIENT_SECRET=your-client-secret-here
GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback
SESSION_SECRET=your-random-session-secret
EOF
# Protect credentials
chmod 600 .env
📋 Step 3: Create OAuth2 Application
Build a simple OAuth2 app:
# Initialize Node.js project
npm init -y
# Install dependencies
npm install express express-session passport passport-google-oauth20
npm install dotenv ejs
# Create main application file
cat > app.js << 'EOF'
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
require('dotenv').config();
const app = express();
// Session setup
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: { secure: false } // Set true with HTTPS
}));
// Passport setup
app.use(passport.initialize());
app.use(passport.session());
// View engine
app.set('view engine', 'ejs');
// Passport serialization
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Google OAuth2 strategy
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL
},
(accessToken, refreshToken, profile, done) => {
// In production, save user to database
const user = {
id: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
photo: profile.photos[0].value
};
return done(null, user);
}
));
// Routes
app.get('/', (req, res) => {
res.render('index', { user: req.user });
});
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/dashboard');
}
);
app.get('/dashboard', ensureAuth, (req, res) => {
res.render('dashboard', { user: req.user });
});
app.get('/logout', (req, res) => {
req.logout(() => {
res.redirect('/');
});
});
// Middleware
function ensureAuth(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/');
}
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
EOF
# Create views directory
mkdir views
# Create index page
cat > views/index.ejs << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>OAuth2 Demo</title>
<style>
body { font-family: Arial; margin: 50px; text-align: center; }
.login-btn {
background: #4285f4;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 5px;
display: inline-block;
margin: 10px;
}
.user-info { margin: 20px 0; }
img { border-radius: 50%; }
</style>
</head>
<body>
<h1>OAuth2 Authentication Demo</h1>
<% if (!user) { %>
<p>Please log in to continue</p>
<a href="/auth/google" class="login-btn">Login with Google</a>
<% } else { %>
<div class="user-info">
<img src="<%= user.photo %>" width="100" alt="Profile">
<h2>Welcome, <%= user.name %>!</h2>
<p>Email: <%= user.email %></p>
<a href="/dashboard">Go to Dashboard</a> |
<a href="/logout">Logout</a>
</div>
<% } %>
</body>
</html>
EOF
# Create dashboard page
cat > views/dashboard.ejs << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
<style>
body { font-family: Arial; margin: 50px; }
.container { max-width: 800px; margin: 0 auto; }
.user-card {
background: #f0f0f0;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>User Dashboard</h1>
<div class="user-card">
<h2>Profile Information</h2>
<p><strong>Name:</strong> <%= user.name %></p>
<p><strong>Email:</strong> <%= user.email %></p>
<p><strong>ID:</strong> <%= user.id %></p>
</div>
<p>This is a protected page. Only authenticated users can see this!</p>
<a href="/">Home</a> | <a href="/logout">Logout</a>
</div>
</body>
</html>
EOF
📋 Step 4: Add GitHub OAuth2
Support multiple providers:
# Install GitHub strategy
npm install passport-github2
# Update .env with GitHub credentials
cat >> .env << 'EOF'
# GitHub OAuth2 Credentials
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_CALLBACK_URL=http://localhost:3000/auth/github/callback
EOF
# Add GitHub strategy to app.js
cat >> github-auth.js << 'EOF'
// Add this to your app.js file
const GitHubStrategy = require('passport-github2').Strategy;
// GitHub OAuth2 strategy
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: process.env.GITHUB_CALLBACK_URL
},
(accessToken, refreshToken, profile, done) => {
const user = {
id: profile.id,
name: profile.displayName || profile.username,
email: profile.emails?.[0]?.value || 'No public email',
photo: profile.photos[0].value,
provider: 'github'
};
return done(null, user);
}
));
// GitHub routes
app.get('/auth/github',
passport.authenticate('github', { scope: ['user:email'] })
);
app.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/dashboard');
}
);
EOF
# Update index.ejs to add GitHub button
# Add after Google button:
# <a href="/auth/github" class="login-btn" style="background: #333;">Login with GitHub</a>
📋 Step 5: Secure Your OAuth2 Setup
Implement security best practices:
# Create security middleware
cat > middleware/security.js << 'EOF'
// Security headers
exports.securityHeaders = (req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000');
next();
};
// Rate limiting
const rateLimit = {};
exports.rateLimiter = (req, res, next) => {
const ip = req.ip;
const now = Date.now();
if (!rateLimit[ip]) {
rateLimit[ip] = { count: 1, resetTime: now + 60000 };
} else if (now > rateLimit[ip].resetTime) {
rateLimit[ip] = { count: 1, resetTime: now + 60000 };
} else {
rateLimit[ip].count++;
if (rateLimit[ip].count > 10) {
return res.status(429).send('Too many requests');
}
}
next();
};
// CSRF protection
exports.csrfCheck = (req, res, next) => {
if (req.method === 'POST') {
const token = req.session.csrfToken;
const submitted = req.body._csrf || req.headers['x-csrf-token'];
if (!token || token !== submitted) {
return res.status(403).send('CSRF token mismatch');
}
}
next();
};
EOF
# Add HTTPS support
cat > setup-https.sh << 'EOF'
#!/bin/sh
# Generate self-signed certificate for testing
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
# Update app to use HTTPS
echo "
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, app).listen(3443, () => {
console.log('HTTPS Server running on https://localhost:3443');
});" >> app.js
EOF
chmod +x setup-https.sh
📋 Step 6: Handle OAuth2 Tokens
Manage access tokens properly:
# Create token manager
cat > utils/tokenManager.js << 'EOF'
const crypto = require('crypto');
class TokenManager {
constructor() {
this.tokens = new Map();
}
// Encrypt token
encrypt(text) {
const algorithm = 'aes-256-gcm';
const key = crypto.scryptSync(process.env.SESSION_SECRET, 'salt', 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encrypted,
authTag: authTag.toString('hex'),
iv: iv.toString('hex')
};
}
// Store token securely
storeToken(userId, provider, accessToken, refreshToken) {
const encrypted = this.encrypt(accessToken);
this.tokens.set(`${userId}-${provider}`, {
accessToken: encrypted,
refreshToken: refreshToken ? this.encrypt(refreshToken) : null,
expiresAt: Date.now() + 3600000 // 1 hour
});
}
// Get token
getToken(userId, provider) {
const tokenData = this.tokens.get(`${userId}-${provider}`);
if (!tokenData || Date.now() > tokenData.expiresAt) {
return null;
}
return tokenData;
}
// Refresh token
async refreshToken(userId, provider, refreshToken) {
// Implement token refresh logic here
// This depends on the OAuth2 provider
}
// Clean expired tokens
cleanupTokens() {
const now = Date.now();
for (const [key, value] of this.tokens.entries()) {
if (now > value.expiresAt) {
this.tokens.delete(key);
}
}
}
}
module.exports = new TokenManager();
EOF
# Run cleanup periodically
echo "*/30 * * * * cd /var/www/oauth2-app && node -e 'require(\"./utils/tokenManager\").cleanupTokens()'" | crontab -
📋 Step 7: Test OAuth2 Flow
Verify everything works:
# Start the application
node app.js
# Test endpoints with curl
# Check home page
curl http://localhost:3000/
# Check authentication required
curl -I http://localhost:3000/dashboard
# Should return 302 redirect
# Monitor OAuth2 flow
cat > monitor-oauth.js << 'EOF'
// OAuth2 Flow Monitor
const events = require('events');
const oauth2Monitor = new events.EventEmitter();
oauth2Monitor.on('auth_start', (provider, userId) => {
console.log(`[${new Date().toISOString()}] Auth started: ${provider} - User: ${userId}`);
});
oauth2Monitor.on('auth_success', (provider, userId) => {
console.log(`[${new Date().toISOString()}] Auth success: ${provider} - User: ${userId}`);
});
oauth2Monitor.on('auth_failure', (provider, error) => {
console.log(`[${new Date().toISOString()}] Auth failed: ${provider} - Error: ${error}`);
});
module.exports = oauth2Monitor;
EOF
# Create test script
cat > test-oauth.sh << 'EOF'
#!/bin/sh
echo "🔐 Testing OAuth2 Setup"
echo "====================="
# Check if app is running
if curl -s http://localhost:3000 > /dev/null; then
echo "✅ App is running"
else
echo "❌ App is not running"
exit 1
fi
# Check OAuth2 endpoints
for endpoint in "/auth/google" "/auth/github"; do
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000$endpoint)
if [ "$response" = "302" ]; then
echo "✅ $endpoint redirects correctly"
else
echo "❌ $endpoint not working (HTTP $response)"
fi
done
echo ""
echo "📍 OAuth2 Providers Status:"
echo " Google: Configured"
echo " GitHub: Configured"
echo ""
echo "Visit http://localhost:3000 to test login"
EOF
chmod +x test-oauth.sh
./test-oauth.sh
🎮 Practice Exercise
Extend the OAuth2 implementation:
- Add Facebook OAuth2
- Implement user roles
- Add remember me option
- Create API endpoints
# Add Facebook OAuth2
npm install passport-facebook
# Add to .env:
# FACEBOOK_APP_ID=your-app-id
# FACEBOOK_APP_SECRET=your-app-secret
# FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback
# Implement user roles
cat > models/user.js << 'EOF'
const users = new Map();
class User {
constructor(profile, provider) {
this.id = `${provider}-${profile.id}`;
this.providerId = profile.id;
this.provider = provider;
this.name = profile.displayName;
this.email = profile.emails?.[0]?.value;
this.photo = profile.photos?.[0]?.value;
this.role = 'user'; // Default role
this.createdAt = new Date();
this.lastLogin = new Date();
}
static findOrCreate(profile, provider) {
const id = `${provider}-${profile.id}`;
let user = users.get(id);
if (!user) {
user = new User(profile, provider);
users.set(id, user);
} else {
user.lastLogin = new Date();
}
return user;
}
static findById(id) {
return users.get(id);
}
isAdmin() {
return this.role === 'admin';
}
}
module.exports = User;
EOF
🚨 Troubleshooting Common Issues
Redirect URI Mismatch
Fix OAuth2 redirect issues:
# Check exact redirect URI in provider console
# Must match exactly including:
# - Protocol (http/https)
# - Domain
# - Port
# - Path
# Common fixes:
# 1. Update provider settings
# 2. Check .env file
# 3. Verify callback routes
Session Not Persisting
Fix session problems:
# Use Redis for sessions
npm install connect-redis redis
# Update session config:
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // With HTTPS
httpOnly: true,
maxAge: 86400000 // 24 hours
}
}));
Provider Errors
Debug provider-specific issues:
# Enable debug mode
DEBUG=passport:* node app.js
# Log all OAuth2 events
passport.use(new GoogleStrategy({
// ... config
},
async (accessToken, refreshToken, profile, done) => {
try {
console.log('Profile received:', profile);
// Process user
} catch (error) {
console.error('OAuth2 error:', error);
done(error);
}
}
));
💡 Pro Tips
Tip 1: Multiple Accounts
Link multiple providers:
# Allow users to link accounts
function linkAccount(userId, provider, profile) {
const user = User.findById(userId);
if (user) {
user.linkedAccounts = user.linkedAccounts || {};
user.linkedAccounts[provider] = {
id: profile.id,
email: profile.emails?.[0]?.value
};
}
}
Tip 2: Scope Management
Request only needed permissions:
# Minimal scopes
passport.authenticate('google', {
scope: ['profile'] // Just basic profile
});
# Extended scopes
passport.authenticate('google', {
scope: ['profile', 'email', 'https://www.googleapis.com/auth/calendar']
});
Tip 3: Token Storage
Store tokens securely:
# Never store in:
# - Cookies (without encryption)
# - LocalStorage
# - URL parameters
# Good options:
# - Server session
# - Encrypted database
# - Secure token service
✅ Best Practices
-
Always use HTTPS
# Force HTTPS in production app.use((req, res, next) => { if (req.header('x-forwarded-proto') !== 'https') { res.redirect(`https://${req.header('host')}${req.url}`); } else { next(); } });
-
Validate tokens
// Verify token hasn't expired // Check token signature // Validate issuer
-
Implement logout properly
app.get('/logout', (req, res) => { req.logout(); req.session.destroy(); res.redirect('/'); });
-
Rate limit auth endpoints
# Prevent brute force npm install express-rate-limit
-
Log authentication events
// Track login attempts // Monitor suspicious activity // Alert on anomalies
🏆 What You Learned
Fantastic work! You can now:
- ✅ Implement OAuth2 authentication
- ✅ Support multiple providers
- ✅ Handle tokens securely
- ✅ Protect routes properly
- ✅ Debug OAuth2 issues
Your app now has secure social login!
🎯 What’s Next?
Now that you have OAuth2, explore:
- OpenID Connect (OIDC)
- SAML authentication
- Multi-factor authentication
- JWT token management
Keep building secure apps! 🔐