RushLabs ID developer docs
RushLabs ID is the single sign-on service for RushLabs apps. It is a standard OAuth 2.0 + OpenID Connect provider: your app redirects the user here, we handle email/password + mandatory two-factor authentication, and you get back signed tokens describing who they are.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /.well-known/openid-configuration | OIDC discovery document |
GET /.well-known/jwks.json | Public keys (RS256) for verifying tokens |
GET /oauth/authorize | Start the authorization-code flow (PKCE S256) |
POST /oauth/token | Exchange a code or refresh token for tokens |
GET /oauth/userinfo | Profile claims for a Bearer access token |
POST /oauth/revoke | Revoke a refresh token |
Scopes
| Scope | Grants |
|---|---|
openid | An ID token with the user's stable sub |
profile | given_name, family_name, name |
email | email |
offline_access | A refresh token |
1 · Register your app
Client registration happens on the server (there is no self-serve portal). On the VPS:
cd /root/websites/auth.rushlabs.dev
bun run client:create -- --name "My App" \
--redirect https://myapp.rushlabs.dev/auth/callback
This prints a client_id and (for confidential server-side apps) a client_secret shown once. Pass --public for SPAs/native apps — they get no secret and must use PKCE (the SSO library always uses PKCE). Pass --first-party to skip the consent screen for RushLabs-owned apps.
2 · Add the flow with @rushlabs/sso
The library lives at packages/rushlabs-sso in the auth repo and has zero dependencies (fetch + WebCrypto). Example with Hono on Bun:
import { Hono } from 'hono';
import { getCookie, setCookie, deleteCookie } from 'hono/cookie';
import { RushLabsSSO } from '@rushlabs/sso';
const sso = new RushLabsSSO({
clientId: process.env.SSO_CLIENT_ID!,
clientSecret: process.env.SSO_CLIENT_SECRET, // omit for --public clients
redirectUri: 'https://myapp.rushlabs.dev/auth/callback',
});
const app = new Hono();
app.get('/auth/login', async (c) => {
const { url, state, codeVerifier } = await sso.createAuthorization();
setCookie(c, 'sso_tx', JSON.stringify({ state, codeVerifier }), {
httpOnly: true, secure: true, sameSite: 'Lax', maxAge: 600, path: '/',
});
return c.redirect(url);
});
app.get('/auth/callback', async (c) => {
const tx = JSON.parse(getCookie(c, 'sso_tx') ?? '{}');
deleteCookie(c, 'sso_tx', { path: '/' });
if (!tx.state || c.req.query('state') !== tx.state) return c.text('Bad state', 400);
const tokens = await sso.exchangeCode(c.req.query('code')!, tx.codeVerifier);
const user = await sso.verifyToken(tokens.id_token!);
// user = { sub, email, given_name, family_name, name }
// Create YOUR app session here (its own cookie), keyed by user.sub.
return c.redirect('/');
});
3 · Verify tokens on API requests
Access and ID tokens are RS256 JWTs. Verify them locally (no network round-trip after the first JWKS fetch):
const claims = await sso.verifyToken(bearerToken);
// claims.sub is the stable RushLabs user id
Or call await sso.getUserInfo(accessToken) to fetch fresh profile data.
Raw flow (no library)
# 1. Redirect the browser to:
https://auth.rushlabs.dev/oauth/authorize?response_type=code
&client_id=rl_...&redirect_uri=https://myapp/callback
&scope=openid+profile+email&state=...&code_challenge=...&code_challenge_method=S256
# 2. Exchange the code:
curl -X POST https://auth.rushlabs.dev/oauth/token \
-d grant_type=authorization_code -d code=... \
-d redirect_uri=https://myapp/callback \
-d client_id=rl_... -d client_secret=rls_... -d code_verifier=...
Accounts & 2FA
Every account uses email + password and mandatory TOTP two-factor authentication — users set it up during registration and confirm a code at each sign-in. Sessions on auth.rushlabs.dev are JWT httpOnly cookies, so returning users who are already signed in go straight through the authorize flow (first-party apps skip consent, too).
Federated providers (Google, X, …)
The provider layer is pluggable. Deployments enable providers via AUTH_PROVIDERS in .env; only local ships enabled today. Adding a provider is a small module implementing IdentityProvider — see docs/adding-providers.md in the repo.