Skip to main content

OAuth Integration Guide for Third-Party Applications

This guide explains how to integrate your application with ZewstID using standard OAuth 2.0 / OpenID Connect. This is the recommended approach for third-party developers building applications that use ZewstID for authentication.


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/realms/zewstid/.well-known/openid-configuration

Most OAuth libraries can auto-configure using this URL.

Key Endpoints:

EndpointURL
Authorization
https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/auth
Token
https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/token
UserInfo
https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/userinfo
Logout
https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/logout
JWKS
https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/certs

Step 3: Implement the OAuth Flow

Authorization Code Flow (Web Applications)

1. Redirect user to authorize:

const authUrl = new URL('https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/auth'); 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/realms/zewstid/protocol/openid-connect/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/realms/zewstid/protocol/openid-connect/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: 'oidc', clientId: process.env.ZEWSTID_CLIENT_ID!, clientSecret: process.env.ZEWSTID_CLIENT_SECRET!, issuer: 'https://auth.zewstid.com/realms/zewstid', }, ], 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: 'https://auth.zewstid.com/realms/zewstid', 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: 'https://auth.zewstid.com/realms/zewstid', authorizationURL: 'https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/auth', tokenURL: 'https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/token', userInfoURL: 'https://auth.zewstid.com/realms/zewstid/protocol/openid-connect/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/realms/zewstid/.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/realms/zewstid/protocol/openid-connect/auth'); 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/realms/zewstid/protocol/openid-connect/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/realms/zewstid/protocol/openid-connect/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/realms/zewstid/protocol/openid-connect/certs', }); function getKey(header, callback) { client.getSigningKey(header.kid, (err, key) => { callback(null, key.getPublicKey()); }); } jwt.verify(token, getKey, { issuer: 'https://auth.zewstid.com/realms/zewstid', 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/realms/zewstid/protocol/openid-connect/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
realm_access.roles
,
resource_access.{client}.roles

Custom Claims

Your application's roles are included in the access token:

{ "realm_access": { "roles": ["default-roles-zewstid", "user"] }, "resource_access": { "your-client-id": { "roles": ["admin", "editor"] } } }

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

kc_idp_hint=
parameter to skip identity provider selection during testing.


Support

Was this page helpful?

Let us know how we can improve our documentation