Embedded Sign-In (Clerk-style)
Render a complete sign-in form inline on your page. No redirects, no popups. The form calls the ZewstID API directly and returns an authorization code for server-side token exchange.
SDK Version: Requires
v0.9.0+@zewstid/nextjs
When to Use Embedded Sign-In
| Use Case | Recommended |
|---|---|
| Clerk-style inline sign-in on your marketing page | Embedded |
| Full control over auth UX positioning and styling | Embedded |
| Simple login page with ZewstID branding | Redirect |
| Need popup blocker immunity | Redirect |
Quick Start
1. Add the EmbeddedSignIn Component
'use client'; import { EmbeddedSignIn } from '@zewstid/nextjs'; export default function LoginPage() { return ( <div style={{ maxWidth: 400, margin: '100px auto' }}> <EmbeddedSignIn clientId={process.env.NEXT_PUBLIC_ZEWSTID_CLIENT_ID!} onSuccess={({ code, email }) => { // Exchange auth code for tokens server-side fetch('/api/auth/exchange', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }), }).then(() => { window.location.href = '/dashboard'; }); }} /> </div> ); }
2. Create the Token Exchange Endpoint
// app/api/auth/exchange/route.ts import { NextRequest, NextResponse } from 'next/server'; export async function POST(req: NextRequest) { const { code } = await req.json(); // Exchange auth code for tokens via ZewstID API const response = await fetch('https://api.zewstid.com/api/v1/embedded/auth/code/exchange', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code, client_id: process.env.ZEWSTID_CLIENT_ID, client_secret: process.env.ZEWSTID_CLIENT_SECRET, }), }); const tokens = await response.json(); // Set tokens in httpOnly cookies or session // ... return NextResponse.json({ success: true }); }
How It Works
1. User enters email in EmbeddedSignIn form 2. Component calls POST /api/v1/embedded/auth/check-email 3. API returns available auth methods for that email 4. User selects method (password, OTP, or magic link) 5. User authenticates via the selected method 6. API returns an authorization code (valid 10 minutes) 7. onSuccess({ code, email }) fires in your app 8. Your server exchanges the code for tokens
Multi-Step Flow
The embedded form follows a multi-step flow:
- Email Entry - User enters their email address
- Method Selection - Shows available methods (password, OTP, magic link)
- Authentication - User completes the selected method
- Success - Authorization code returned to your app
If only one method is available, the method selection step is skipped.
API Reference
<EmbeddedSignIn>
<EmbeddedSignIn>| Prop | Type | Default | Description |
|---|---|---|---|
clientId | string | (required) | Your ZewstID client ID |
apiUrl | string | "https://api.zewstid.com" | ZewstID API URL |
methods | EmbeddedAuthMethod[] | ['password', 'otp', 'magic-link'] | Allowed auth methods |
onSuccess | (result: { code: string; email: string }) => void | - | Called on successful auth |
onError | (error: string) => void | - | Called on auth failure |
codeChallenge | string | - | PKCE code challenge |
codeChallengeMethod | string | - | PKCE method (S256 |
showSocialLogins | boolean | false | Show social login buttons |
socialProviders | SocialProvider[] | All providers | Which social providers to show |
authUrl | string | "https://auth.zewstid.com" | Auth URL for social popup |
appearance | EmbeddedSignInAppearance | - | Custom appearance |
labels | EmbeddedSignInLabels | - | Custom labels |
className | string | - | CSS class |
style | CSSProperties | - | Inline styles |
<UserButton>
<UserButton>Clerk-style user avatar with dropdown menu.
import { UserButton } from '@zewstid/nextjs'; function Header() { return ( <nav> <UserButton afterSignOutUrl="/" userProfileUrl="https://account.zewstid.com" /> </nav> ); }
| Prop | Type | Default | Description |
|---|---|---|---|
afterSignOutUrl | string | "/" | Redirect URL after sign out |
userProfileUrl | string | "https://account.zewstid.com" | "Manage Account" link |
appearance | UserButtonAppearance | - | Custom appearance |
labels | UserButtonLabels | - | Custom labels |
children | ReactNode | - | Custom menu items |
className | string | - | CSS class |
Customization
Appearance
<EmbeddedSignIn clientId="my-app" appearance={{ primaryColor: '#6366f1', backgroundColor: '#fafafa', textColor: '#1a1a2e', borderRadius: '16px', logo: '/logo.svg', logoHeight: '40px', boxShadow: '0 4px 24px rgba(0,0,0,0.08)', fontFamily: '"Inter", sans-serif', }} onSuccess={handleSuccess} />
Labels
<EmbeddedSignIn clientId="my-app" labels={{ title: 'Welcome to MyApp', subtitle: 'Sign in to continue', emailPlaceholder: '[email protected]', continueButton: 'Next', signInButton: 'Log in', orDivider: 'or continue with', }} onSuccess={handleSuccess} />
Social Logins
Social logins open in a popup (since they require redirect to Google/GitHub/etc.) and return the result to the embedded form.
<EmbeddedSignIn clientId="my-app" showSocialLogins socialProviders={['google', 'github', 'microsoft']} onSuccess={handleSuccess} />
PKCE Support
For enhanced security, use PKCE with the embedded flow:
import { randomBytes, createHash } from 'crypto'; // Generate PKCE pair const codeVerifier = randomBytes(32).toString('base64url'); const codeChallenge = createHash('sha256') .update(codeVerifier) .digest('base64url'); <EmbeddedSignIn clientId="my-app" codeChallenge={codeChallenge} codeChallengeMethod="S256" onSuccess={({ code }) => { // Pass codeVerifier when exchanging fetch('/api/auth/exchange', { method: 'POST', body: JSON.stringify({ code, code_verifier: codeVerifier }), }); }} />
API Endpoints
The embedded sign-in component calls these API Gateway endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/v1/embedded/auth/check-email | POST | Check if user exists, get available methods |
/api/v1/embedded/auth/password | POST | Authenticate with password |
/api/v1/embedded/auth/otp/send | POST | Send OTP code to email |
/api/v1/embedded/auth/otp/verify | POST | Verify OTP code |
/api/v1/embedded/auth/magic-link/send | POST | Send magic link email |
/api/v1/embedded/auth/magic-link/verify | GET | Verify magic link token |
/api/v1/embedded/auth/code/exchange | POST | Exchange auth code for tokens (server-side) |
Rate Limits
- IP-based: 20 requests per minute
- Email-based: 5 attempts per 15 minutes
Origin Validation
Requests must come from an origin registered in your application's
webOriginsSecurity
- Origin validation: Only registered origins can call embedded auth endpoints
- PKCE: S256 code challenge support for enhanced security
- Rate limiting: Per-IP and per-email rate limits prevent brute force
- Auth codes: Cryptographically random, 10-minute TTL, single-use
- Constant-time comparison: Prevents timing attacks on code verification
- No credentials in browser: Auth codes are exchanged server-side for tokens
Choosing an Integration Style
| Feature | Redirect | Popup | Embedded |
|---|---|---|---|
| Complexity | Simplest | Simple | Moderate |
| User stays on your page | No | Yes | Yes |
| Popup blocker safe | Yes | No (fallback) | Yes |
| Custom styling | Limited | Limited | Full |
| Social login | Native | Native | Via popup |
| Mobile friendly | Yes | No | Yes |
| Security | Highest | High | High |
Was this page helpful?
Let us know how we can improve our documentation