Authentication Guide
Comprehensive guide to implementing authentication with GreenMonkey's API and OAuth providers.
Authentication Methods
GreenMonkey supports multiple authentication methods:
- API Key Authentication - For server-to-server communication
- OAuth 2.0 - For user authentication via GitHub/Google
- Session Tokens - For web application sessions
- Magic Links - Passwordless email authentication
API Key Authentication
Obtaining API Keys
- Sign in to GreenMonkey
- Navigate to Dashboard ā Settings ā API
- Click "Generate API Key"
- Set permissions and name
- Copy key immediately (shown once!)
Using API Keys
Header Authentication (Recommended)
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.greenmonkey.dev/v1/products
Query Parameter (Not Recommended)
curl https://api.greenmonkey.dev/v1/products?api_key=YOUR_API_KEY
ā ļø Warning: Query parameters may be logged. Use headers instead.
API Key Security
// Good: Environment variable
const apiKey = process.env.GREENMONKEY_API_KEY;
// Bad: Hardcoded
const apiKey = 'gm_live_abc123...'; // Never do this!
// Good: Secure storage
import { SecretManager } from '@google-cloud/secret-manager';
const apiKey = await secretManager.getSecret('greenmonkey-api-key');
API Key Permissions
Configure granular permissions:
{
"permissions": {
"products:read": true,
"products:write": true,
"purchases:read": true,
"purchases:write": false,
"analytics:read": true,
"ai:execute": true
}
}
OAuth 2.0 Authentication
OAuth Flow Overview
sequenceDiagram
participant User
participant YourApp
participant GreenMonkey
participant Provider
User->>YourApp: Click "Sign in with GitHub"
YourApp->>GreenMonkey: Redirect to /api/auth/github
GreenMonkey->>Provider: OAuth authorization
Provider->>User: Consent screen
User->>Provider: Approve
Provider->>GreenMonkey: Authorization code
GreenMonkey->>Provider: Exchange for tokens
GreenMonkey->>YourApp: Redirect with session
YourApp->>User: Authenticated!
Implementing OAuth
GitHub OAuth
-
Register Your App
https://github.com/settings/applications/new Application name: Your App Name Homepage URL: https://yourapp.com Authorization callback URL: https://yourapp.com/api/auth/callback/github
-
Configure Environment
GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret
-
Implement Flow
// Using NextAuth.js import NextAuth from 'next-auth'; import GithubProvider from 'next-auth/providers/github'; export default NextAuth({ providers: [ GithubProvider({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET, }), ], callbacks: { async signIn({ user, account }) { // Custom logic after sign in return true; }, }, });
Google OAuth
-
Create OAuth Client
https://console.cloud.google.com/apis/credentials Application type: Web application Authorized JavaScript origins: https://yourapp.com Authorized redirect URIs: https://yourapp.com/api/auth/callback/google
-
Configure Scopes
GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, authorization: { params: { scope: 'openid email profile', }, }, });
OAuth Best Practices
- State Parameter - Always use for CSRF protection
- PKCE - Implement for public clients
- Scope Limitation - Request minimal scopes
- Token Storage - Never store in localStorage
Session Management
Session Token Format
GreenMonkey uses JWT tokens for sessions:
// Decoded JWT payload
{
"sub": "user_123",
"email": "user@example.com",
"name": "John Doe",
"iat": 1616239022,
"exp": 1616842822
}
Working with Sessions
Client-Side
import { useSession } from 'next-auth/react';
function MyComponent() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <p>Loading...</p>;
}
if (status === 'unauthenticated') {
return <p>Please sign in</p>;
}
return <p>Welcome, {session.user.name}!</p>;
}
Server-Side
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function GET(request) {
const session = await getServerSession(authOptions);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
// Authenticated logic here
return new Response(JSON.stringify({ user: session.user }));
}
Session Security
// Configure secure sessions
export const authOptions = {
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: `__Secure-gm.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
},
},
},
};
Magic Link Authentication
Implementation
// Send magic link
async function sendMagicLink(email) {
const response = await fetch('/api/auth/magic-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (response.ok) {
return { success: true, message: 'Check your email!' };
}
}
// Verify magic link token
async function verifyMagicLink(token) {
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
if (response.ok) {
const { sessionToken } = await response.json();
// Store session token securely
}
}
Magic Link Security
- Tokens expire in 10 minutes
- Single use only
- Rate limited per email
- Secure random generation
Multi-Factor Authentication (2FA)
Enabling 2FA
// Generate 2FA secret
import { authenticator } from 'otplib';
const secret = authenticator.generateSecret();
const qrCode = authenticator.keyuri(user.email, 'GreenMonkey', secret);
// Verify TOTP code
const isValid = authenticator.verify({
token: userInput,
secret: user.totpSecret,
});
2FA Flow
// During login
async function loginWith2FA(email, password, totpCode) {
// First, verify credentials
const user = await verifyCredentials(email, password);
if (user.has2FA) {
// Then verify TOTP
const validTOTP = await verify2FA(user.id, totpCode);
if (!validTOTP) {
throw new Error('Invalid 2FA code');
}
}
// Create session
return createSession(user);
}
Authorization
Role-Based Access Control (RBAC)
// Define roles
const roles = {
user: ['read:own_profile', 'create:products'],
seller: ['read:analytics', 'manage:products'],
admin: ['manage:all', 'view:admin_panel'],
};
// Check permissions
function hasPermission(user, permission) {
const userRoles = user.roles || ['user'];
return userRoles.some((role) => roles[role]?.includes(permission));
}
// Protect routes
function requirePermission(permission) {
return async (req, res, next) => {
const session = await getSession({ req });
if (!session || !hasPermission(session.user, permission)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
Resource-Based Authorization
// Check resource ownership
async function canAccessProduct(userId, productId) {
const product = await db.product.findUnique({
where: { id: productId },
});
return product?.authorId === userId;
}
// Middleware
async function authorizeProductAccess(req, res, next) {
const session = await getSession({ req });
const { productId } = req.params;
if (!(await canAccessProduct(session.user.id, productId))) {
return res.status(403).json({ error: 'Access denied' });
}
next();
}
Security Best Practices
Token Storage
// Good: HttpOnly cookies
res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/`);
// Good: Secure session storage
sessionStorage.setItem('tempToken', token); // Cleared on tab close
// Bad: localStorage (XSS vulnerable)
localStorage.setItem('token', token); // Avoid this!
CSRF Protection
// Generate CSRF token
import { randomBytes } from 'crypto';
function generateCSRFToken() {
return randomBytes(32).toString('hex');
}
// Verify CSRF token
function verifyCSRFToken(req) {
const token = req.headers['x-csrf-token'];
const sessionToken = req.session.csrfToken;
return token === sessionToken;
}
Rate Limiting
import rateLimit from 'express-rate-limit';
// Auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests
message: 'Too many login attempts',
});
app.post('/api/auth/login', authLimiter, loginHandler);
Testing Authentication
Unit Tests
import { describe, it, expect } from 'vitest';
import { verifyApiKey, generateToken } from '@/lib/auth';
describe('Authentication', () => {
it('should verify valid API key', async () => {
const key = 'gm_test_valid_key';
const result = await verifyApiKey(key);
expect(result.valid).toBe(true);
});
it('should reject invalid API key', async () => {
const key = 'invalid_key';
const result = await verifyApiKey(key);
expect(result.valid).toBe(false);
});
});
Integration Tests
import { test, expect } from '@playwright/test';
test('OAuth login flow', async ({ page }) => {
await page.goto('/login');
await page.click('button:has-text("Sign in with GitHub")');
// Handle OAuth provider page
await page.fill('input[name="login"]', process.env.TEST_GITHUB_USER);
await page.fill('input[name="password"]', process.env.TEST_GITHUB_PASS);
await page.click('input[type="submit"]');
// Should redirect back authenticated
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('text=Welcome')).toBeVisible();
});
Troubleshooting
Common Issues
"Invalid API Key"
- Check for typos or extra spaces
- Verify key hasn't expired
- Ensure correct environment (test vs production)
"Session Expired"
- Implement refresh token logic
- Extend session duration if needed
- Handle gracefully in UI
"OAuth Callback Error"
- Verify redirect URI matches exactly
- Check OAuth app settings
- Ensure secrets are correct
Debug Mode
// Enable auth debugging
export const authOptions = {
debug: process.env.NODE_ENV === 'development',
logger: {
error: (code, metadata) => {
console.error('Auth error:', code, metadata);
},
warn: (code) => {
console.warn('Auth warning:', code);
},
},
};
Migration Guide
From v4 to v5 (NextAuth)
// Old (v4)
import { getSession } from 'next-auth/react';
const session = await getSession({ req });
// New (v5)
import { auth } from '@/lib/auth';
const session = await auth();
Custom Authentication
If migrating from custom auth:
- Map user schema to NextAuth schema
- Migrate session handling
- Update API endpoints
- Test thoroughly
Next Steps
- Implement webhooks for real-time events
- Build with SDKs for faster integration
- Explore API endpoints for full capabilities