Adding SudoAuth to your app
SudoAuth handles login and identity so you don't have to. Your app redirects users here, they authenticate, and you get back a signed JWT with their info. That's the whole thing.
It uses the standard OAuth 2.0 Authorization Code flow — the same pattern used by Google, GitHub, and every other major auth provider. If you've integrated one of those before, this will feel familiar.
https://auth.sudohq.meGet your credentials first
You need a clientId and clientSecret before anything else. Submit a request with your app name and the redirect URI you'll be using. You'll get the credentials by email.
Keep the clientSecret on your server. Never put it in frontend code.
Add a login form to your app
SudoAuth doesn't have a hosted login page — your app collects the email and password, then passes them along. You keep full control over how your login UI looks.
const state = crypto.randomUUID();
sessionStorage.setItem('oauth_state', state);
const url = new URL('https://auth.sudohq.me/oauth/authorize');
url.searchParams.set('client_id', 'YOUR_CLIENT_ID');
url.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
url.searchParams.set('response_type', 'code');
url.searchParams.set('state', state);
url.searchParams.set('email', form.email.value);
url.searchParams.set('password', form.password.value);
window.location.href = url.toString();
state parameter protects against CSRF. Always verify it matches when you receive the callback.Handle the callback
After the user authenticates, they're redirected back to your redirect_uri with a code and state in the query string.
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
if (state !== sessionStorage.getItem('oauth_state')) {
throw new Error('State mismatch — possible CSRF attack');
}
// Send the code to your server
await fetch('/auth/callback', {
method: 'POST',
body: JSON.stringify({ code }),
});
Exchange the code for tokens
This part happens on your server. The code is short-lived (5 minutes) and single-use — exchange it immediately.
// server-side — Node.js
const res = await fetch('https://auth.sudohq.me/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: req.body.code,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
redirect_uri: 'https://yourapp.com/callback',
}),
});
const { access_token, refresh_token } = await res.json();
// store refresh_token in an httpOnly cookie or session
Get the user's info
Use the access token to fetch who just logged in.
const res = await fetch('https://auth.sudohq.me/oauth/userinfo', {
headers: { Authorization: `Bearer ${access_token}` },
});
const user = await res.json();
// { sub: "clxyz123", email: "user@example.com", name: "Jane" }
// Use user.sub as the stable identifier in your own DB
req.session.userId = user.sub;
user.sub is stable and unique — use it as your foreign key.
Keep them logged in
Access tokens expire after 15 minutes. Use the refresh token to get a new one silently. Refresh tokens are rotated on every use — always save the new one.
const res = await fetch('https://auth.sudohq.me/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: storedRefreshToken,
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
}),
});
const { access_token, refresh_token } = await res.json();
// old refresh_token is now invalid — save the new one
All endpoints
/.well-known/openid-configurationOIDC discovery document/jwksPublic keys in JWK format/oauth/authorizeStart the auth flow/oauth/tokenExchange code or refresh token/oauth/userinfoGet user profile (needs Bearer token)/auth/loginDirect login — for your own apps/auth/signupCreate a new account/auth/verifyVerify a JWT (no DB call needed)What's inside the token
Access tokens are RS256-signed JWTs. Decode one and you'll see:
{
"sub": "clxyz123abc",
"email": "user@example.com",
"type": "access",
"iat": 1714500000,
"exp": 1714500900,
"iss": "https://auth.sudohq.me"
}
Verify tokens without calling this server
Any service can verify tokens locally using the public key from /jwks — no network call to SudoAuth needed at runtime.
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({ jwksUri: 'https://auth.sudohq.me/jwks', cache: true });
const key = await client.getSigningKey('auth-key-1');
const decoded = jwt.verify(token, key.getPublicKey(), {
algorithms: ['RS256'],
issuer: 'https://auth.sudohq.me',
});
Or just hit the verify endpoint:
const { valid, decoded } = await fetch('https://auth.sudohq.me/auth/verify', {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());