← Files InsForgeARCHIVED FILE
skills/insforge/auth/sdk-integration.md
18.7 KB · Oct 4, 2026 · 12:28 UTC
# Authentication SDK Integration
User authentication, registration, and session management via `insforge.auth`.
> **Package choice**: Use `@insforge/sdk` directly 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](../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 @insforge/cli secrets get ANON_KEY`. See the main [SKILL.md](../SKILL.md) for framework-specific variable names and full setup steps.
```javascript
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](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.
1. **Sign up** — Create the user account
2. **If verification is required** — Branch on `verifyEmailMethod`
3. **Complete the verification flow**
- `code`: user enters the 6-digit code and your app calls `verifyEmail()`
- `link`: backend verifies the emailed link first, then redirects to your app via `redirectTo`
> **Important**: For link-based verification, pass `redirectTo` to `signUp()`. Recommended: use your sign-in page as `redirectTo`, then show a success message and ask the user to sign in with their email and password.
```javascript
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
```javascript
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
```javascript
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)
}
```
## 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
```javascript
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](ssr-integration.md) for the full implementation.
```javascript
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
```javascript
const { error } = await insforge.auth.signOut()
```
## Get Current User
```javascript
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`:
```tsx
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`:
```tsx
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.
```tsx
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'`.
```javascript
// 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.
```javascript
// 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`: `success` or `error`
- `insforge_type`: always `verify_email`
- `insforge_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
```javascript
// 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 shown
- `insforge_status`: `ready` or `error`
- `insforge_type`: always `reset_password`
- `insforge_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/ssr` for client creation, refresh routes, and auth cookies; use `@insforge/sdk/ssr/middleware` for Proxy/Middleware session updates. See [ssr-integration.md](ssr-integration.md)
- All methods return `{ data, error }` — always check for errors
- OAuth uses PKCE flow for security
---
## Best Practices
1. **Always check auth config first** before implementing
- Run `npx @insforge/cli metadata --json` to 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 @insforge/cli config apply` — see the **insforge-cli** skill's Configuration section. OAuth providers and external app setup are dashboard/provider-managed.
2. **The sign-up page must handle the full registration flow**
- After calling `signUp()`, if `requireEmailVerification` is true, branch on `verifyEmailMethod`
- For `"code"`, switch the UI to show a 6-digit code input on the **same page**
- For `"link"`, pass `redirectTo` to `signUp()` 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
3. **Render OAuth from configured providers**
- Check `oAuthProviders` array in config
- The array contains enabled provider names (e.g., `["google", "github"]`)
4. **Handle the sign-up response correctly**
```javascript
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
}
```
5. **Use `@insforge/sdk/ssr` for 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_token` httpOnly and server-owned
- Let `insforge_access_token` be browser-readable so Storage and Realtime can authenticate from Client Components
- Use `createServerClient()` for Server Components / Route Handlers and `createBrowserClient()` for Client Components; the SSR browser client exposes read-only auth methods only
- Add `/api/auth/refresh` with `createRefreshAuthRouter()` and use `updateSession()` from `@insforge/sdk/ssr/middleware` in Proxy/Middleware
- Use [ssr-integration.md](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
```javascript
// 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
```javascript
// 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 @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 `requireEmailVerification` is true)
- [ ] 6-digit code input (if `verifyEmailMethod` is "code")
- [ ] "Check your email" state plus sign-in-page `redirectTo` handling (if `verifyEmailMethod` is "link")
- [ ] Sign in form
- [ ] OAuth buttons from enabled providers
- [ ] Password reset flow
- [ ] Code input (if `resetPasswordMethod` is "code")
- [ ] App reset page using `redirectTo` (if `resetPasswordMethod` is "link")
- [ ] Sign out
SHA-256: 33e8f02dacc401bdb134a9ae7b3cedac00291f71bafa36df964c65ede5550e72