/** * Basic OAuth Authentication with JWT Example * * This example demonstrates: * - Setting up JWT Auth Plugin * - Configuring OAuth Plugin for GitHub and Google * - Creating or finding users based on OAuth profile * - Generating JWT tokens after successful OAuth authentication * - Complete OAuth -> User Creation -> JWT Token flow */ import { FlinkApp } from "@flink-app/flink"; import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin"; import { oauthPlugin } from "@flink-app/oauth-plugin"; import { FlinkContext, FlinkRepo } from "@flink-app/flink"; // Define User schema interface User { _id?: string; email: string; name?: string; avatarUrl?: string; oauthProviders: Array<{ provider: "github" | "google"; providerId: string; }>; roles: string[]; createdAt: Date; updatedAt: Date; } // User repository class UserRepo extends FlinkRepo { async findByEmail(email: string) { return this.getOne({ email }); } async findByOAuthProvider(provider: string, providerId: string) { return this.getOne({ "oauthProviders.provider": provider, "oauthProviders.providerId": providerId, }); } } // Application context interface AppContext extends FlinkContext { repos: { userRepo: UserRepo; }; plugins: { jwtAuth: any; oauth: any; }; } async function start() { const app = new FlinkApp({ name: "OAuth Basic Example", port: 3333, db: { uri: process.env.MONGODB_URI || "mongodb://localhost:27017/oauth-example", }, // Step 1: Configure JWT Auth Plugin (REQUIRED) // This MUST be set up before OAuth plugin auth: jwtAuthPlugin({ secret: process.env.JWT_SECRET || "your-super-secret-jwt-key", // Function to retrieve user data for token validation getUser: async (tokenData) => { const user = await app.ctx.repos.userRepo.getById(tokenData.userId); if (!user) { throw new Error("User not found"); } return { id: user._id, email: user.email, roles: user.roles, }; }, // Define role permissions rolePermissions: { user: ["read:own", "write:own"], admin: ["read:all", "write:all", "delete:all"], }, // Token expiration expiresIn: "7d", // 7 days }), plugins: [ // Step 2: Configure OAuth Plugin oauthPlugin({ // Configure OAuth providers providers: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, callbackUrl: process.env.GITHUB_CALLBACK_URL || "http://localhost:3333/oauth/github/callback", // Optional: override default scopes // scope: ["user:email", "read:user"] }, google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, callbackUrl: process.env.GOOGLE_CALLBACK_URL || "http://localhost:3333/oauth/google/callback", // Optional: override default scopes // scope: ["openid", "email", "profile"] }, }, // Don't store OAuth tokens (auth-only mode) storeTokens: false, // Session TTL (10 minutes default) sessionTTL: 600, // Step 3: Handle successful OAuth authentication onAuthSuccess: async ({ profile, provider }, ctx) => { console.log(`OAuth success for ${provider}:`, profile.email); // Try to find existing user by email let user = await ctx.repos.userRepo.findByEmail(profile.email); if (user) { // User exists - check if this provider is already linked const isLinked = user.oauthProviders.some( (p) => p.provider === provider && p.providerId === profile.id ); if (!isLinked) { // Link new provider to existing user console.log(`Linking ${provider} to existing user:`, user.email); user = await ctx.repos.userRepo.updateOne(user._id!, { oauthProviders: [ ...user.oauthProviders, { provider, providerId: profile.id }, ], avatarUrl: profile.avatarUrl || user.avatarUrl, updatedAt: new Date(), }); } } else { // New user - create account console.log(`Creating new user from ${provider}:`, profile.email); user = await ctx.repos.userRepo.create({ email: profile.email, name: profile.name, avatarUrl: profile.avatarUrl, oauthProviders: [{ provider, providerId: profile.id }], roles: ["user"], // Default role createdAt: new Date(), updatedAt: new Date(), }); } // Step 4: Generate JWT token using JWT Auth Plugin // This is the token the client will use for authenticated requests const token = await ctx.plugins.jwtAuth.createToken( { userId: user._id, email: user.email, }, user.roles ); console.log(`JWT token generated for user:`, user.email); // Step 5: Return user object and JWT token // The OAuth plugin will return this token to the client return { user: { _id: user._id, email: user.email, name: user.name, avatarUrl: user.avatarUrl, roles: user.roles, }, token, // JWT token for client authentication redirectUrl: process.env.OAUTH_SUCCESS_REDIRECT || "/dashboard", }; }, // Step 6: Handle OAuth errors (optional) onAuthError: async ({ error, provider }) => { console.error(`OAuth error for ${provider}:`, error); // Map error codes to user-friendly messages const errorMessages: Record = { access_denied: "You denied access to the application", invalid_state: "Session expired, please try again", invalid_grant: "Authorization code expired, please try again", network_error: "Unable to connect to authentication service", }; const message = errorMessages[error.code] || "Authentication failed"; return { redirectUrl: `/login?error=${encodeURIComponent(message)}`, }; }, }), ], // Optional: CORS configuration for OAuth endpoints cors: { origin: process.env.CORS_ORIGIN || "http://localhost:3000", credentials: true, }, }); await app.start(); console.log("\n==========================================="); console.log("OAuth Basic Example Server Running"); console.log("==========================================="); console.log(`Server: http://localhost:3333`); console.log(`\nOAuth Endpoints:`); console.log(` GitHub: http://localhost:3333/oauth/github/initiate`); console.log(` Google: http://localhost:3333/oauth/google/initiate`); console.log(`\nEnvironment Variables Required:`); console.log(` GITHUB_CLIENT_ID`); console.log(` GITHUB_CLIENT_SECRET`); console.log(` GOOGLE_CLIENT_ID`); console.log(` GOOGLE_CLIENT_SECRET`); console.log(` JWT_SECRET`); console.log("===========================================\n"); } start().catch((error) => { console.error("Failed to start application:", error); process.exit(1); }); /** * Usage: * * 1. Set environment variables: * export GITHUB_CLIENT_ID="your_github_client_id" * export GITHUB_CLIENT_SECRET="your_github_client_secret" * export GOOGLE_CLIENT_ID="your_google_client_id" * export GOOGLE_CLIENT_SECRET="your_google_client_secret" * export JWT_SECRET="your_jwt_secret" * export MONGODB_URI="mongodb://localhost:27017/oauth-example" * * 2. Run the application: * npx ts-node examples/basic-auth.ts * * 3. Open browser to: * http://localhost:3333/oauth/github/initiate * or * http://localhost:3333/oauth/google/initiate * * 4. After OAuth authorization, you'll be redirected to: * /dashboard?token=eyJhbGc... * * 5. Client should extract token from URL and store it: * const urlParams = new URLSearchParams(window.location.search); * const token = urlParams.get("token"); * localStorage.setItem("jwt_token", token); * * 6. Use JWT token for authenticated requests: * fetch("/api/protected", { * headers: { * "Authorization": `Bearer ${token}` * } * }) * * OAuth Flow Summary: * 1. User visits /oauth/github/initiate * 2. OAuth plugin redirects to GitHub authorization page * 3. User authorizes app * 4. GitHub redirects to /oauth/github/callback with code * 5. OAuth plugin exchanges code for access token * 6. OAuth plugin fetches user profile from GitHub * 7. onAuthSuccess callback creates/finds user * 8. onAuthSuccess generates JWT token via ctx.plugins.jwtAuth.createToken() * 9. OAuth plugin redirects to /dashboard?token=JWT_TOKEN * 10. Client stores JWT token and uses it for authenticated requests */