Skip to main content

OAuth Integration Guide

This is the standard integration approach for all applications. As of February 2026 (Google Model), all apps integrate with ZewstID via OAuth redirect to

auth.zewstid.com
. This is the same pattern used by Google, Microsoft, and Okta.

This guide explains how to integrate your application with ZewstID using standard OAuth 2.0 / OpenID Connect.


Overview

ZewstID provides a complete OAuth 2.0 and OpenID Connect (OIDC) compliant identity provider. Third-party applications can integrate using:

  • Authorization Code Flow (recommended for web apps)
  • Authorization Code Flow with PKCE (recommended for mobile/SPA apps)
  • Client Credentials Flow (for machine-to-machine)

Key Benefits:

  • Standard OIDC compliance - works with any OAuth library
  • No custom SDK required (though we provide one for convenience)
  • Full control over the authentication UI
  • Secure token handling via industry-standard flows

Quick Start

Step 1: Register Your Application

  1. Go to https://developers.zewstid.com

  2. Sign in with your ZewstID account

  3. Navigate to ApplicationsCreate Application

  4. Fill in:

    • Application Name: Your app's display name
    • Application Type: Web Application, SPA, Mobile, or Machine-to-Machine
    • Redirect URIs: Where to redirect after authentication
    • Logout URIs: Where to redirect after logout (optional)
  5. Save and note your:

    • Client ID: Public identifier for your app
    • Client Secret: Keep this secure (server-side only)

Step 2: Configure OIDC Discovery

ZewstID exposes standard OIDC discovery endpoints:

Discovery URL: https://auth.zewstid.com/.well-known/openid-configuration

Most OAuth libraries can auto-configure using this URL.

Key Endpoints:

EndpointURL
Authorization
https://auth.zewstid.com/oauth/authorize
Token
https://auth.zewstid.com/oauth/token
UserInfo
https://auth.zewstid.com/userinfo
Logout
https://auth.zewstid.com/oauth/logout
JWKS
https://auth.zewstid.com/jwks.json

Step 3: Implement the OAuth Flow

Authorization Code Flow (Web Applications)

1. Redirect user to authorize:

const authUrl = new URL('https://auth.zewstid.com/oauth/authorize'); authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID'); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('scope', 'openid email profile'); authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback'); authUrl.searchParams.set('state', generateRandomState()); // Redirect user window.location.href = authUrl.toString();

2. Handle the callback:

// GET /callback?code=AUTH_CODE&state=STATE async function handleCallback(code, state) { // Verify state matches what you sent if (state !== savedState) { throw new Error('State mismatch'); } // Exchange code for tokens const response = await fetch('https://auth.zewstid.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', // Server-side only! code: code, redirect_uri: 'https://yourapp.com/callback', }), }); const tokens = await response.json(); // tokens.access_token, tokens.id_token, tokens.refresh_token }

3. Get user info:

const userInfo = await fetch('https://auth.zewstid.com/userinfo', { headers: { 'Authorization': `Bearer ${tokens.access_token}` }, }).then(r => r.json()); // userInfo.sub (user ID), userInfo.email, userInfo.name, etc.

Framework Integration Examples

Next.js with NextAuth.js

// app/api/auth/[...nextauth]/route.ts import NextAuth from 'next-auth'; const handler = NextAuth({ providers: [ { id: 'zewstid', name: 'ZewstID', type: 'oauth', wellKnown: 'https://auth.zewstid.com/.well-known/openid-configuration', clientId: process.env.ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, idToken: true, checks: ['pkce', 'state'], profile(profile) { return { id: profile.sub, name: profile.name || profile.preferred_username, email: profile.email, image: profile.picture, }; }, }, ], callbacks: { async jwt({ token, account }) { if (account) { token.accessToken = account.access_token; token.idToken = account.id_token; } return token; }, async session({ session, token }) { session.accessToken = token.accessToken; return session; }, }, }); export { handler as GET, handler as POST };
# .env.local ZEWSTID_CLIENT_ID=your-client-id ZEWSTID_CLIENT_SECRET=your-client-secret NEXTAUTH_URL=https://yourapp.com NEXTAUTH_SECRET=your-nextauth-secret

React with OIDC Client

// src/auth/config.ts import { UserManager, WebStorageStateStore } from 'oidc-client-ts'; export const userManager = new UserManager({ authority: process.env.REACT_APP_ZEWSTID_ISSUER_URL, // From Developer Portal → App Settings client_id: 'your-client-id', redirect_uri: 'https://yourapp.com/callback', post_logout_redirect_uri: 'https://yourapp.com', response_type: 'code', scope: 'openid email profile', userStore: new WebStorageStateStore({ store: window.localStorage }), }); // Login export const login = () => userManager.signinRedirect(); // Handle callback export const handleCallback = () => userManager.signinRedirectCallback(); // Logout export const logout = () => userManager.signoutRedirect(); // Get current user export const getUser = () => userManager.getUser();

Express.js with Passport

// auth.js const passport = require('passport'); const { Strategy: OIDCStrategy } = require('passport-openidconnect'); passport.use('zewstid', new OIDCStrategy({ issuer: process.env.ZEWSTID_ISSUER_URL, // From Developer Portal → App Settings authorizationURL: 'https://auth.zewstid.com/oauth/authorize', tokenURL: 'https://auth.zewstid.com/oauth/token', userInfoURL: 'https://auth.zewstid.com/userinfo', clientID: process.env.ZEWSTID_CLIENT_ID, clientSecret: process.env.ZEWSTID_CLIENT_SECRET, callbackURL: 'https://yourapp.com/auth/callback', scope: 'openid email profile', }, (issuer, profile, done) => { return done(null, profile); })); // Routes app.get('/auth/login', passport.authenticate('zewstid')); app.get('/auth/callback', passport.authenticate('zewstid', { failureRedirect: '/login' }), (req, res) => res.redirect('/dashboard') );

Python Flask

# app.py from flask import Flask, redirect, url_for, session from authlib.integrations.flask_client import OAuth app = Flask(__name__) app.secret_key = 'your-secret-key' oauth = OAuth(app) oauth.register( name='zewstid', client_id='your-client-id', client_secret='your-client-secret', server_metadata_url='https://auth.zewstid.com/.well-known/openid-configuration', client_kwargs={'scope': 'openid email profile'}, ) @app.route('/login') def login(): redirect_uri = url_for('callback', _external=True) return oauth.zewstid.authorize_redirect(redirect_uri) @app.route('/callback') def callback(): token = oauth.zewstid.authorize_access_token() user = oauth.zewstid.userinfo() session['user'] = user return redirect('/dashboard') @app.route('/logout') def logout(): session.pop('user', None) return redirect('/')

PKCE Flow (for SPAs and Mobile Apps)

For single-page applications and mobile apps, use PKCE to secure the flow without a client secret:

import crypto from 'crypto'; // Generate PKCE parameters function generatePKCE() { const verifier = crypto.randomBytes(32).toString('base64url'); const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); return { verifier, challenge }; } // Start auth flow const { verifier, challenge } = generatePKCE(); sessionStorage.setItem('pkce_verifier', verifier); const authUrl = new URL('https://auth.zewstid.com/oauth/authorize'); authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID'); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('scope', 'openid email profile'); authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback'); authUrl.searchParams.set('code_challenge', challenge); authUrl.searchParams.set('code_challenge_method', 'S256'); window.location.href = authUrl.toString(); // Exchange code (no client_secret needed with PKCE) const response = await fetch('https://auth.zewstid.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: 'YOUR_CLIENT_ID', code: authCode, redirect_uri: 'https://yourapp.com/callback', code_verifier: sessionStorage.getItem('pkce_verifier'), }), });

Token Handling

Access Token

Use the access token to call ZewstID-protected APIs:

const response = await fetch('https://api.yourapp.com/protected', { headers: { 'Authorization': `Bearer ${accessToken}`, }, });

Token Refresh

Access tokens expire (default: 5 minutes). Use the refresh token to get new ones:

const response = await fetch('https://auth.zewstid.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', // If confidential client refresh_token: refreshToken, }), }); const newTokens = await response.json();

Token Validation

Validate tokens using the JWKS endpoint:

import jwt from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; const client = jwksClient({ jwksUri: 'https://auth.zewstid.com/jwks.json', }); function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { callback(null, key.getPublicKey()); }); } jwt.verify(token, getKey, { issuer: process.env.ZEWSTID_ISSUER_URL, // From Developer Portal → App Settings audience: 'YOUR_CLIENT_ID', }, (err, decoded) => { if (err) { console.error('Token invalid:', err); } else { console.log('Token valid:', decoded); } });

Logout

RP-Initiated Logout

Redirect users to ZewstID logout endpoint:

const logoutUrl = new URL('https://auth.zewstid.com/oauth/logout'); logoutUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID'); logoutUrl.searchParams.set('post_logout_redirect_uri', 'https://yourapp.com'); logoutUrl.searchParams.set('id_token_hint', idToken); // Optional but recommended window.location.href = logoutUrl.toString();

Backchannel Logout (Optional)

ZewstID supports backchannel logout for server-side session invalidation:

  1. Configure your backchannel logout URL in the Developer Portal
  2. ZewstID will POST a logout token when a user logs out
  3. Validate the token and invalidate the user's session
// POST /api/backchannel-logout app.post('/api/backchannel-logout', async (req, res) => { const logoutToken = req.body.logout_token; // Validate the logout token const decoded = await validateLogoutToken(logoutToken); // Invalidate the user's session await invalidateSession(decoded.sub); res.status(200).send(); });

Scopes and Claims

Available Scopes

ScopeClaims Included
openid
sub
(required)
email
email
,
email_verified
profile
name
,
given_name
,
family_name
,
preferred_username
,
picture
phone
phone_number
,
phone_number_verified
address
address
roles
roles
(application-specific roles)

Role Claims

Your application's roles are included in the access token. Use the ZewstID SDK to extract roles automatically, or read them from the token:

{ "roles": ["user", "admin", "editor"] }

SDK Usage: The ZewstID SDKs automatically extract and flatten role claims for you. Use

session.user.roles
(Next.js) or
zewstId.getUser(token).roles
(Node.js) to access them.


Security Best Practices

  1. Always use HTTPS - Never use HTTP in production
  2. Validate state parameter - Prevent CSRF attacks
  3. Use PKCE for public clients - SPAs and mobile apps
  4. Keep client secrets secure - Never expose in frontend code
  5. Validate tokens - Always verify signatures and claims
  6. Implement token refresh - Handle expiration gracefully
  7. Use short-lived access tokens - Minimize exposure window

Troubleshooting

Common Errors

ErrorCauseSolution
invalid_client
Wrong client ID or secretVerify credentials in Developer Portal
invalid_redirect_uri
Redirect URI not registeredAdd URI in Developer Portal
invalid_grant
Code expired or already usedRequest new authorization
unauthorized_client
Client not allowed for grant typeEnable grant type in Developer Portal

Debug Mode

Add

idp_hint=
parameter to skip identity provider selection during testing.


Support

Was this page helpful?

Let us know how we can improve our documentation