2442sso
Developer Documentation

Integrate SSO into your app

2442sso uses standard OpenID Connect (OIDC) over OAuth 2.0. Any app that supports OIDC integrates in minutes.

Your Live Endpoints

Copy these into your app — they update automatically with your domain
Base URL https://sso.2442.io
Discovery https://sso.2442.io/sso/.well-known/openid-configuration
Authorization https://sso.2442.io/sso/authorize
Token Exchange https://sso.2442.io/sso/token
Userinfo https://sso.2442.io/sso/userinfo
Logout https://sso.2442.io/sso/logout

Integration Steps

01

Create an application in 2442sso

Go to Admin → Applications → New Application. Choose OIDC as protocol. Add your callback URL as Redirect URI, e.g. https://yourapp.com/auth/callback. Save and note down the Client ID and Client Secret.

If you need multiple redirect URIs (staging, production), add one per line in the Redirect URIs field.

02

Redirect the user to 2442sso

When the user clicks "Sign in", redirect them to the Authorization endpoint:

PHP // 1. Generate a random state token and store it in session $state = bin2hex(random_bytes(16)); $_SESSION['oauth_state'] = $state; // 2. Build the authorization URL $url = 'https://sso.2442.io/sso/authorize' . '?' . http_build_query([ 'response_type' => 'code', 'client_id' => 'YOUR_CLIENT_ID', 'redirect_uri' => 'https://yourapp.com/auth/callback', 'scope' => 'openid profile email roles', 'state' => $state, ]); // 3. Redirect the user header('Location: ' . $url); exit();
Node.js const crypto = require('crypto'); const state = crypto.randomBytes(16).toString('hex'); req.session.oauthState = state; const params = new URLSearchParams({ response_type: 'code', client_id: 'YOUR_CLIENT_ID', redirect_uri: 'https://yourapp.com/auth/callback', scope: 'openid profile email roles', state: state, }); res.redirect(`https://sso.2442.io/sso/authorize?` + params);
Python import secrets, urllib.parse state = secrets.token_hex(16) session['oauth_state'] = state params = urllib.parse.urlencode({ 'response_type': 'code', 'client_id': 'YOUR_CLIENT_ID', 'redirect_uri': 'https://yourapp.com/auth/callback', 'scope': 'openid profile email roles', 'state': state, }) return redirect('https://sso.2442.io/sso/authorize?' + params)
03

Handle the callback — exchange code for tokens

After login, 2442sso redirects to your redirect_uri with ?code=xxx&state=yyy. Verify state, then exchange the code server-to-server:

PHP // Verify state if ($_GET['state'] !== $_SESSION['oauth_state']) die('Invalid state'); // Exchange code for tokens $response = http_build_query([ 'grant_type' => 'authorization_code', 'code' => $_GET['code'], 'redirect_uri' => 'https://yourapp.com/auth/callback', 'client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_CLIENT_SECRET', ]); $ctx = stream_context_create(['http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/x-www-form-urlencoded', 'content' => $response, ]]); $tokens = json_decode( file_get_contents('https://sso.2442.io/sso/token', false, $ctx), true ); $accessToken = $tokens['access_token'];
Node.js const fetch = require('node-fetch'); // or use built-in fetch (Node 18+) if (req.query.state !== req.session.oauthState) throw new Error('Invalid state'); const tokenRes = await fetch('https://sso.2442.io/sso/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: req.query.code, redirect_uri: 'https://yourapp.com/auth/callback', client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET', }), }); const { access_token } = await tokenRes.json();
Python import requests if request.args['state'] != session['oauth_state']: raise ValueError('Invalid state') token_res = requests.post('https://sso.2442.io/sso/token', data={ 'grant_type': 'authorization_code', 'code': request.args['code'], 'redirect_uri': 'https://yourapp.com/auth/callback', 'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET', }) access_token = token_res.json()['access_token']
04

Fetch user info & log in

Use the access token to get the user's identity, then create/update the user in your app:

PHP $ctx = stream_context_create(['http' => [ 'method' => 'GET', 'header' => 'Authorization: Bearer ' . $accessToken, ]]); $user = json_decode( file_get_contents('https://sso.2442.io/sso/userinfo', false, $ctx), true ); // $user contains: // "sub" => "550e8400-e29b-41d4-..." (unique user ID) // "name" => "Jane Smith" // "email" => "jane@company.com" // "email_verified" => true // "roles" => ["employee"] // Log the user into your app $localUser = findOrCreateUser($user['email'], $user['name']); $_SESSION['user_id'] = $localUser['id'];
Node.js const userRes = await fetch('https://sso.2442.io/sso/userinfo', { headers: { 'Authorization': `Bearer ${access_token}` } }); const user = await userRes.json(); // { sub, name, email, email_verified, roles } const localUser = await findOrCreateUser(user.email, user.name); req.session.userId = localUser.id;
Python user_res = requests.get('https://sso.2442.io/sso/userinfo', headers={'Authorization': f'Bearer {access_token}'}) user = user_res.json() # { 'sub', 'name', 'email', 'email_verified', 'roles' } local_user = find_or_create_user(user['email'], user['name']) session['user_id'] = local_user.id

Full userinfo response example:

{ "sub": "550e8400-e29b-41d4-a716-446655440000", "name": "Jane Smith", "email": "jane@company.com", "email_verified": true, "roles": ["employee"], "iss": "https://sso.2442.io", "aud": "YOUR_CLIENT_ID" }
05

Implement logout

To sign out from both your app and 2442sso simultaneously:

PHP // 1. Destroy your local session session_destroy(); // 2. Redirect to SSO logout endpoint $returnTo = urlencode('https://yourapp.com/login'); header('Location: https://sso.2442.io/sso/logout?post_logout_redirect_uri=' . $returnTo); exit();

API-Only Flows (No Browser Required)

These flows must be enabled per-application in Guardian Admin → Applications → Edit → API Grant Types.

Password Grant (ROPC) — Direct username/password

cURL curl -X POST 'https://sso.2442.io/sso/token' -d 'grant_type=password' -d 'client_id=YOUR_CLIENT_ID' -d 'client_secret=YOUR_CLIENT_SECRET' -d 'username=user@example.com' -d 'password=userpassword' -d 'scope=openid profile email roles'

Client Credentials — Server-to-server, no user

cURL curl -X POST 'https://sso.2442.io/sso/token' -d 'grant_type=client_credentials' -d 'client_id=YOUR_CLIENT_ID' -d 'client_secret=YOUR_CLIENT_SECRET'

Available Scopes

openid
Required. Returns the unique user identifier (sub).
profile
Returns name and preferred_username.
email
Returns email and email_verified.
roles
Returns the roles array (superadmin, admin, employee, guest).

Endpoint Reference

MethodEndpointURLDescription
GET Discovery https://sso.2442.io/sso/.well-known/openid-configuration OIDC metadata & endpoint URLs
GET Authorization https://sso.2442.io/sso/authorize Start SSO flow — redirect user here
POST Token https://sso.2442.io/sso/token Exchange code for access + ID tokens
GET Userinfo https://sso.2442.io/sso/userinfo Get user profile with access token
GET Logout https://sso.2442.io/sso/logout End session, redirect back to your app

Server Configuration (sso.env)

Required keys in sso.env — auto-generated by Setup Wizard
JWT_SECRET 64-char hex string Signs and verifies ROPC & Client Credentials tokens. Must be stable across restarts. Required
ENCRYPTION_KEY 64-char hex string Encrypts client secrets at rest in the database. Required
APP_KEY 64-char hex string Application-level secret for additional crypto operations. Required
APP_VERSION e.g. v2.8 Shown in the footer. Change for each release. Optional
DEFAULT_TIMEZONE e.g. Europe/Vienna Fallback display timezone when browser detection is unavailable. Optional
Important for ROPC & Client Credentials: If JWT_SECRET is not set or changes, all previously issued ROPC/CC tokens become invalid immediately. The Setup Wizard generates this automatically — run /setup if missing.

Tips & Notes

Always verify the state parameter before exchanging the code — prevents CSRF attacks.
The access token expires. Use the refresh_token or re-initiate the SSO flow to get a new one.
If your app runs on multiple domains, register each redirect URI on a separate line in Guardian Admin → Applications.
Never expose your Client Secret in frontend code. Token exchange must happen server-to-server.
Use sub (UUID) as the primary user identifier — email can change, sub never does.