/** * Basic OIDC Authentication Example * * Demonstrates: * - Single OIDC provider configuration * - OIDC Discovery * - JIT (Just-In-Time) user provisioning * - JWT token generation */ import { FlinkApp } from "@flink-app/flink"; import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin"; import { oidcPlugin } from "@flink-app/oidc-plugin"; interface User { _id?: string; email: string; name: string; oidcConnections: Array<{ issuer: string; subject: string; provider: string; }>; createdAt: Date; } async function start() { const app = new FlinkApp({ name: "Basic OIDC Example", // Step 1: Configure JWT Auth Plugin auth: jwtAuthPlugin({ secret: process.env.JWT_SECRET || "your-jwt-secret-min-32-chars-long", getUser: async (tokenData) => { // Fetch user from database using token data return await app.ctx.repos.userRepo.getById(tokenData.userId); }, rolePermissions: { user: ["read", "write"], admin: ["read", "write", "delete"], }, }), db: { uri: process.env.MONGODB_URI || "mongodb://localhost:27017/oidc-example", }, // Step 2: Configure OIDC Plugin plugins: [ oidcPlugin({ providers: { // Provider name (used in URLs: /oidc/acme/initiate) acme: { // OIDC Issuer URL issuer: process.env.OIDC_ISSUER || "https://idp.acme.com", // Client credentials from IdP clientId: process.env.OIDC_CLIENT_ID || "your-client-id", clientSecret: process.env.OIDC_CLIENT_SECRET || "your-client-secret", // Callback URL (must match IdP configuration) callbackUrl: process.env.OIDC_CALLBACK_URL || "http://localhost:3000/oidc/acme/callback", // OIDC Discovery URL (automatic endpoint configuration) discoveryUrl: `${process.env.OIDC_ISSUER || "https://idp.acme.com"}/.well-known/openid-configuration`, // Scopes to request scope: ["openid", "email", "profile"], }, }, // Step 3: Implement JIT user provisioning onAuthSuccess: async ({ profile, claims, provider }, ctx) => { console.log("OIDC authentication successful"); console.log("Provider:", provider); console.log("Profile:", profile); console.log("Claims:", claims); // Find user by OIDC subject + issuer let user = await ctx.repos.userRepo.getOne({ "oidcConnections.subject": claims.sub, "oidcConnections.issuer": claims.iss, }); if (!user) { // JIT provisioning - create new user console.log("Creating new user via JIT provisioning"); user = await ctx.repos.userRepo.create({ email: claims.email, name: claims.name || profile.name || "Unknown User", oidcConnections: [ { issuer: claims.iss, subject: claims.sub, provider, }, ], createdAt: new Date(), }); console.log("User created:", user._id); } else { console.log("Existing user found:", user._id); } // Generate JWT token for the application const token = await ctx.plugins.jwtAuth.createToken( { userId: user._id, email: user.email, }, ["user"] ); console.log("JWT token generated"); return { user, token, redirectUrl: "/dashboard", }; }, // Step 4: Handle authentication errors onAuthError: async ({ error, provider }) => { console.error(`OIDC error for ${provider}:`, error); if (error.code === "access_denied") { return { redirectUrl: "/login?error=user_cancelled", }; } return { redirectUrl: "/login?error=authentication_failed", }; }, }), ], }); await app.start(); console.log("App started on port 3000"); console.log("Visit: http://localhost:3000/oidc/acme/initiate to start OIDC flow"); } start().catch((error) => { console.error("Failed to start app:", error); process.exit(1); });