🔧JAR Complete Implementation Guide

📖 Getting started? Check the Integration Flow Overview to understand the complete integration process.

IMPORTANT: JAR for Confidential Clients Only

Oten Identity Provider requires JAR (JWT-Secured Authorization Request) for CONFIDENTIAL CLIENTS only. Public clients (SPAs/Mobile) must use PKCE instead.

Client Type Requirements:

Supported JAR Algorithms

Oten IDP supports exactly two algorithms:

Algorithm
Key Type
Security Level
Recommended For

HS256

Symmetric (client_secret)

Good

Development, Internal Apps

EdDSA

Asymmetric (Ed25519 key pair)

Better

Production, Public Apps

JAR Structure

Every JAR must include these JWT claims:

Required JWT Claims

{
  "iss": "your_client_id",           // Issuer (your client ID)
  "aud": "https://account.oten.com", // Audience (Oten)
  "iat": 1640995200,                 // Issued at (current timestamp)
  "exp": 1640995500,                 // Expires (iat + 300 seconds max)
  "jti": "unique-request-id",        // JWT ID (unique for each request)
  
  // OAuth parameters
  "client_id": "your_client_id",
  "redirect_uri": "https://yourapp.com/callback",
  "response_type": "code",
  "scope": "openid profile email",
  "state": "random_state_string",
  "code_challenge": "base64url_encoded_challenge",
  "code_challenge_method": "S256"
}

Optional Claims

{
  "nonce": "random_nonce_string",    // For OIDC
  "max_age": 3600,                   // Max authentication age
  "prompt": "login",                 // Force re-authentication
  "ui_locales": "en-US"              // Preferred language
}

Method 1: HS256 Implementation (Simpler)

When to Use HS256

  • ✅ Development and testing

  • ✅ Internal applications

  • ✅ When you want simple setup

  • ❌ Not recommended for public applications

Complete HS256 Example

JavaScript/Node.js

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

async function createJARWithHS256(authParams, clientSecret) {
  const now = Math.floor(Date.now() / 1000);
  
  const jarPayload = {
    // Required JWT claims
    iss: authParams.client_id,
    aud: 'https://account.oten.com',
    iat: now,
    exp: now + 300, // 5 minutes max
    jti: crypto.randomUUID(),
    
    // OAuth parameters
    ...authParams
  };
  
  return jwt.sign(jarPayload, clientSecret, {
    algorithm: 'HS256',
    header: {
      alg: 'HS256',
      typ: 'JWT'
    }
  });
}

// Usage example
const authParams = {
  client_id: 'your_client_id',
  redirect_uri: 'https://yourapp.com/callback',
  response_type: 'code',
  scope: 'openid profile email',
  state: crypto.randomUUID(),
  code_challenge: 'your_code_challenge',
  code_challenge_method: 'S256'
};

const requestJWT = await createJARWithHS256(authParams, process.env.CLIENT_SECRET);
const authURL = `https://account.oten.com/v1/oauth/authorize?client_id=${authParams.client_id}&request=${requestJWT}`;

Python

import jwt
import time
import uuid
import os

def create_jar_with_hs256(auth_params, client_secret):
    now = int(time.time())
    
    jar_payload = {
        # Required JWT claims
        'iss': auth_params['client_id'],
        'aud': 'https://account.oten.com',
        'iat': now,
        'exp': now + 300,  # 5 minutes max
        'jti': str(uuid.uuid4()),
        
        # OAuth parameters
        **auth_params
    }
    
    return jwt.encode(
        jar_payload,
        client_secret,
        algorithm='HS256',
        headers={'alg': 'HS256', 'typ': 'JWT'}
    )

# Usage example
auth_params = {
    'client_id': 'your_client_id',
    'redirect_uri': 'https://yourapp.com/callback',
    'response_type': 'code',
    'scope': 'openid profile email',
    'state': str(uuid.uuid4()),
    'code_challenge': 'your_code_challenge',
    'code_challenge_method': 'S256'
}

request_jwt = create_jar_with_hs256(auth_params, os.getenv('CLIENT_SECRET'))
auth_url = f"https://account.oten.com/v1/oauth/authorize?client_id={auth_params['client_id']}&request={request_jwt}"

Go

package main

import (
    "crypto/rand"
    "encoding/hex"
    "time"
    
    "github.com/golang-jwt/jwt/v5"
)

func createJARWithHS256(authParams map[string]interface{}, clientSecret string) (string, error) {
    now := time.Now().Unix()
    
    // Generate unique JTI
    jtiBytes := make([]byte, 16)
    rand.Read(jtiBytes)
    jti := hex.EncodeToString(jtiBytes)
    
    claims := jwt.MapClaims{
        // Required JWT claims
        "iss": authParams["client_id"],
        "aud": "https://account.oten.com",
        "iat": now,
        "exp": now + 300, // 5 minutes max
        "jti": jti,
    }
    
    // Add OAuth parameters
    for k, v := range authParams {
        claims[k] = v
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(clientSecret))
}

// Usage example
authParams := map[string]interface{}{
    "client_id":              "your_client_id",
    "redirect_uri":           "https://yourapp.com/callback",
    "response_type":          "code",
    "scope":                  "openid profile email",
    "state":                  generateRandomString(32),
    "code_challenge":         "your_code_challenge",
    "code_challenge_method":  "S256",
}

requestJWT, err := createJARWithHS256(authParams, os.Getenv("CLIENT_SECRET"))
if err != nil {
    log.Fatal(err)
}

authURL := fmt.Sprintf("https://account.oten.com/v1/oauth/authorize?client_id=%s&request=%s",
    authParams["client_id"], requestJWT)

🔐 Method 2: EdDSA Implementation (More Secure)

When to Use EdDSA

  • ✅ Production applications

  • ✅ Public applications

  • ✅ When you need maximum security

  • ✅ When you want to follow best practices

Step 1: Generate Ed25519 Key Pair

Using OpenSSL

# Generate private key
openssl genpkey -algorithm Ed25519 -out jar-private-key.pem

# Extract public key
openssl pkey -in jar-private-key.pem -pubout -out jar-public-key.pem

# View keys (for verification)
openssl pkey -in jar-private-key.pem -text -noout
openssl pkey -in jar-public-key.pem -pubin -text -noout

Using Node.js

const crypto = require('crypto');
const fs = require('fs');

// Generate key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem'
  }
});

// Save keys
fs.writeFileSync('jar-private-key.pem', privateKey);
fs.writeFileSync('jar-public-key.pem', publicKey);

Step 2: Register Public Key with Oten

You have two options to register your public key:

Option A: Through Developer Portal

  1. Login to https://developer.oten.com (Coming Soon)

  2. Navigate to your application settings

  3. Upload your public key file (jar-public-key.pem)

  4. Note the Key ID assigned by the system

Option B: Through Support Request

  1. Email your public key to [email protected]

  2. Include your client_id and application name

  3. Wait for confirmation and Key ID

Step 3: Complete EdDSA Implementation

JavaScript/Node.js

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

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

  const jarPayload = {
    // Required JWT claims
    iss: authParams.client_id,
    aud: 'https://account.oten.com',
    iat: now,
    exp: now + 300, // 5 minutes max
    jti: crypto.randomUUID(),

    // OAuth parameters
    ...authParams
  };

  return jwt.sign(jarPayload, privateKey, {
    algorithm: 'EdDSA',
    header: {
      alg: 'EdDSA',
      typ: 'JWT',
      kid: keyId // Must match registered key ID
    }
  });
}

// Usage example
const authParams = {
  client_id: 'your_client_id',
  redirect_uri: 'https://yourapp.com/callback',
  response_type: 'code',
  scope: 'openid profile email',
  state: crypto.randomUUID(),
  code_challenge: 'your_code_challenge',
  code_challenge_method: 'S256'
};

const requestJWT = await createJARWithEdDSA(
  authParams,
  './jar-private-key.pem',
  'your-key-id'
);
const authURL = `https://account.oten.com/v1/oauth/authorize?client_id=${authParams.client_id}&request=${requestJWT}`;

Python

import jwt
import time
import uuid
from cryptography.hazmat.primitives import serialization

def create_jar_with_eddsa(auth_params, private_key_path, key_id):
    now = int(time.time())

    # Load private key
    with open(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': 'https://account.oten.com',
        'iat': now,
        'exp': now + 300,  # 5 minutes max
        'jti': str(uuid.uuid4()),

        # OAuth parameters
        **auth_params
    }

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

# Usage example
auth_params = {
    'client_id': 'your_client_id',
    'redirect_uri': 'https://yourapp.com/callback',
    'response_type': 'code',
    'scope': 'openid profile email',
    'state': str(uuid.uuid4()),
    'code_challenge': 'your_code_challenge',
    'code_challenge_method': 'S256'
}

request_jwt = create_jar_with_eddsa(auth_params, './jar-private-key.pem', 'your-key-id')
auth_url = f"https://account.oten.com/v1/oauth/authorize?client_id={auth_params['client_id']}&request={request_jwt}"

Go

package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "crypto/x509"
    "encoding/hex"
    "encoding/pem"
    "io/ioutil"
    "time"

    "github.com/golang-jwt/jwt/v5"
)

func createJARWithEdDSA(authParams map[string]interface{}, privateKeyPath, keyID string) (string, error) {
    now := time.Now().Unix()

    // Load private key
    keyData, err := ioutil.ReadFile(privateKeyPath)
    if err != nil {
        return "", err
    }

    block, _ := pem.Decode(keyData)
    privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    if err != nil {
        return "", err
    }

    ed25519Key := privateKey.(ed25519.PrivateKey)

    // Generate unique JTI
    jtiBytes := make([]byte, 16)
    rand.Read(jtiBytes)
    jti := hex.EncodeToString(jtiBytes)

    claims := jwt.MapClaims{
        // Required JWT claims
        "iss": authParams["client_id"],
        "aud": "https://account.oten.com",
        "iat": now,
        "exp": now + 300, // 5 minutes max
        "jti": jti,
    }

    // Add OAuth parameters
    for k, v := range authParams {
        claims[k] = v
    }

    token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
    token.Header["kid"] = keyID

    return token.SignedString(ed25519Key)
}

// Usage example
authParams := map[string]interface{}{
    "client_id":              "your_client_id",
    "redirect_uri":           "https://yourapp.com/callback",
    "response_type":          "code",
    "scope":                  "openid profile email",
    "state":                  generateRandomString(32),
    "code_challenge":         "your_code_challenge",
    "code_challenge_method":  "S256",
}

requestJWT, err := createJARWithEdDSA(authParams, "./jar-private-key.pem", "your-key-id")
if err != nil {
    log.Fatal(err)
}

authURL := fmt.Sprintf("https://account.oten.com/v1/oauth/authorize?client_id=%s&request=%s",
    authParams["client_id"], requestJWT)

🚨 Common Errors and Solutions

Error: "invalid_request" (Missing request parameter)

Cause: Missing request parameter in authorization URL Solution: Always include JAR in request parameter

Error: "invalid_request_object" (Invalid JAR signature)

Cause:

  • Wrong signing algorithm (must be HS256 or EdDSA)

  • Wrong private key or client secret

  • Missing or incorrect kid in JWT header (for EdDSA)

Solution:

  • Verify algorithm is HS256 or EdDSA

  • Check private key matches registered public key

  • Ensure kid matches registered Key ID

Error: "invalid_request" (JAR expired)

Cause: JAR exp claim is too old Solution: Set exp to current time + 5 minutes maximum

Error: "invalid_request" (Invalid audience)

Cause: Wrong aud claim in JAR Solution: Use exact audience: https://account.oten.com

Error: "invalid_request" (Invalid issuer)

Cause: iss claim doesn't match client_id Solution: Set iss to your exact client_id

JAR Validation Checklist

Before testing with Oten IDP:

For HS256:

For EdDSA:

Testing Your JAR Implementation

Test JAR Structure

// Decode JAR to verify structure (for debugging only)
const jwt = require('jsonwebtoken');

function debugJAR(jarToken) {
  const decoded = jwt.decode(jarToken, { complete: true });
  console.log('Header:', decoded.header);
  console.log('Payload:', decoded.payload);

  // Verify required claims
  const required = ['iss', 'aud', 'iat', 'exp', 'jti', 'client_id', 'redirect_uri', 'response_type'];
  const missing = required.filter(claim => !decoded.payload[claim]);

  if (missing.length > 0) {
    console.error('Missing required claims:', missing);
  } else {
    console.log('✅ All required claims present');
  }
}

Test with curl

# Test authorization endpoint with JAR
curl -v "https://account.oten.com/v1/oauth/authorize?client_id=your_client_id&request=your_jar_token"


Remember: JAR is required for confidential clients only. Public clients must use PKCE instead.

Need Help with JAR Implementation?

If your application cannot implement JAR due to technical constraints, please contact our support team to discuss enabling traditional OAuth flow as a temporary solution:

📧 Contact Support: [email protected]

Include in your request:

  • Application details and technical constraints

  • Reason why JAR cannot be implemented

  • Security measures you have in place

  • Timeline for potential JAR migration

Security Notice: Traditional OAuth flow has lower security compared to JAR and should only be used temporarily while planning JAR implementation.

Last updated