Raw .md
Getting Started Essential Updated Sep 23, 2026

Step-by-step developer tutorial for integrating Ternis Auth OAuth 2.0 & OIDC SSO into web and mobile clients.

Ternis Auth & SSO • Quick Start & Client Implementation Guide


📋 Table of Contents

  1. Architecture & Ecosystem Overview
  2. Step 1: Obtain OAuth 2.0 Client Credentials
  3. Step 2: Implement Authorization Code Flow with PKCE
  4. Step 3: Exchange Authorization Code for Tokens
  5. Step 4: Query OpenID Connect UserInfo
  6. Step 5: Retrieve & Display User Profile Pictures
  7. Step 6: Machine-to-Machine Integration (Client Credentials)
  8. Multi-Domain Single Sign-On (SSO) & Callbacks
  9. Code Examples
  10. 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

  1. Log in to the Account Center at http://localhost:8000/login (or https://account.ternis.org).
  2. Navigate to OAuth Applications (/account/oauth-apps).
  3. 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.
  4. 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
  1. Verify that the received state matches the session state to prevent CSRF.
  2. 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. Includes ternis_member.organizations and internal security badges.
  • general_user: Standard individual consumer account.
  • paying_customer: Commercial subscribers. ternis_customer contains plan (starter, pro, enterprise), status, and expires_at.
  • partner: Verified B2B partners and integrators. ternis_partner contains partner_tier, api_quota, and webhook_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.de and auth.thosted.de (e.g. https://auth.thosted.de/callback, https://app.thosted.de/sso/callback)
  • localhost and 127.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