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
-
Sign in with your ZewstID account
-
Navigate to Applications → Create Application
-
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)
-
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:
| Endpoint | URL |
|---|---|
| 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:
- Configure your backchannel logout URL in the Developer Portal
- ZewstID will POST a logout token when a user logs out
- 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
| Scope | Claims Included |
|---|---|
openid | sub |
email | emailemail_verified |
profile | namegiven_namefamily_namepreferred_usernamepicture |
phone | phone_numberphone_number_verified |
address | address |
roles | realm_access.rolesresource_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
- Always use HTTPS - Never use HTTP in production
- Validate state parameter - Prevent CSRF attacks
- Use PKCE for public clients - SPAs and mobile apps
- Keep client secrets secure - Never expose in frontend code
- Validate tokens - Always verify signatures and claims
- Implement token refresh - Handle expiration gracefully
- Use short-lived access tokens - Minimize exposure window
Troubleshooting
Common Errors
| Error | Cause | Solution |
|---|---|---|
invalid_client | Wrong client ID or secret | Verify credentials in Developer Portal |
invalid_redirect_uri | Redirect URI not registered | Add URI in Developer Portal |
invalid_grant | Code expired or already used | Request new authorization |
unauthorized_client | Client not allowed for grant type | Enable grant type in Developer Portal |
Debug Mode
Add
kc_idp_hint=Support
- Documentation: https://developers.zewstid.com/docs
- API Reference: https://developers.zewstid.com/docs/api
- GitHub Issues: https://github.com/zewstid/zewstid-js/issues
- Discord: https://discord.gg/zewstid
Was this page helpful?
Let us know how we can improve our documentation