Sample Code
This document provides complete, working code examples for integrating with Oten IDP across multiple programming languages.
Prerequisites
Before using these examples:
Register your application with Oten IDP
Obtain your
client_idandclient_secret(if applicable)Set up your redirect URI
Choose your JAR signing method (HS256 or EdDSA)
Environment Variables
All examples use these environment variables:
# 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
// 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
# 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.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[:])
}Last updated