Ternis Auth & SSO • Quick Start & Client Implementation Guide
📋 Table of Contents
- Architecture & Ecosystem Overview
- Step 1: Obtain OAuth 2.0 Client Credentials
- Step 2: Implement Authorization Code Flow with PKCE
- Step 3: Exchange Authorization Code for Tokens
- Step 4: Query OpenID Connect UserInfo
- Step 5: Retrieve & Display User Profile Pictures
- Step 6: Machine-to-Machine Integration (Client Credentials)
- Multi-Domain Single Sign-On (SSO) & Callbacks
- Code Examples
- Reference Endpoints
1. Architecture & Ecosystem Overview
Ternis Auth operates a federated identity network supporting cross-domain Single Sign-On (SSO):
| Domain | Role | Description |
|---|---|---|
auth.ternis.net |
Primary Global Authority | Default OAuth 2.0 & OIDC authorization server |
auth.ternis.org |
Foundation SSO | Open-source foundation & community authentication |
auth.ternis.dev |
Developer Sandbox | Integration testing and staging environment |
auth.t-api.de |
Dedicated API Gateway | Direct API authentication edge |
auth.thosted.de |
Cloud Services SSO | Cloud hosting services SSO & callback authority |
user.t-cdn.de |
Dedicated Avatar CDN | Fast cookieless user profile & avatar media edge |
avatar.t-cdn.de |
Avatar Edge Network | Direct avatar delivery CDN (/{user_id}.png, etc.) |
user.t-api.de |
Profile Picture & CDN | Fast edge avatar delivery with automated fallbacks |
account.ternis.org |
User Account Portal | User profile, security, and developer app management |
Identification Standard
All entities (Users, Organizations, Memberships, Subscriptions, OAuth Clients) strictly use RFC 4122 Version 4 UUIDs (e.g. b2f6b897-4001-460d-8386-db93f1d8c1c4).
2. Step 1: Obtain OAuth 2.0 Client Credentials
- Log in to the Account Center at http://localhost:8000/login (or
https://account.ternis.org). - Navigate to OAuth Applications (
/account/oauth-apps). - Fill out "Register New OAuth Client":
- Name: Your Application Name (e.g.,
My Hosted App) - Client Type:
- Confidential: For backend web apps (Next.js SSR, Express, Django, Laravel, Rails) with a secure client secret.
- Public: For Single Page Applications (React, Vue) and mobile apps using PKCE without storing a secret.
- Redirect URIs: Authorized callback URLs where the auth code will be delivered.
- Example:
https://auth.thosted.de/callback,https://app.example.com/auth/callback,http://localhost:3000/callback.
- Example:
- Name: Your Application Name (e.g.,
- Copy your Client ID (UUID) and Client Secret.
[!NOTE] Official vs. Third-Party Applications: Applications created by Ternis Platform Administrators display a "Verified Official Application" check on the user consent screen. Third-party applications created by community developers display an informative "Unofficial Application" security caution with the registered developer's name to ensure transparency.
3. Step 2: Implement Authorization Code Flow with PKCE
Ternis Auth supports RFC 7636 Proof Key for Code Exchange (PKCE) for both public and confidential clients.
1. Generate PKCE Verifier & Challenge
- Code Verifier: A cryptographically random string between 43 and 128 characters:
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk - Code Challenge: Base64URL-encoded SHA-256 hash of the verifier:
E9Melhoa2OwvFrGMTJguCH5rtx64ZnUqQNb3vg8e060
2. Redirect User to the Authorization Endpoint
Direct the user's browser to the /oauth/authorize endpoint:
GET https://auth.ternis.net/oauth/authorize?
response_type=code
&client_id=YOUR_CLIENT_UUID
&redirect_uri=https%3A%2F%2Fauth.thosted.de%2Fcallback
&scope=openid%20profile%20email%20ternis%3Asso
&state=SECURE_RANDOM_STATE
&code_challenge=E9Melhoa2OwvFrGMTJguCH5rtx64ZnUqQNb3vg8e060
&code_challenge_method=S256
Supported Scopes
| Scope | Description |
|---|---|
openid |
Required for OpenID Connect identity assertion (sub claim). |
profile |
Full name, username, avatar picture URL, company name. |
email |
User email address and email verification status. |
ternis:sso |
Single Sign-On session federation across ecosystem apps. |
ternis:member |
Internal team memberships, repository permissions, member badges. |
ternis:customer |
Subscription plan tier (starter, pro, enterprise), billing status. |
ternis:partner |
Verified partner status, API quota allocations, webhooks. |
4. Step 3: Exchange Authorization Code for Tokens
When the user grants consent, Ternis Auth redirects back to your configured callback URI:
https://auth.thosted.de/callback?code=AUTH_CODE_HERE&state=SECURE_RANDOM_STATE
- Verify that the received
statematches the session state to prevent CSRF. - Exchange the code via HTTP POST to
/oauth/token:
curl -X POST "https://auth.ternis.net/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_UUID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=https://auth.thosted.de/callback" \
-d "code=AUTH_CODE_HERE" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
Token Response (200 OK)
{
"token_type": "Bearer",
"expires_in": 1296000,
"access_token": "eyJ0eXAiOiJKV1QiLCJh...",
"refresh_token": "def50200..."
}
Store the access_token securely (e.g. in an HTTP-only secure cookie or server session).
5. Step 4: Query OpenID Connect UserInfo
Call /oauth/userinfo with the Bearer access token:
curl -X GET "https://auth.ternis.net/oauth/userinfo" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/json"
UserInfo Response (200 OK)
{
"sub": "b2f6b897-4001-460d-8386-db93f1d8c1c4",
"name": "Elena Rostova",
"preferred_username": "elena.rostova",
"email": "member@ternis.org",
"email_verified": true,
"picture": "https://user.t-api.de/b2f6b897-4001-460d-8386-db93f1d8c1c4.png",
"user_type": "ternis_member",
"role": "member",
"status": "active",
"company": "Ternis Foundation",
"ternis_member": {
"is_member": true,
"member_badge": "ternis-core",
"organizations": {
"ternis-core": "Ternis Core Platform Team",
"ternis-foundation": "Ternis Open Source Foundation"
}
},
"ternis_customer": null,
"ternis_partner": null
}
User Classifications
ternis_member: Core engineers, maintainers, and foundation contributors. Includesternis_member.organizationsand internal security badges.general_user: Standard individual consumer account.paying_customer: Commercial subscribers.ternis_customercontainsplan(starter,pro,enterprise),status, andexpires_at.partner: Verified B2B partners and integrators.ternis_partnercontainspartner_tier,api_quota, andwebhook_url.
6. Step 5: Retrieve & Display User Profile Pictures
Ternis Auth includes a high-performance profile picture and avatar engine with automated initials generation and fallback silhouettes.
1. Direct Avatar URL Patterns
Client applications can display avatars directly without making authenticated API calls:
| Identifier Type | URL Example | Format |
|---|---|---|
| By User UUID (t-CDN) | https://user.t-cdn.de/{user_id}.png |
PNG (256x256 default, cookieless edge) |
| By Username (t-CDN) | https://avatar.t-cdn.de/{username}.png |
PNG (256x256 default, avatar edge) |
| SVG Vector (t-CDN) | https://avatar.t-cdn.de/{username}.svg |
Vector SVG with auto initials |
| By User UUID (t-API) | https://user.t-api.de/{user_id}.png |
PNG (256x256 default) |
| By Username (t-API) | https://user.t-api.de/{username}.png |
PNG (256x256 default) |
| Standard Path | https://auth.ternis.net/avatar/{identifier} |
Negotiated format |
| Default Fallback | https://user.t-cdn.de/default.png |
Neutral silhouette |
2. Customization Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
size or s |
Integer | 256 |
Image width and height in pixels (16 to 1024). |
fallback or d |
String | default |
Fallback mode: default, silhouette, initials, or 404. |
bg |
Hex String | Auto | Custom background color without # (e.g. bg=4F46E5). |
color or fg |
Hex String | ffffff |
Custom text/silhouette foreground color (e.g. color=ffffff). |
3. Graceful Fallbacks (Never Broken Images)
If a requested user does not exist or has no custom avatar:
- The endpoint automatically responds with HTTP 200 and a clean, neutral silhouette avatar accompanied by header
X-Ternis-Fallback: default-avatar. - Your HTML
<img>tags will never show broken image icons. - If you explicitly want a 404 for missing avatars, append
?fallback=404.
4. HTML Implementation Example
<!-- Display user avatar using UUID from UserInfo -->
<img
src="https://user.t-api.de/b2f6b897-4001-460d-8386-db93f1d8c1c4.png?size=128"
alt="Elena Rostova"
width="64"
height="64"
class="rounded-full border border-slate-200"
loading="lazy"
/>
<!-- Or using username in crisp SVG -->
<img
src="https://user.t-api.de/elena.rostova.svg"
alt="Elena Rostova"
width="48"
height="48"
/>
7. Step 6: Machine-to-Machine Integration (Client Credentials)
Backend services, cron jobs, and verified partners can authenticate directly server-to-server without a user browser:
curl -X POST "https://auth.ternis.net/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_PARTNER_CLIENT_UUID" \
-d "client_secret=YOUR_PARTNER_CLIENT_SECRET" \
-d "scope=ternis:partner"
Use the issued access token to interact with partner API endpoints under /api/v1/*.
8. Multi-Domain Single Sign-On (SSO) & Callbacks
1. Allowed Redirect Wildcards
Ternis Auth validates all OAuth redirect URIs against the ecosystem policy:
*.ternis.net(e.g.https://console.ternis.net/callback)*.ternis.org(e.g.https://community.ternis.org/callback)*.ternis.dev(e.g.https://sandbox.ternis.dev/callback)*.t-api.de(e.g.https://auth.t-api.de/callback)*.thosted.deandauth.thosted.de(e.g.https://auth.thosted.de/callback,https://app.thosted.de/sso/callback)localhostand127.0.0.1(for local development)
2. Silent SSO Authentication (prompt=none)
To check if a user is already authenticated without showing a login prompt, pass prompt=none:
GET https://auth.ternis.net/oauth/authorize?
response_type=code
&client_id=YOUR_CLIENT_UUID
&redirect_uri=https%3A%2F%2Fauth.thosted.de%2Fcallback
&scope=openid%20profile%20ternis%3Asso
&prompt=none
If the user has an active session on the identity provider, the authorization code is issued immediately with zero user friction. If not, the server redirects back with ?error=login_required.
9. Code Examples
TypeScript / Node.js
import axios from 'axios';
interface UserInfo {
sub: string;
name: string;
email: string;
picture: string;
user_type: string;
}
// 1. Build Authorization URL
export function getLoginUrl(clientId: string, redirectUri: string, state: string, codeChallenge: string): string {
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: 'openid profile email ternis:sso',
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
return `https://auth.ternis.net/oauth/authorize?${params.toString()}`;
}
// 2. Exchange Code for Tokens
export async function exchangeCode(clientId: string, clientSecret: string, redirectUri: string, code: string, codeVerifier: string) {
const response = await axios.post('https://auth.ternis.net/oauth/token', {
grant_type: 'authorization_code',
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code,
code_verifier: codeVerifier,
});
return response.data; // { access_token, refresh_token, expires_in }
}
// 3. Fetch User Profile
export async function fetchUserInfo(accessToken: string): Promise<UserInfo> {
const response = await axios.get('https://auth.ternis.net/oauth/userinfo', {
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
});
return response.data;
}
Python / FastAPI
import httpx
AUTH_SERVER = "https://auth.ternis.net"
async def exchange_code_for_token(client_id: str, client_secret: str, redirect_uri: str, code: str, verifier: str):
async with httpx.AsyncClient() as client:
response = await client.post(
f"{AUTH_SERVER}/oauth/token",
data={
"grant_type": "authorization_code",
"client_id": client_id,
"client_secret": client_secret,
"redirect_uri": redirect_uri,
"code": code,
"code_verifier": verifier,
}
)
response.raise_for_status()
return response.json()
async def get_user_profile(access_token: str):
async with httpx.AsyncClient() as client:
response = await client.get(
f"{AUTH_SERVER}/oauth/userinfo",
headers={"Authorization": f"Bearer {access_token}"}
)
response.raise_for_status()
return response.json()
PHP / Laravel
use Illuminate\Support\Facades\Http;
// Exchange authorization code
$tokenResponse = Http::asForm()->post('https://auth.ternis.net/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => config('services.ternis.client_id'),
'client_secret' => config('services.ternis.client_secret'),
'redirect_uri' => 'https://auth.thosted.de/callback',
'code' => $request->query('code'),
'code_verifier' => session('code_verifier'),
]);
$tokens = $tokenResponse->json();
$accessToken = $tokens['access_token'];
// Query UserInfo
$user = Http::withToken($accessToken)
->acceptJson()
->get('https://auth.ternis.net/oauth/userinfo')
->json();
// Access user claims and profile picture
$userId = $user['sub'];
$avatarUrl = $user['picture']; // e.g. https://user.t-api.de/{uuid}.png
$isMember = $user['user_type'] === 'ternis_member';
cURL
# 1. Exchange code for access token
curl -X POST "https://auth.ternis.net/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_UUID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=https://auth.thosted.de/callback" \
-d "code=AUTH_CODE" \
-d "code_verifier=YOUR_VERIFIER"
# 2. Fetch UserInfo
curl -X GET "https://auth.ternis.net/oauth/userinfo" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Accept: application/json"
# 3. Fetch User Avatar
curl -o avatar.png "https://user.t-api.de/elena.rostova.png?size=256"
10. Reference Endpoints
| Endpoint | Method | Format | Description |
|---|---|---|---|
/.well-known/openid-configuration |
GET |
JSON | Standard OpenID Connect discovery metadata |
/.well-known/oauth-authorization-server |
GET |
JSON | RFC 8414 Authorization server metadata |
/oauth/authorize |
GET |
HTML | Interactive user authorization consent screen |
/oauth/token |
POST |
JSON | OAuth token issuance (code, refresh, client_credentials) |
/oauth/userinfo |
GET |
JSON | Standard OIDC user claims with profile picture URL |
/avatar/{identifier} |
GET |
Image | Profile picture delivery (PNG, SVG, JPG, WebP) |
https://user.t-api.de/{user_id}.png |
GET |
PNG | Fast direct profile picture by UUID |
https://user.t-api.de/{username}.png |
GET |
PNG | Fast direct profile picture by username |
https://user.t-api.de/{username}.svg |
GET |
SVG | Vector profile picture with initials |
/docs |
GET |
HTML | Interactive Developer & Agent Documentation Viewer |
/api/openapi.json |
GET |
JSON | OpenAPI 3.1.0 specification |
/llms.txt |
GET |
Text | AI agent summary index |
/llms-full.txt |
GET |
Text | Comprehensive context-ready API & LLM documentation |