20 KiB
Authentication SDK Integration
User authentication, registration, and session management via insforge.auth.
Package choice: Use
@insforge/sdkdirectly for authentication flows. Build auth UI components with the SDK methods documented below.
Auth email delivery is built in. Signup verification, password reset, magic links, and invites ship on every plan (free included) through the InsForge platform. For custom transactional email, see email/sdk-integration.md.
Setup
First, ensure your .env file is configured with your InsForge URL and anon key. Get the anon key with npx -y @insforge/cli secrets get ANON_KEY. See the main SKILL.md for framework-specific variable names and full setup steps.
import { createClient } from '@insforge/sdk'
const insforge = createClient({
baseUrl: process.env.NEXT_PUBLIC_INSFORGE_URL, // adjust prefix for your framework
anonKey: process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY // adjust prefix for your framework
})
SSR / Server-Rendered Apps
For Next.js, Remix, SvelteKit, Nuxt server routes, or any other SSR setup, use @insforge/sdk/ssr helpers. See ssr-integration.md for the full pattern and minimal examples.
Sign Up (Complete Flow)
Registration may require email verification. Implement the flow based on backend config.
- Sign up — Create the user account
- If verification is required — Branch on
verifyEmailMethod - Complete the verification flow
code: user enters the 6-digit code and your app callsverifyEmail()link: backend verifies the emailed link first, then redirects to your app viaredirectTo
Important
: For link-based verification, pass
redirectTotosignUp(). Recommended: use your sign-in page asredirectTo, then show a success message and ask the user to sign in with their email and password.
try {
// Step 1: Register the user
const { data, error } = await insforge.auth.signUp({
email: 'user@example.com',
password: 'securepassword123',
name: 'John Doe',
redirectTo: 'http://localhost:3000/sign-in'
})
if (error) throw error
if (data?.requireEmailVerification) {
// Code method:
// - Show a 6-digit code input on the same page
// - Call verifyEmail({ email, otp })
//
// Link method:
// - Show "Check your email"
// - Recommended redirectTo: your sign-in page
// - On redirect success, show a confirmation message and ask the user to sign in
} else if (data?.accessToken) {
// No verification required — user is already signed in
console.log('Signed in:', data.user)
}
} catch (error) {
console.error('Registration flow failed:', error.message)
}
Resend Verification Email
try {
await insforge.auth.resendVerificationEmail({
email: 'user@example.com',
redirectTo: 'http://localhost:3000/sign-in'
})
console.log('Verification email resent.')
} catch (error) {
console.error('Failed to resend:', error.message)
}
Sign In
const { data, error } = await insforge.auth.signInWithPassword({
email: 'user@example.com',
password: 'securepassword123'
})
if (error) {
console.error('Sign in failed:', error.message)
if (error.statusCode === 403) {
console.error('Email not verified. Redirect to verification page.')
}
} else {
console.log('Signed in:', data.user.email)
}
Passwordless Sign In (Email OTP)
Requires
@insforge/sdk1.5.1+.
Two steps: request a code, then verify it. verifyOtp() returns { data: { user, accessToken }, error } and automatically saves the session — the user is signed in on success. When signups are enabled a brand-new email becomes a verified, passwordless user (with disableSignup on, verifyOtp() rejects unknown emails with a 403); name sets the display name only when the account is first created.
// 1. Request the 6-digit code. The response is generic whether or not an
// account exists (prevents account enumeration) — always show the same
// "check your email" state.
const { error: sendError } = await insforge.auth.signInWithOtp({
email: 'user@example.com'
})
if (sendError) console.error('Could not send code:', sendError.message)
// 2. Verify the code the user typed in.
const { data, error } = await insforge.auth.verifyOtp({
email: 'user@example.com',
otp: '123456',
name: 'Ada Lovelace' // optional; applied only when the account is first created
})
if (error) {
console.error('Invalid or expired code:', error.message)
} else {
console.log('Signed in:', data.user.email)
}
The code expires in 5 minutes, is single-use, and is invalidated after 3 failed attempts. A wrong entry can be retried with the same code; once it expires or the three attempts are used up, have the user request a fresh one with
signInWithOtp().
OAuth Sign In
OAuth uses PKCE. The SDK handles code generation, redirect, and token exchange automatically in the browser.
Two redirect URLs
| URL | Points to | Where to configure |
|---|---|---|
| OAuth provider callback | InsForge backend (https://<project>.insforge.app/api/auth/oauth/<provider>/callback) |
Google Console, GitHub OAuth app, etc. |
redirectTo |
Your app (https://yourapp.com/auth/callback) |
Passed in signInWithOAuth() |
redirectTo is where the user lands after auth. The backend appends ?insforge_code=<code> to it. Set it to a page where your app initializes the SDK or handles the server callback.
SPA (browser) — fully automatic
await insforge.auth.signInWithOAuth('google', {
redirectTo: 'http://localhost:3000/dashboard', // any page where SDK is initialized
additionalParams: { prompt: 'select_account' } // optional provider-specific params
})
Use additionalParams only for provider-specific optional hints. Do not pass server-owned OAuth fields such as client_id, scope, redirect_uri, code_challenge, state, or response_type; InsForge sets those server-side and ignores colliding client-provided keys.
The SDK constructor auto-detects insforge_code in the URL, exchanges it for a session, and cleans the URL. Initialize the SDK on the redirectTo page.
SSR (Next.js) — manual exchange required
The browser auto-detection is for SPA flows. In SSR apps, use skipBrowserRedirect: true and exchange the OAuth code in a Route Handler so the refresh token can be written as an httpOnly cookie. See ssr-integration.md for the full implementation.
const { data } = await insforge.auth.signInWithOAuth('google', {
redirectTo: 'https://yourapp.com/api/auth/callback',
skipBrowserRedirect: true
})
// data.codeVerifier — store in httpOnly cookie before redirect
// data.url — redirect user to this
SDK methods reference
| Method | When to use |
|---|---|
signInWithOAuth(provider, { redirectTo, additionalParams? }) |
SPA: auto-redirects and handles everything |
signInWithOAuth(provider, { redirectTo, additionalParams?, skipBrowserRedirect: true }) |
SSR: returns { url, codeVerifier } for manual handling |
exchangeOAuthCode(code, codeVerifier?) |
Exchange insforge_code for session. Auto-called in SPA; call manually in SSR |
Sign Out
const { error } = await insforge.auth.signOut()
Get Current User
const { data, error } = await insforge.auth.getCurrentUser()
if (data.user) {
console.log('User:', data.user.email)
}
For browser apps, call getCurrentUser() during startup. The SDK will use the httpOnly refresh cookie automatically when it can refresh the session.
For SSR apps, use createRefreshAuthRouter() / refreshAuth() from @insforge/sdk/ssr to refresh through your app route.
Cold loads & external redirects
In SPA browser apps using the root @insforge/sdk client, the access token is stored in memory only. On a cold page load, getCurrentUser() starts with no in-memory access token, so the SDK rehydrates the session by calling the backend refresh endpoint with the httpOnly refresh cookie and the JS-readable insforge_csrf_token cookie/header flow. During that network round-trip, user is temporarily null.
In SSR browser clients created with createBrowserClient() from @insforge/sdk/ssr, the access token is read from the insforge_access_token cookie and refreshed through your app's /api/auth/refresh route.
Any auth wrapper or hook should expose both user and loading:
import { createContext, useContext, useEffect, useState } from 'react'
const AuthContext = createContext({ user: null, loading: true })
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
async function hydrateAuth() {
const { data, error } = await insforge.auth.getCurrentUser()
if (cancelled) return
setUser(error ? null : (data?.user ?? null))
setLoading(false)
}
void hydrateAuth()
return () => {
cancelled = true
}
}, [])
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
return useContext(AuthContext)
}
When auth state controls visible UI, gate the logged-in vs logged-out branch on loading:
function Layout() {
const { user, loading } = useAuth()
return (
<header>
{loading ? <div className="auth-skeleton" /> : user ? <AccountMenu /> : <SignInButton />}
</header>
)
}
This matters most when the user lands back in your app after an external redirect:
- Post-OAuth callback
- Stripe Checkout success or cancel URL
- Stripe Customer Portal return URL
- Password-reset link landing
- Email-verification link landing
Gate user-dependent side effects during auth loading
If a mount-time effect branches on the current user, guard the user-dependent work until loading === false. This is especially important for code paths that do one thing for signed-in users and another for guests.
const [shouldRunAction, setShouldRunAction] = useState(false)
const handled = useRef(false)
const userId = user?.id ?? null
useEffect(() => {
const handleStatusChanged = ({ id, status }) => {
if (id === resourceId && status === 'ready') {
setShouldRunAction(true)
}
}
insforge.realtime.on('status_changed', handleStatusChanged)
return () => insforge.realtime.off('status_changed', handleStatusChanged)
}, [resourceId])
useEffect(() => {
if (loading || !shouldRunAction || handled.current) return
async function runUserDependentAction() {
await performUserDependentAction({ userId })
handled.current = true
}
void runUserDependentAction()
}, [loading, shouldRunAction, userId])
Webhook-backed Realtime flows can complete before the cold-load auth refresh finishes, especially after Stripe Checkout, Customer Portal, OAuth, password-reset, or email-verification redirects. If you use a cleared.current or other "first event wins" guard, flip it after loading === false and the user-dependent work has actually succeeded.
Profile Management
In SQL triggers and migrations, user profile metadata lives in
auth.users.profile JSONB. Read common values with NEW.profile->>'name' and
NEW.profile->>'avatar_url'.
// Get any user's public profile
const { data } = await insforge.auth.getProfile('user-id')
// Update current user's profile
const { data } = await insforge.auth.setProfile({
name: 'John',
avatar_url: 'https://...',
custom_field: 'value'
})
Email Verification
verifyEmail() returns { data: { user, accessToken }, error } and automatically saves the session — the user is signed in after successful verification.
// Verify with code (6-digit OTP from email)
const { data, error } = await insforge.auth.verifyEmail({
email: 'user@example.com',
otp: '123456'
})
if (error) {
if (error.statusCode === 400) {
console.error('Invalid or expired code')
}
} else {
// User is now verified AND signed in
console.log('Signed in:', data.user)
}
// Resend verification email
await insforge.auth.resendVerificationEmail({
email: 'user@example.com',
redirectTo: 'http://localhost:3000/sign-in'
})
Link Verification Flow
Use redirectTo for link-based verification. Recommended: use your sign-in page.
Your frontend should handle these redirect query params:
insforge_status:successorerrorinsforge_type: alwaysverify_emailinsforge_error: present only on error
When insforge_status=success, show a confirmation message and ask the user to sign in with their email and password.
Password Reset
// Step 1: Send reset email
await insforge.auth.sendResetPasswordEmail({
email: 'user@example.com',
redirectTo: 'http://localhost:3000/reset-password'
})
// Step 2: Code method — exchange code for token
const { data } = await insforge.auth.exchangeResetPasswordToken({
email: 'user@example.com',
code: '123456'
})
// Step 3: Reset password
await insforge.auth.resetPassword({
newPassword: 'newPassword123',
otp: data.token // or token from magic link
})
Link Reset Flow
Use redirectTo for link-based reset. Recommended: use your app's dedicated reset-password page.
Your frontend should handle these redirect query params:
token: present only when the reset form should be showninsforge_status:readyorerrorinsforge_type: alwaysreset_passwordinsforge_error: present only on error
Only render the reset form when insforge_status=ready and token is present.
Important Notes
- SPA Web vs Mobile: Root browser SDK flows use httpOnly refresh cookies + CSRF; mobile/desktop returns refreshToken in response
- SSR apps should use SDK SSR helpers: For Next.js and similar SSR frameworks, use
@insforge/sdk/ssrfor client creation, refresh routes, and auth cookies; use@insforge/sdk/ssr/middlewarefor Proxy/Middleware session updates. See ssr-integration.md - All methods return
{ data, error }— always check for errors - OAuth uses PKCE flow for security
Best Practices
-
Always check auth config first before implementing
- Run
npx -y @insforge/cli metadata --jsonto get auth config (requireEmailVerification,verifyEmailMethod,resetPasswordMethod,oAuthProviders,allowedRedirectUrls) - This tells you what features to implement
- To change supported project config such as redirect URLs, verification flags, password policy, or auth SMTP settings, use
npx -y @insforge/cli config apply— see the insforge-cli skill's Configuration section. OAuth providers and external app setup are dashboard/provider-managed.
- Run
-
The sign-up page must handle the full registration flow
- After calling
signUp(), ifrequireEmailVerificationis true, branch onverifyEmailMethod - For
"code", switch the UI to show a 6-digit code input on the same page - For
"link", passredirectTotosignUp()and show a "check your email" state - Keep the user in the verification flow until verification is completed
- Recommended verification
redirectTo: your sign-in page verifyEmail()automatically saves the session only for the code flow
- After calling
-
Render OAuth from configured providers
- Check
oAuthProvidersarray in config - The array contains enabled provider names (e.g.,
["google", "github"])
- Check
-
Handle the sign-up response correctly
const { data, error } = await insforge.auth.signUp({...}) if (error) { // Show error message to user } else if (data?.requireEmailVerification) { // Usually: switch UI to show 6-digit code input and keep the user in the verification flow // If verifyEmailMethod === "link", show a "check your email" state instead } else if (data?.accessToken) { // No verification needed — user is signed in, navigate to app } -
Use
@insforge/sdk/ssrfor SSR auth- For Next.js or other SSR frameworks, perform auth mutations where cookies can be written
- Use
createAuthActions()for sign-in, sign-up, sign-out, OAuth initiation/exchange, ID-token sign-in, and email verification flows that create or clear sessions - Keep
insforge_refresh_tokenhttpOnly and server-owned - Let
insforge_access_tokenbe browser-readable so Storage and Realtime can authenticate from Client Components - Use
createServerClient()for Server Components / Route Handlers andcreateBrowserClient()for Client Components; the SSR browser client exposes read-only auth methods only - Add
/api/auth/refreshwithcreateRefreshAuthRouter()and useupdateSession()from@insforge/sdk/ssr/middlewarein Proxy/Middleware - Use ssr-integration.md as the reference implementation
Common Mistakes
| Mistake | Fix |
|---|---|
| Navigating to dashboard/home while verification is still required | Stay in the verification flow and branch on verifyEmailMethod |
| Skipping the email verification step | Check requireEmailVerification in the sign-up response and implement the verification step |
Missing redirectTo for link flows |
Pass the app URL as redirectTo and include it in allowedRedirectUrls |
| Building the wrong verification UI | Build the UI from verifyEmailMethod |
| Treating link verification like code verification | Handle the redirect result and send the user to sign in |
Signing in again after code-based verifyEmail() |
Use the session returned by verifyEmail() |
| Hardcoding OAuth providers | Render providers from oAuthProviders |
| Handling SSR auth like a browser-only flow | Use createAuthActions() for auth mutations, createBrowserClient() only to consume an existing SSR session, and @insforge/sdk/ssr/middleware for Proxy/Middleware session updates |
Passing apiKey to createClient() |
Use createAdminClient({ apiKey }) in trusted server-only code |
Conditional Implementation Guide
Email Verification Flow
// After sign-up, check if verification is needed
if (data?.requireEmailVerification) {
// If verifyEmailMethod === "code":
// Show 6-digit code input on the SAME page, then call:
const { data: verifyData, error } = await insforge.auth.verifyEmail({ email, otp: userEnteredCode })
// On success, user is automatically signed in — navigate to the app
// If verifyEmailMethod === "link":
// Pass redirectTo to signUp() / resendVerificationEmail()
// Show "Check your email and click the verification link" message
// Recommended redirectTo: your sign-in page
// On redirect success, show a confirmation message and ask the user to sign in
}
OAuth Implementation
// oAuthProviders is already an array of enabled provider names
// e.g., ["google", "github"]
const enabledProviders = authConfig.oAuthProviders
// Show OAuth buttons from enabled providers:
if (enabledProviders.includes('google')) {
// Show Google login button
}
if (enabledProviders.includes('github')) {
// Show GitHub login button
}
Recommended Workflow
1. Get auth config → npx -y @insforge/cli metadata --json
2. Check what's enabled → Email verification? Which OAuth providers?
3. Build appropriate UI → Code input vs magic link, OAuth buttons
4. Implement sign-up → Handle requireEmailVerification response
5. Implement verification → Code input or redirectTo-based link flow
6. Implement OAuth → Use providers from oAuthProviders
7. Implement password reset → Based on resetPasswordMethod (code vs link)
Implementation Checklist
Based on auth config, implement:
- Sign up form with password (respecting
passwordMinLength) - Email verification step on the sign-up page (if
requireEmailVerificationis true)- 6-digit code input (if
verifyEmailMethodis "code") - "Check your email" state plus sign-in-page
redirectTohandling (ifverifyEmailMethodis "link")
- 6-digit code input (if
- Sign in form
- OAuth buttons from enabled providers
- Password reset flow
- Code input (if
resetPasswordMethodis "code") - App reset page using
redirectTo(ifresetPasswordMethodis "link")
- Code input (if
- Sign out