---
title: Client Implementation & Quick Start
category: Getting Started
order: 2
badge: Essential
description: 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](#1-architecture--ecosystem-overview)
2. [Step 1: Obtain OAuth 2.0 Client Credentials](#2-step-1-obtain-oauth-20-client-credentials)
3. [Step 2: Implement Authorization Code Flow with PKCE](#3-step-2-implement-authorization-code-flow-with-pkce)
4. [Step 3: Exchange Authorization Code for Tokens](#4-step-3-exchange-authorization-code-for-tokens)
5. [Step 4: Query OpenID Connect UserInfo](#5-step-4-query-openid-connect-userinfo)
6. [Step 5: Retrieve & Display User Profile Pictures](#6-step-5-retrieve--display-user-profile-pictures)
7. [Step 6: Machine-to-Machine Integration (Client Credentials)](#7-step-6-machine-to-machine-integration-client-credentials)
8. [Multi-Domain Single Sign-On (SSO) & Callbacks](#8-multi-domain-single-sign-on-sso--callbacks)
9. [Code Examples](#9-code-examples)
   - [TypeScript / Node.js](#typescript--nodejs)
   - [Python / FastAPI](#python--fastapi)
   - [PHP / Laravel](#php--laravel)
   - [cURL](#curl)
10. [Reference Endpoints](#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](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:
  ```text
  dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
  ```
- **Code Challenge**: Base64URL-encoded SHA-256 hash of the verifier:
  ```text
  E9Melhoa2OwvFrGMTJguCH5rtx64ZnUqQNb3vg8e060
  ```

### 2. Redirect User to the Authorization Endpoint

Direct the user's browser to the `/oauth/authorize` endpoint:

```text
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:

```text
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`:

```bash
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)

```json
{
  "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:

```bash
curl -X GET "https://auth.ternis.net/oauth/userinfo" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Accept: application/json"
```

### UserInfo Response (200 OK)

```json
{
  "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

```html
<!-- 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:

```bash
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`:
```text
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

```typescript
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

```python
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

```php
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

```bash
# 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 |
