Customizing the Authentication Experience
Learn how to customize the ZewstID authentication pages to match your application's branding and user experience.
Overview
ZewstID allows you to customize various aspects of the authentication experience for your users, including:
- Brand Colors - Match your application's color scheme
- Logos - Display your company logo
- Custom Text - Modify messaging and labels
- Email Templates - Customize authentication emails
- Redirect Behavior - Control post-login flow
OAuth Client Branding
ZewstID provides two layers of branding customization:
- Per-application branding — configured via the Developer Portal for each OAuth client
- Realm-level theming — the underlying Keycloak login page theme (FreeMarker templates)
Configure via Developer Portal
- Go to https://developers.zewstid.com
- Navigate to Applications → Select your application
- Go to the Branding tab ()
/applications/[id]/branding - Configure your application's display name, logo, primary color, and supporting URLs (terms, privacy, support email).
These values are persisted to your application record and surfaced on the Keycloak login page when users authenticate against your client.
Customizing the Keycloak Login Page (Realm Theme)
The login page itself is rendered by Keycloak using FreeMarker templates living in
themes/zewst/login/zewstidthemes/zewst/ ├── login/ # Login pages (login.ftl, register.ftl, etc.) ├── email/ # Transactional email templates │ ├── html/ │ │ ├── magic-link.ftl │ │ └── email-otp.ftl │ └── messages/ └── account/ # Account management pages
After editing templates, rebuild the custom Keycloak image (
./keycloak/build.shEmail Template Customization
Email templates (magic link, email OTP, password reset, email verification) are FreeMarker templates that ship inside the custom Keycloak image. Edit them directly under
themes/zewst/email/themes/zewst/email/html/magic-link.ftlthemes/zewst/email/html/email-otp.ftlthemes/zewst/email/html/password-reset.ftlthemes/zewst/email/html/email-verification.ftl- — subject lines and i18n strings
themes/zewst/email/messages/messages_en.properties
Available template variables include the standard Keycloak email context (
${user.firstName}${user.email}${link}${code}${linkExpiration}Custom Authentication Pages
ZewstID supports two integration models for the sign-in experience:
- Google Model (OAuth redirect) — users are redirected to to authenticate, then redirected back to your app. The page they see is the Keycloak login page styled by your realm theme (see "Customizing the Keycloak Login Page" above).
auth.zewstid.com - Embedded auth (Clerk-style inline) — users sign in directly inside your app using the component, with no redirect away from your domain.
<EmbeddedSignIn />
Embedded Sign-In with <EmbeddedSignIn />
<EmbeddedSignIn />The SDK ships a fully customizable inline sign-in component that handles password, email OTP, and magic-link authentication out of the box:
'use client'; import { EmbeddedSignIn } from '@zewstid/id-nextjs'; export default function SignInPage() { return ( <div className="auth-container"> <EmbeddedSignIn appearance={{ variables: { colorPrimary: '#6B46C1', colorBackground: '#FFFFFF', colorText: '#1F2937', borderRadius: '8px', fontFamily: 'Inter, sans-serif', }, elements: { card: 'my-custom-card', primaryButton: 'my-custom-button', }, logoUrl: '/my-logo.svg', }} labels={{ title: 'Welcome to My App', subtitle: 'Sign in to continue', continueButton: 'Continue', }} methods={['password', 'otp', 'magic-link']} onSuccess={() => { window.location.href = '/dashboard'; }} /> </div> ); }
<EmbeddedSignIn />/api/v1/embedded/auth/check-email/password/otp/send/otp/verify/magic-link/send/magic-link/verify/code/exchangePopup Sign-In with <PopupSignIn />
<PopupSignIn />For an Auth0 Lock–style popup experience:
'use client'; import { PopupSignIn } from '@zewstid/id-nextjs'; export default function HomePage() { return ( <PopupSignIn buttonLabel="Sign in" appearance={{ variables: { colorPrimary: '#6B46C1' } }} onSuccess={() => (window.location.href = '/dashboard')} /> ); }
NextAuth Setup with createZewstIDAuth()
createZewstIDAuth()For both the redirect and embedded flows you'll typically wire up NextAuth with the SDK's one-liner:
// app/api/auth/[...nextauth]/route.ts import NextAuth from 'next-auth'; import { createZewstIDAuth } from '@zewstid/id-nextjs'; export const authOptions = createZewstIDAuth({ clientId: process.env.ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, }); const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
For complete control over the sign-in UI, you can call the embedded auth API endpoints directly with
fetch()Redirect Behavior
Custom Redirect URLs
NextAuth handles OAuth redirect mechanics for you. To control where users land after authentication, pass
callbackUrlsignIn()'use client'; import { signIn } from 'next-auth/react'; export function SignInButton() { return ( <button onClick={() => signIn('zewstid', { callbackUrl: '/dashboard' })}> Sign in </button> ); }
To preserve a
returnTo'use client'; import { useSearchParams } from 'next/navigation'; import { signIn } from 'next-auth/react'; export function SignInButton() { const searchParams = useSearchParams(); const returnTo = searchParams.get('returnTo') || '/dashboard'; return ( <button onClick={() => signIn('zewstid', { callbackUrl: returnTo })}> Sign in </button> ); }
The OAuth
codecreateZewstIDAuth()Conditional Redirects
Redirect based on user roles:
import { getServerSession } from '@zewstid/id-nextjs/server'; export async function GET() { const session = await getServerSession(); if (!session) { return Response.redirect('/auth/signin'); } // Redirect based on role if (session.user.roles.includes('admin')) { return Response.redirect('/admin/dashboard'); } else if (session.user.roles.includes('manager')) { return Response.redirect('/manager/dashboard'); } else { return Response.redirect('/dashboard'); } }
Localization
Multi-Language Support
Customize text for different locales:
const translations = { en: { title: "Welcome to My App", signIn: "Sign In", email: "Email address" }, es: { title: "Bienvenido a Mi Aplicación", signIn: "Iniciar sesión", email: "Correo electrónico" }, fr: { title: "Bienvenue sur Mon Application", signIn: "Se connecter", email: "Adresse e-mail" } }; export default function LocalizedLoginPage({ locale }: { locale: string }) { const t = translations[locale] || translations.en; return ( <EmbeddedSignIn labels={{ title: t.title, continueButton: t.signIn, emailPlaceholder: t.email, }} /> ); }
For the redirect flow, localize the Keycloak login page itself by editing
themes/zewst/login/messages/messages_<locale>.propertiesAdvanced Customization
Custom CSS
Apply custom styles to authentication pages:
/* public/auth-theme.css */ :root { --auth-primary: #6B46C1; --auth-secondary: #9333EA; --auth-background: #F3F4F6; --auth-text: #1F2937; } .zewstid-auth-container { background: var(--auth-background); font-family: 'Your Custom Font', sans-serif; } .zewstid-button-primary { background: linear-gradient(135deg, var(--auth-primary), var(--auth-secondary)); border-radius: 8px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; } .zewstid-input { border: 2px solid var(--auth-primary); border-radius: 8px; font-size: 16px; } .zewstid-logo { filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1)); }
For embedded auth (<EmbeddedSignIn />
appearance.elementsFor the Keycloak login page (redirect flow): drop your custom CSS into
themes/zewst/login/resources/css/theme.propertiestemplate.ftlCustom Domain
The Keycloak realm already serves all auth flows from
auth.zewstid.comauth.myapp.comIf you'd rather keep the auth UI entirely on your own domain, use embedded auth (
<EmbeddedSignIn />Best Practices
1. Brand Consistency
Ensure your authentication pages match your application:
- ✅ Use the same logo, colors, and fonts
- ✅ Match button styles and spacing
- ✅ Use consistent terminology
2. User Experience
Keep authentication simple and familiar:
- ✅ Minimize required fields
- ✅ Provide clear error messages
- ✅ Show loading states
- ✅ Auto-focus on email input
3. Mobile Responsiveness
Test on mobile devices:
- ✅ Use responsive design
- ✅ Ensure touch targets are ≥44px
- ✅ Test in portrait and landscape
4. Accessibility
Make authentication accessible to all users:
- ✅ Use semantic HTML
- ✅ Provide alt text for images
- ✅ Ensure sufficient color contrast (WCAG AA)
- ✅ Support keyboard navigation
5. Performance
Optimize for fast loading:
- ✅ Use CDN for logos and CSS
- ✅ Minimize custom CSS
- ✅ Optimize image sizes
- ✅ Test on slow connections
Examples
Minimal Embedded Sign-In
<EmbeddedSignIn appearance={{ variables: { colorPrimary: '#2563EB' }, logoUrl: 'https://myapp.com/logo.png', }} labels={{ title: 'My App' }} />
Fully Themed Embedded Sign-In
<EmbeddedSignIn appearance={{ variables: { colorPrimary: '#6B46C1', colorBackground: '#FFFFFF', colorText: '#1F2937', borderRadius: '8px', fontFamily: 'Inter, sans-serif', }, elements: { card: 'my-auth-card', primaryButton: 'my-auth-button', input: 'my-auth-input', }, logoUrl: 'https://myapp.com/logo.svg', }} labels={{ title: 'My Enterprise App', subtitle: 'Sign in to continue', termsOfServiceUrl: 'https://myapp.com/terms', privacyPolicyUrl: 'https://myapp.com/privacy', }} methods={['password', 'otp', 'magic-link']} />
Troubleshooting
Logo Not Displaying
Issue: Custom logo not showing on auth pages Solution:
- Ensure logo URL is publicly accessible
- Use HTTPS for logo URL
- Check image format (PNG, SVG, or JPG recommended)
- Verify image size is reasonable (< 200KB)
Custom CSS Not Applied
Issue: Custom styles not appearing Solution:
- Verify CSS URL is publicly accessible via HTTPS
- Check browser console for CORS errors
- Ensure CSS selectors match ZewstID's class names
- Clear browser cache and test in incognito mode
Colors Not Matching
Issue: Brand colors look different on auth pages Solution:
- Use hex color codes (e.g., )
#6B46C1 - Test in different browsers
- Check for color profile differences
- Ensure sufficient contrast for accessibility
Next Steps
- SDK Documentation - Complete SDK API reference
- Quick Start Guide - Get started with ZewstID
- RBAC Guide - Implement role-based access control
Was this page helpful?
Let us know how we can improve our documentation