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.

Base URLhttps://auth.sudohq.me

Get 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.

1

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();
NoteThe state parameter protects against CSRF. Always verify it matches when you receive the callback.
2

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 }),
});
3

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
4

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.

5

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

MethodPathWhat it does
GET/.well-known/openid-configurationOIDC discovery document
GET/jwksPublic keys in JWK format
GET/oauth/authorizeStart the auth flow
POST/oauth/tokenExchange code or refresh token
GET/oauth/userinfoGet user profile (needs Bearer token)
POST/auth/loginDirect login — for your own apps
POST/auth/signupCreate a new account
GET/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());