# Sample Code

This document provides complete, working code examples for integrating with Oten IDP across multiple programming languages.

## Prerequisites

Before using these examples:

1. Register your application with Oten IDP
2. Obtain your `client_id` and `client_secret` (if applicable)
3. Set up your redirect URI
4. Choose your JAR signing method (HS256 or EdDSA)

## Environment Variables

All examples use these environment variables:

```bash
# Required for all examples
OTEN_CLIENT_ID=your_client_id
OTEN_REDIRECT_URI=https://yourapp.com/callback

# For HS256 signing
OTEN_CLIENT_SECRET=your_client_secret

# For EdDSA signing
OTEN_PRIVATE_KEY_PATH=./jar-private-key.pem
OTEN_KEY_ID=your-key-id

# Environment
OTEN_ENV=production  # or development
```

## 🌐 JavaScript/Node.js

### Complete Integration Example

```javascript
// package.json dependencies:
// {
//   "jsonwebtoken": "^9.0.0",
//   "express": "^4.18.0",
//   "crypto": "^1.0.1"
// }

const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const fs = require('fs');

const app = express();

// Configuration
const config = {
  clientId: process.env.OTEN_CLIENT_ID,
  clientSecret: process.env.OTEN_CLIENT_SECRET,
  redirectUri: process.env.OTEN_REDIRECT_URI,
  privateKeyPath: process.env.OTEN_PRIVATE_KEY_PATH,
  keyId: process.env.OTEN_KEY_ID,
  environment: process.env.OTEN_ENV || 'production'
};

const baseUrl = config.environment === 'production'
  ? 'https://account.oten.com'
  : 'https://account.sbx.oten.dev';

// PKCE helper functions
function generateCodeVerifier() {
  return crypto.randomBytes(32).toString('base64url');
}

async function generateCodeChallenge(verifier) {
  const hash = crypto.createHash('sha256').update(verifier).digest();
  return hash.toString('base64url');
}

// JAR creation functions
async function createJARWithHS256(authParams) {
  const now = Math.floor(Date.now() / 1000);

  const jarPayload = {
    // Required JWT claims
    iss: authParams.client_id,
    aud: baseUrl,
    iat: now,
    exp: now + 300, // 5 minutes
    jti: crypto.randomUUID(),

    // OAuth parameters
    ...authParams
  };

  return jwt.sign(jarPayload, config.clientSecret, {
    algorithm: 'HS256',
    header: { alg: 'HS256', typ: 'JWT' }
  });
}

async function createJARWithEdDSA(authParams) {
  const now = Math.floor(Date.now() / 1000);
  const privateKey = fs.readFileSync(config.privateKeyPath, 'utf8');

  const jarPayload = {
    // Required JWT claims
    iss: authParams.client_id,
    aud: baseUrl,
    iat: now,
    exp: now + 300, // 5 minutes
    jti: crypto.randomUUID(),

    // OAuth parameters
    ...authParams
  };

  return jwt.sign(jarPayload, privateKey, {
    algorithm: 'EdDSA',
    header: { alg: 'EdDSA', typ: 'JWT', kid: config.keyId }
  });
}

// Routes
app.get('/login', async (req, res) => {
  try {
    // Generate PKCE parameters
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);
    const state = crypto.randomUUID();

    // Store in session (use proper session storage in production)
    req.session = { codeVerifier, state };

    // Create OAuth parameters
    const authParams = {
      client_id: config.clientId,
      redirect_uri: config.redirectUri,
      response_type: 'code',
      scope: 'openid profile email',
      state: state,
      code_challenge: codeChallenge,
      code_challenge_method: 'S256'
    };

    // Create JAR
    const requestJWT = config.clientSecret
      ? await createJARWithHS256(authParams)
      : await createJARWithEdDSA(authParams);

    // Redirect to authorization endpoint
    const authURL = `${baseUrl}/v1/oauth/authorize?client_id=${config.clientId}&request=${requestJWT}`;
    res.redirect(authURL);

  } catch (error) {
    console.error('Login error:', error);
    res.status(500).send('Login failed');
  }
});

app.get('/callback', async (req, res) => {
  try {
    const { code, state, error } = req.query;

    if (error) {
      return res.status(400).send(`Authorization error: ${error}`);
    }

    // Verify state
    if (state !== req.session.state) {
      return res.status(400).send('Invalid state parameter');
    }

    // Exchange code for tokens
    const tokenResponse = await fetch(`${baseUrl}/v1/oauth/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        redirect_uri: config.redirectUri,
        client_id: config.clientId,
        code_verifier: req.session.codeVerifier,
        ...(config.clientSecret && { client_secret: config.clientSecret })
      })
    });

    const tokens = await tokenResponse.json();

    if (tokens.status === 'OK') {
      const tokenData = tokens.data[0];

      // Store tokens securely (use proper storage in production)
      req.session.tokens = tokenData;

      // Decode ID token for user info
      const idTokenPayload = JSON.parse(Buffer.from(tokenData.id_token.split('.')[1], 'base64').toString());

      res.json({
        message: 'Login successful',
        user: {
          id: idTokenPayload.sub,
          email: idTokenPayload.email,
          name: idTokenPayload.name
        },
        tokens: {
          access_token: tokenData.access_token,
          expires_in: tokenData.expires_in
        }
      });
    } else {
      res.status(400).json({ error: 'Token exchange failed', details: tokens });
    }

  } catch (error) {
    console.error('Callback error:', error);
    res.status(500).send('Callback processing failed');
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
  console.log('Visit http://localhost:3000/login to start OAuth flow');
});
```

## 🐍 Python

### Complete Integration Example

```python
# requirements.txt:
# flask==2.3.0
# pyjwt[crypto]==2.8.0
# requests==2.31.0
# cryptography==41.0.0

from flask import Flask, request, redirect, session, jsonify
import jwt
import time
import uuid
import hashlib
import base64
import os
import requests
from cryptography.hazmat.primitives import serialization

app = Flask(__name__)
app.secret_key = 'your-secret-key-change-in-production'

# Configuration
class Config:
    CLIENT_ID = os.getenv('OTEN_CLIENT_ID')
    CLIENT_SECRET = os.getenv('OTEN_CLIENT_SECRET')
    REDIRECT_URI = os.getenv('OTEN_REDIRECT_URI')
    PRIVATE_KEY_PATH = os.getenv('OTEN_PRIVATE_KEY_PATH')
    KEY_ID = os.getenv('OTEN_KEY_ID')
    ENVIRONMENT = os.getenv('OTEN_ENV', 'production')

    @property
    def base_url(self):
        return ('https://account.oten.com' if self.ENVIRONMENT == 'production'
                else 'https://account.sbx.oten.dev')

config = Config()

# PKCE helper functions
def generate_code_verifier():
    return base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')

def generate_code_challenge(verifier):
    digest = hashlib.sha256(verifier.encode('utf-8')).digest()
    return base64.urlsafe_b64encode(digest).decode('utf-8').rstrip('=')

# JAR creation functions
def create_jar_with_hs256(auth_params):
    now = int(time.time())

    jar_payload = {
        # Required JWT claims
        'iss': auth_params['client_id'],
        'aud': config.base_url,
        'iat': now,
        'exp': now + 300,  # 5 minutes
        'jti': str(uuid.uuid4()),

        # OAuth parameters
        **auth_params
    }

    return jwt.encode(
        jar_payload,
        config.CLIENT_SECRET,
        algorithm='HS256',
        headers={'alg': 'HS256', 'typ': 'JWT'}
    )

def create_jar_with_eddsa(auth_params):
    now = int(time.time())

    # Load private key
    with open(config.PRIVATE_KEY_PATH, 'rb') as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(),
            password=None
        )

    jar_payload = {
        # Required JWT claims
        'iss': auth_params['client_id'],
        'aud': config.base_url,
        'iat': now,
        'exp': now + 300,  # 5 minutes
        'jti': str(uuid.uuid4()),

        # OAuth parameters
        **auth_params
    }

    return jwt.encode(
        jar_payload,
        private_key,
        algorithm='EdDSA',
        headers={'alg': 'EdDSA', 'typ': 'JWT', 'kid': config.KEY_ID}
    )

# Routes
@app.route('/login')
def login():
    try:
        # Generate PKCE parameters
        code_verifier = generate_code_verifier()
        code_challenge = generate_code_challenge(code_verifier)
        state = str(uuid.uuid4())

        # Store in session
        session['code_verifier'] = code_verifier
        session['state'] = state

        # Create OAuth parameters
        auth_params = {
            'client_id': config.CLIENT_ID,
            'redirect_uri': config.REDIRECT_URI,
            'response_type': 'code',
            'scope': 'openid profile email',
            'state': state,
            'code_challenge': code_challenge,
            'code_challenge_method': 'S256'
        }

        # Create JAR
        request_jwt = (create_jar_with_hs256(auth_params) if config.CLIENT_SECRET
                      else create_jar_with_eddsa(auth_params))

        # Redirect to authorization endpoint
        auth_url = f"{config.base_url}/v1/oauth/authorize?client_id={config.CLIENT_ID}&request={request_jwt}"
        return redirect(auth_url)

    except Exception as e:
        return f"Login failed: {str(e)}", 500

@app.route('/callback')
def callback():
    try:
        code = request.args.get('code')
        state = request.args.get('state')
        error = request.args.get('error')

        if error:
            return f"Authorization error: {error}", 400

        # Verify state
        if state != session.get('state'):
            return "Invalid state parameter", 400

        # Exchange code for tokens
        token_data = {
            'grant_type': 'authorization_code',
            'code': code,
            'redirect_uri': config.REDIRECT_URI,
            'client_id': config.CLIENT_ID,
            'code_verifier': session.get('code_verifier')
        }

        if config.CLIENT_SECRET:
            token_data['client_secret'] = config.CLIENT_SECRET

        token_response = requests.post(
            f"{config.base_url}/v1/oauth/token",
            data=token_data,
            headers={'Content-Type': 'application/x-www-form-urlencoded'}
        )

        tokens = token_response.json()

        if tokens.get('status') == 'OK':
            token_data = tokens['data'][0]

            # Store tokens in session
            session['tokens'] = token_data

            # Decode ID token for user info
            id_token_payload = jwt.decode(
                token_data['id_token'],
                options={"verify_signature": False}
            )

            return jsonify({
                'message': 'Login successful',
                'user': {
                    'id': id_token_payload.get('sub'),
                    'email': id_token_payload.get('email'),
                    'name': id_token_payload.get('name')
                },
                'tokens': {
                    'access_token': token_data['access_token'],
                    'expires_in': token_data['expires_in']
                }
            })
        else:
            return jsonify({'error': 'Token exchange failed', 'details': tokens}), 400

    except Exception as e:
        return f"Callback processing failed: {str(e)}", 500

if __name__ == '__main__':
    app.run(debug=True, port=3000)
```

## 🐹 Go

### Complete Integration Example

```go
// go.mod:
// module oten-example
//
// go 1.21
//
// require (
//     github.com/golang-jwt/jwt/v5 v5.0.0
//     github.com/gorilla/mux v1.8.0
//     github.com/gorilla/sessions v1.2.1
// )

package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "os"
    "strings"
    "time"

    "github.com/golang-jwt/jwt/v5"
    "github.com/gorilla/mux"
    "github.com/gorilla/sessions"
)

// Configuration
type Config struct {
    ClientID        string
    ClientSecret    string
    RedirectURI     string
    PrivateKeyPath  string
    KeyID          string
    Environment    string
}

func (c *Config) BaseURL() string {
    if c.Environment == "production" {
        return "https://account.oten.com"
    }
    return "https://account.sbx.oten.dev"
}

var (
    config = &Config{
        ClientID:       os.Getenv("OTEN_CLIENT_ID"),
        ClientSecret:   os.Getenv("OTEN_CLIENT_SECRET"),
        RedirectURI:    os.Getenv("OTEN_REDIRECT_URI"),
        PrivateKeyPath: os.Getenv("OTEN_PRIVATE_KEY_PATH"),
        KeyID:         os.Getenv("OTEN_KEY_ID"),
        Environment:   getEnv("OTEN_ENV", "production"),
    }
    store = sessions.NewCookieStore([]byte("your-secret-key-change-in-production"))
)

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

// PKCE helper functions
func generateCodeVerifier() string {
    bytes := make([]byte, 32)
    rand.Read(bytes)
    return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(bytes)
}

func generateCodeChallenge(verifier string) string {
    hash := sha256.Sum256([]byte(verifier))
    return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(hash[:])
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://oten.gitbook.io/identity-support/integration/prerequisites/appendix/sample-code.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
