---
title: Multi-Language SDK & Client Implementation Examples
category: SDKs & Implementation
order: 10
badge: Code Samples
description: Production-grade integration code samples in TypeScript, Python, PHP, Go, and cURL.
---

# Multi-Language SDK & Client Implementation Examples

This guide provides end-to-end integration examples for interacting with Ternis Auth across major programming languages and frameworks.

---

## 1. TypeScript / Node.js (Express)

Complete implementation of the Authorization Code flow with PKCE using native Node.js `crypto` and `fetch`:

```typescript
import express, { Request, Response } from 'express';
import crypto from 'crypto';

const app = express();
const PORT = 3000;

// Ternis Auth Configuration
const AUTH_ISSUER = 'https://auth.ternis.net';
const CLIENT_ID = '9dc6e84a-714c-4e89-9a29-bc828cf99874';
const CLIENT_SECRET = process.env.TERNIS_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/auth/callback';

// Temporary memory store for state & verifier (use Redis or session in production)
const pkceStore = new Map<string, string>();

function base64UrlEncode(buffer: Buffer): string {
  return buffer.toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

// 1. Redirect user to Ternis Auth
app.get('/login', (req: Request, res: Response) => {
  const state = base64UrlEncode(crypto.randomBytes(32));
  const verifier = base64UrlEncode(crypto.randomBytes(32));
  const challenge = base64UrlEncode(crypto.createHash('sha256').update(verifier).digest());

  pkceStore.set(state, verifier);

  const authUrl = new URL(`${AUTH_ISSUER}/oauth/authorize`);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('scope', 'openid profile email ternis:sso');
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('code_challenge', challenge);
  authUrl.searchParams.set('code_challenge_method', 'S256');

  res.redirect(authUrl.toString());
});

// 2. OAuth Callback
app.get('/auth/callback', async (req: Request, res: Response) => {
  const { code, state } = req.query as { code?: string; state?: string };

  if (!code || !state || !pkceStore.has(state)) {
    return res.status(400).send('Invalid state or authorization code');
  }

  const verifier = pkceStore.get(state)!;
  pkceStore.delete(state);

  // 3. Exchange code for access token
  const tokenParams = new URLSearchParams({
    grant_type: 'authorization_code',
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET || '',
    redirect_uri: REDIRECT_URI,
    code,
    code_verifier: verifier,
  });

  const tokenRes = await fetch(`${AUTH_ISSUER}/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: tokenParams.toString(),
  });

  const tokens = await tokenRes.json();

  // 4. Fetch User Profile
  const userRes = await fetch(`${AUTH_ISSUER}/oauth/userinfo`, {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  });
  const user = await userRes.json();

  res.send(`
    <h1>Welcome, ${user.name}!</h1>
    <p>Email: ${user.email}</p>
    <img src="https://user.t-cdn.de/${user.sub}.png?size=128" alt="Avatar" width="64" height="64" />
  `);
});

app.listen(PORT, () => console.log(`Server listening on http://localhost:${PORT}`));
```

---

## 2. Python (FastAPI / Requests)

```python
import os
import hashlib
import base64
import secrets
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import RedirectResponse, HTMLResponse
import requests

app = FastAPI()

AUTH_ISSUER = "https://auth.ternis.net"
CLIENT_ID = "9dc6e84a-714c-4e89-9a29-bc828cf99874"
CLIENT_SECRET = os.getenv("TERNIS_CLIENT_SECRET", "")
REDIRECT_URI = "http://localhost:8000/callback"

pkce_cache = {}

def generate_pkce():
    verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip("=")
    challenge = base64.urlsafe_b64encode(
        hashlib.sha256(verifier.encode()).digest()
    ).decode().rstrip("=")
    return verifier, challenge

@app.get("/login")
def login():
    state = secrets.token_hex(16)
    verifier, challenge = generate_pkce()
    pkce_cache[state] = verifier

    url = (
        f"{AUTH_ISSUER}/oauth/authorize?"
        f"response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}"
        f"&scope=openid+profile+email+ternis:sso&state={state}"
        f"&code_challenge={challenge}&code_challenge_method=S256"
    )
    return RedirectResponse(url)

@app.get("/callback")
def callback(code: str, state: str):
    verifier = pkce_cache.pop(state, None)
    if not verifier:
        raise HTTPException(status_code=400, detail="Invalid state token")

    token_res = requests.post(
        f"{AUTH_ISSUER}/oauth/token",
        data={
            "grant_type": "authorization_code",
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "redirect_uri": REDIRECT_URI,
            "code": code,
            "code_verifier": verifier,
        },
    )
    token_res.raise_for_status()
    tokens = token_res.json()

    user_res = requests.get(
        f"{AUTH_ISSUER}/oauth/userinfo",
        headers={"Authorization": f"Bearer {tokens['access_token']}"},
    )
    user_res.raise_for_status()
    user = user_res.json()

    return HTMLResponse(f"""
        <h2>Welcome, {user['name']}</h2>
        <p>UUID: <code>{user['sub']}</code></p>
        <img src="https://avatar.t-cdn.de/{user['preferred_username']}.svg" width="64" height="64" />
    """)
```

---

## 3. PHP (Standard PHP / cURL)

```php
<?php

$issuer = 'https://auth.ternis.net';
$clientId = '9dc6e84a-714c-4e89-9a29-bc828cf99874';
$clientSecret = getenv('TERNIS_CLIENT_SECRET');
$redirectUri = 'http://localhost:8080/callback.php';

session_start();

// 1. Initiate Login
if (isset($_GET['login'])) {
    $verifier = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
    $challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
    $state = bin2hex(random_bytes(16));

    $_SESSION['pkce_verifier'] = $verifier;
    $_SESSION['oauth_state'] = $state;

    $url = $issuer . '/oauth/authorize?' . http_build_query([
        'response_type' => 'code',
        'client_id' => $clientId,
        'redirect_uri' => $redirectUri,
        'scope' => 'openid profile email ternis:sso',
        'state' => $state,
        'code_challenge' => $challenge,
        'code_challenge_method' => 'S256',
    ]);

    header('Location: ' . $url);
    exit;
}

// 2. Handle Callback
if (isset($_GET['code']) && isset($_GET['state'])) {
    if ($_GET['state'] !== $_SESSION['oauth_state']) {
        die('State mismatch CSRF detected');
    }

    $ch = curl_init($issuer . '/oauth/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'grant_type' => 'authorization_code',
        'client_id' => $clientId,
        'client_secret' => $clientSecret,
        'redirect_uri' => $redirectUri,
        'code' => $_GET['code'],
        'code_verifier' => $_SESSION['pkce_verifier'],
    ]));

    $tokenResponse = json_decode(curl_exec($ch), true);
    curl_close($ch);

    // 3. Fetch UserInfo
    $ch = curl_init($issuer . '/oauth/userinfo');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $tokenResponse['access_token']
    ]);
    $user = json_decode(curl_exec($ch), true);
    curl_close($ch);

    echo "<h1>Welcome, " . htmlspecialchars($user['name']) . "!</h1>";
    echo "<img src='https://user.t-cdn.de/" . urlencode($user['sub']) . ".png?size=128' width='64' height='64' />";
}
```

---

## 4. Go (golang.org/x/oauth2)

```go
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"

	"golang.org/x/oauth2"
)

var oauthConfig = &oauth2.Config{
	ClientID:     "9dc6e84a-714c-4e89-9a29-bc828cf99874",
	ClientSecret: os.Getenv("TERNIS_CLIENT_SECRET"),
	RedirectURL:  "http://localhost:8080/callback",
	Scopes:       []string{"openid", "profile", "email", "ternis:sso"},
	Endpoint: oauth2.Endpoint{
		AuthURL:  "https://auth.ternis.net/oauth/authorize",
		TokenURL: "https://auth.ternis.net/oauth/token",
	},
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	url := oauthConfig.AuthCodeURL("random-state-string", oauth2.AccessTypeOffline)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
	code := r.URL.Query().Get("code")
	token, err := oauthConfig.Exchange(context.Background(), code)
	if err != nil {
		http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
		return
	}

	fmt.Fprintf(w, "Access Token: %s\n", token.AccessToken)
}

func main() {
	http.HandleFunc("/login", handleLogin)
	http.HandleFunc("/callback", handleCallback)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```
