> For the complete documentation index, see [llms.txt](https://oten.gitbook.io/identity-support/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://oten.gitbook.io/identity-support/integration/prerequisites/jar-complete-implementation-guide.md).

# JAR Complete Implementation Guide

> 📖 **Getting started?** Check the [Integration Flow Overview](broken://pages/iMMrTD1lQOMbxxvheKAz) 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:

* **Confidential Clients**: JAR is REQUIRED
* **Public Clients**: JAR is FORBIDDEN, use [PKCE Implementation Guide](https://gitlab.silvertiger.tech/documents/idp/-/blob/main/developer-guide/pkce-implementation-guide.md)

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

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

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

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

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

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

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

```javascript
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 <support@oten.dev>
2. Include your client\_id and application name
3. Wait for confirmation and Key ID

### Step 3: Complete EdDSA Implementation

#### JavaScript/Node.js

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

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

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

* [ ] Client secret available and correct
* [ ] JAR includes all required JWT claims (`iss`, `aud`, `iat`, `exp`, `jti`)
* [ ] JAR includes all OAuth parameters
* [ ] JAR signed with HS256 algorithm
* [ ] JAR expiration set to reasonable time (≤ 5 minutes)
* [ ] Authorization URL only contains `client_id` and `request` parameters

### For EdDSA:

* [ ] Ed25519 key pair generated
* [ ] Public key registered with Oten
* [ ] Key ID obtained from registration
* [ ] JAR includes all required JWT claims (`iss`, `aud`, `iat`, `exp`, `jti`)
* [ ] JAR includes all OAuth parameters
* [ ] JAR signed with EdDSA algorithm
* [ ] JAR `kid` header matches registered Key ID
* [ ] JAR expiration set to reasonable time (≤ 5 minutes)
* [ ] Authorization URL only contains `client_id` and `request` parameters

## Testing Your JAR Implementation

### Test JAR Structure

```javascript
// 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

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

## 🔗 Related Documentation

* [Prerequisites](broken://pages/qErS2kM8hpRocnYPaDQW) - JAR setup requirements
* [Step 3: Authorization Flow](broken://pages/1zKgOda9hrWwNX8K6Zea) - Complete JAR implementation
* [Configuration Reference](broken://pages/XsyPoCRDPUpz0vcP6KkD) - Endpoints and settings
* [Common Errors](broken://pages/ChQqcK8cqX9LLgyQtBJT) - JAR-related error troubleshooting

***

## 🧭 Navigation

* **← Previous**: [Prerequisites](broken://pages/qErS2kM8hpRocnYPaDQW) - Environment setup
* **↑ Overview**: [Integration Flow Overview](broken://pages/iMMrTD1lQOMbxxvheKAz) - See the big picture
* **→ Next**: [Step 1: Choose OAuth Library](broken://pages/CuJNdfiNnGHuqh7gDIK2) - Select JAR-compatible library

***

**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**: <support@oten.dev>

**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.
