/** * API Client Integration with response_type=json * * This example demonstrates: * - Using OAuth plugin with mobile apps and SPAs * - response_type=json for JSON responses instead of redirects * - Handling OAuth flow in API clients * - Storing and using JWT tokens in mobile/SPA context */ 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"; interface User { _id?: string; email: string; name?: string; avatarUrl?: string; oauthProviders: Array<{ provider: "github" | "google"; providerId: string; }>; roles: string[]; createdAt: Date; updatedAt: Date; } class UserRepo extends FlinkRepo { async findByEmail(email: string) { return this.getOne({ email }); } } interface AppContext extends FlinkContext { repos: { userRepo: UserRepo; }; plugins: { jwtAuth: any; oauth: any; }; } async function start() { const app = new FlinkApp({ name: "OAuth API Client Example", port: 3336, db: { uri: process.env.MONGODB_URI || "mongodb://localhost:27017/oauth-api-client-example", }, auth: jwtAuthPlugin({ secret: process.env.JWT_SECRET || "your-super-secret-jwt-key", 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, }; }, rolePermissions: { user: ["read:own", "write:own"], }, expiresIn: "30d", // Longer expiration for mobile apps }), plugins: [ oauthPlugin({ providers: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, callbackUrl: process.env.GITHUB_CALLBACK_URL || "http://localhost:3336/oauth/github/callback", }, google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, callbackUrl: process.env.GOOGLE_CALLBACK_URL || "http://localhost:3336/oauth/google/callback", }, }, storeTokens: false, onAuthSuccess: async ({ profile, provider }, ctx) => { console.log(`\nAPI Client OAuth Success: ${provider} - ${profile.email}`); let user = await ctx.repos.userRepo.findByEmail(profile.email); if (!user) { user = await ctx.repos.userRepo.create({ email: profile.email, name: profile.name, avatarUrl: profile.avatarUrl, oauthProviders: [{ provider, providerId: profile.id }], roles: ["user"], createdAt: new Date(), updatedAt: new Date(), }); } const token = await ctx.plugins.jwtAuth.createToken({ userId: user._id, email: user.email }, user.roles); console.log(`JWT token generated for API client`); console.log(`Token will be returned as JSON (not redirect)`); return { user: { _id: user._id, email: user.email, name: user.name, avatarUrl: user.avatarUrl, roles: user.roles, }, token, // redirectUrl is ignored when response_type=json redirectUrl: "/dashboard", }; }, onAuthError: async ({ error, provider }) => { console.error(`OAuth error for ${provider}:`, error); return { redirectUrl: `/login?error=${error.code}`, }; }, }), ], // IMPORTANT: Configure CORS for API clients cors: { origin: [ "http://localhost:3000", // React/Vue dev server "http://localhost:19006", // Expo dev server "capacitor://localhost", // Capacitor iOS/Android "ionic://localhost", // Ionic /\.myapp\.com$/, // Production domains ], credentials: true, allowedHeaders: ["Authorization", "Content-Type"], }, }); await app.start(); console.log("\n==========================================="); console.log("OAuth API Client Integration Example"); console.log("==========================================="); console.log(`Server: http://localhost:3336`); console.log(`\nKey Feature: response_type=json`); console.log(` Add ?response_type=json to callback URL for JSON response`); console.log(` Perfect for mobile apps, SPAs, and API clients`); console.log(`\nExample URLs:`); console.log(` Initiate: http://localhost:3336/oauth/github/initiate`); console.log(` Callback: http://localhost:3336/oauth/github/callback?code=xxx&state=yyy&response_type=json`); console.log("===========================================\n"); } start().catch((error) => { console.error("Failed to start application:", error); process.exit(1); }); /** * CLIENT INTEGRATION EXAMPLES */ /** * Example 1: React SPA with Popup Window */ const ReactSPAExample = ` import React, { useState } from 'react'; function OAuthLogin() { const [loading, setLoading] = useState(false); const handleGitHubLogin = async () => { setLoading(true); // Open OAuth flow in popup window const width = 600; const height = 700; const left = window.screen.width / 2 - width / 2; const top = window.screen.height / 2 - height / 2; const popup = window.open( 'http://localhost:3336/oauth/github/initiate', 'oauth_popup', \`width=\${width},height=\${height},left=\${left},top=\${top}\` ); // Listen for message from popup window.addEventListener('message', async (event) => { if (event.origin !== 'http://localhost:3336') return; const { code, state } = event.data; // Exchange code for token using response_type=json const response = await fetch( \`http://localhost:3336/oauth/github/callback?code=\${code}&state=\${state}&response_type=json\`, { credentials: 'include' } ); const data = await response.json(); if (data.token) { // Store JWT token localStorage.setItem('jwt_token', data.token); // Store user data localStorage.setItem('user', JSON.stringify(data.user)); // Close popup popup?.close(); // Redirect to dashboard window.location.href = '/dashboard'; } setLoading(false); }); }; return (
); } `; /** * Example 2: React Native with expo-auth-session */ const ReactNativeExample = ` import React from 'react'; import { Button, View, Text } from 'react-native'; import * as WebBrowser from 'expo-web-browser'; import * as AuthSession from 'expo-auth-session'; import AsyncStorage from '@react-native-async-storage/async-storage'; // Tell expo-auth-session to finish the session WebBrowser.maybeCompleteAuthSession(); function OAuthLogin() { const [user, setUser] = React.useState(null); // Configure OAuth discovery const discovery = { authorizationEndpoint: 'http://localhost:3336/oauth/github/initiate', }; // Create auth request const [request, response, promptAsync] = AuthSession.useAuthRequest( { clientId: 'not-used-for-our-flow', redirectUri: AuthSession.makeRedirectUri({ native: 'myapp://oauth/callback', }), }, discovery ); React.useEffect(() => { if (response?.type === 'success') { const { code, state } = response.params; // Exchange code for token with response_type=json fetch( \`http://localhost:3336/oauth/github/callback?code=\${code}&state=\${state}&response_type=json\` ) .then(res => res.json()) .then(async (data) => { // Store JWT token securely await AsyncStorage.setItem('jwt_token', data.token); // Store user data await AsyncStorage.setItem('user', JSON.stringify(data.user)); setUser(data.user); }) .catch(console.error); } }, [response]); const handleLogin = () => { promptAsync(); }; if (user) { return ( Welcome, {user.name}! Email: {user.email} ); } return (