Skip to main content

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:

  1. Per-application branding — configured via the Developer Portal for each OAuth client
  2. Realm-level theming — the underlying Keycloak login page theme (FreeMarker templates)

Configure via Developer Portal

  1. Go to https://developers.zewstid.com
  2. Navigate to Applications → Select your application
  3. Go to the Branding tab (
    /applications/[id]/branding
    )
  4. 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/
on the server. To globally restyle the auth experience for the entire
zewstid
realm:

themes/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.sh
) and redeploy. Theme changes are global to the realm — for per-client visual differences, use the per-application branding above.


Email 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.ftl
  • themes/zewst/email/html/email-otp.ftl
  • themes/zewst/email/html/password-reset.ftl
  • themes/zewst/email/html/email-verification.ftl
  • themes/zewst/email/messages/messages_en.properties
    — subject lines and i18n strings

Available template variables include the standard Keycloak email context (

${user.firstName}
,
${user.email}
,
${link}
,
${code}
,
${linkExpiration}
) plus realm/client display data. After changes, rebuild the Keycloak image and redeploy.


Custom Authentication Pages

ZewstID supports two integration models for the sign-in experience:

  1. Google Model (OAuth redirect) — users are redirected to
    auth.zewstid.com
    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).
  2. Embedded auth (Clerk-style inline) — users sign in directly inside your app using the
    <EmbeddedSignIn />
    component, with no redirect away from your domain.

Embedded Sign-In with
<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 />
internally orchestrates the embedded auth API endpoints (
/api/v1/embedded/auth/check-email
,
/password
,
/otp/send
,
/otp/verify
,
/magic-link/send
,
/magic-link/verify
,
/code/exchange
) and exchanges the resulting auth code for Keycloak tokens via your NextAuth configuration.

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()

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()
— the request/response shapes are documented in the API reference under "Embedded Auth".


Redirect Behavior

Custom Redirect URLs

NextAuth handles OAuth redirect mechanics for you. To control where users land after authentication, pass

callbackUrl
to
signIn()
:

'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
query param across the OAuth round-trip:

'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

code
exchange and session creation are handled automatically by the NextAuth callback route configured via
createZewstIDAuth()
.

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>.properties
and configuring supported locales in your realm settings.


Advanced 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 />
): the component accepts an
appearance.elements
map that lets you target individual parts of the form with your own class names — no remote CSS URL needed.

For the Keycloak login page (redirect flow): drop your custom CSS into

themes/zewst/login/resources/css/
and reference it from
theme.properties
or
template.ftl
. Rebuild the Keycloak image and redeploy to apply the changes globally.

Custom Domain

The Keycloak realm already serves all auth flows from

auth.zewstid.com
. If you want users to land on your own domain (e.g.
auth.myapp.com
) for the redirect flow, contact ZewstID support — custom domains require coordinated DNS, certificate provisioning, and realm configuration on the platform side.

If you'd rather keep the auth UI entirely on your own domain, use embedded auth (

<EmbeddedSignIn />
) — no redirect ever leaves your origin.


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:

  1. Ensure logo URL is publicly accessible
  2. Use HTTPS for logo URL
  3. Check image format (PNG, SVG, or JPG recommended)
  4. Verify image size is reasonable (< 200KB)

Custom CSS Not Applied

Issue: Custom styles not appearing Solution:

  1. Verify CSS URL is publicly accessible via HTTPS
  2. Check browser console for CORS errors
  3. Ensure CSS selectors match ZewstID's class names
  4. Clear browser cache and test in incognito mode

Colors Not Matching

Issue: Brand colors look different on auth pages Solution:

  1. Use hex color codes (e.g.,
    #6B46C1
    )
  2. Test in different browsers
  3. Check for color profile differences
  4. Ensure sufficient contrast for accessibility

Next Steps

Was this page helpful?

Let us know how we can improve our documentation