/** * Multiple OIDC Providers Example * * Demonstrates: * - Multiple OIDC providers (Azure AD, Okta) * - Provider-specific configuration * - User linking to multiple IdPs */ import { FlinkApp } from "@flink-app/flink"; import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin"; import { oidcPlugin } from "@flink-app/oidc-plugin"; async function start() { const app = new FlinkApp({ name: "Multi-Provider OIDC Example", auth: jwtAuthPlugin({ secret: process.env.JWT_SECRET!, getUser: async (tokenData) => { return await app.ctx.repos.userRepo.getById(tokenData.userId); }, rolePermissions: { user: ["read", "write"], admin: ["read", "write", "delete"], }, }), db: { uri: process.env.MONGODB_URI!, }, plugins: [ oidcPlugin({ providers: { // Azure AD / Entra ID azure: { issuer: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`, clientId: process.env.AZURE_CLIENT_ID!, clientSecret: process.env.AZURE_CLIENT_SECRET!, callbackUrl: "https://myapp.com/oidc/azure/callback", discoveryUrl: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0/.well-known/openid-configuration`, scope: ["openid", "email", "profile"], }, // Okta okta: { issuer: `https://${process.env.OKTA_DOMAIN}`, clientId: process.env.OKTA_CLIENT_ID!, clientSecret: process.env.OKTA_CLIENT_SECRET!, callbackUrl: "https://myapp.com/oidc/okta/callback", discoveryUrl: `https://${process.env.OKTA_DOMAIN}/.well-known/openid-configuration`, scope: ["openid", "email", "profile"], }, // Auth0 auth0: { issuer: `https://${process.env.AUTH0_DOMAIN}`, clientId: process.env.AUTH0_CLIENT_ID!, clientSecret: process.env.AUTH0_CLIENT_SECRET!, callbackUrl: "https://myapp.com/oidc/auth0/callback", discoveryUrl: `https://${process.env.AUTH0_DOMAIN}/.well-known/openid-configuration`, scope: ["openid", "email", "profile"], }, }, onAuthSuccess: async ({ profile, claims, provider }, ctx) => { // Try to find existing user by OIDC connection let user = await ctx.repos.userRepo.getOne({ "oidcConnections.subject": claims.sub, "oidcConnections.issuer": claims.iss, }); if (!user) { // Try to find by email (link existing account) user = await ctx.repos.userRepo.getOne({ email: claims.email }); if (user) { // Link new provider to existing user console.log(`Linking ${provider} to existing user ${user._id}`); user.oidcConnections = user.oidcConnections || []; user.oidcConnections.push({ issuer: claims.iss, subject: claims.sub, provider, }); await ctx.repos.userRepo.updateOne(user._id, { oidcConnections: user.oidcConnections, }); } else { // Create new user console.log(`Creating new user via ${provider}`); 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(), }); } } const token = await ctx.plugins.jwtAuth.createToken( { userId: user._id, email: user.email, }, ["user"] ); return { user, token, redirectUrl: "/dashboard", }; }, onAuthError: async ({ error, provider }) => { console.error(`OIDC error for ${provider}:`, error); return { redirectUrl: `/login?error=${error.code}&provider=${provider}`, }; }, }), ], }); await app.start(); console.log("App started with multiple OIDC providers:"); console.log("- Azure AD: /oidc/azure/initiate"); console.log("- Okta: /oidc/okta/initiate"); console.log("- Auth0: /oidc/auth0/initiate"); } start().catch((error) => { console.error("Failed to start app:", error); process.exit(1); });