> 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/pkce-implementation-guide/step-3-implement-authorization-flow.md).

# Step 3: Implement Authorization Flow

Now it's time to implement the OAuth 2.0 authorization flow.

**IMPORTANT: Authorization requirements depend on your client type:**

* **Confidential Clients (Server-side)**: JAR (JWT-Secured Authorization Request) is REQUIRED
* **Public Clients (SPAs/Mobile)**: PKCE is REQUIRED, JAR is FORBIDDEN

> 📖 **Need context?** Check the [Integration Flow Overview](broken://pages/iMMrTD1lQOMbxxvheKAz) to see how this step fits into the complete process.

## 🎯 What You'll Learn

In this step, you will learn:

**For Confidential Clients (Server-side applications):**

* Create JAR (JWT-Secured Authorization Request) - REQUIRED
* Sign authorization parameters in JWT format
* Handle state parameter for CSRF protection
* Redirect users to Oten IDP with signed requests

**For Public Clients (SPAs/Mobile apps):**

* Implement PKCE (Proof Key for Code Exchange) - REQUIRED
* Generate secure code verifier and challenge
* Handle state parameter for CSRF protection
* Use direct authorization parameters (NO JAR)

> 📖 **Public Client (SPA/Mobile)?** For comprehensive PKCE implementation with complete code examples, see the [PKCE Implementation Guide](https://gitlab.silvertiger.tech/documents/idp/-/blob/main/developer-guide/pkce-implementation-guide.md) or [PKCE without JAR Guide](https://gitlab.silvertiger.tech/documents/idp/-/blob/main/developer-guide/pkce-without-jar.md).

## 🔄 Authorization Flow Overview

![Authorization Flow Overview](/files/j9pdcZzaiCKiO0TQcSwK)

This diagram shows the authorization flow. The specific requirements depend on your client type:

* **Confidential Clients**: Must use JAR (JWT-Secured Authorization Request)
* **Public Clients**: Must use PKCE with direct parameters (JAR forbidden)

## 🔒 JAR (JWT-Secured Authorization Request) - For Confidential Clients

**Confidential clients (server-side applications) MUST use JAR** for enhanced security. Public clients MUST NOT use JAR.

**Can't implement JAR?** If your confidential client application cannot support JAR due to technical constraints, contact <support@oten.dev> to discuss alternative solutions.

### Why JAR is Required for Confidential Clients

```javascript
// ❌ This will NOT work for confidential clients
const authURL = `https://account.oten.com/v1/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectURI}&response_type=code&scope=openid profile email&state=${state}`;

// ✅ Confidential clients MUST use JAR
const requestJWT = await createJAR(authParams, privateKey);
const authURL = `https://account.oten.com/v1/oauth/authorize?client_id=${clientId}&request=${requestJWT}`;

// ✅ Public clients MUST use direct parameters with PKCE
const authURL = `https://account.oten.com/v1/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectURI}&response_type=code&scope=openid profile email&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
```

## JAR Parameters Explained

JAR (JWT Authorization Request) consists of two main parts: **JWT Claims** and **OAuth Parameters**. All OAuth parameters must be included in the JWT payload instead of URL query parameters.

### Standard JWT Claims (Required)

| Parameter | Description                                        | Example                      | Notes                   |
| --------- | -------------------------------------------------- | ---------------------------- | ----------------------- |
| `iss`     | **Issuer** - Your client ID                        | `"your-client-id"`           | Must match client\_id   |
| `aud`     | **Audience** - Oten IDP endpoint                   | `"https://account.oten.com"` | Fixed for Oten          |
| `iat`     | **Issued At** - JWT creation time (Unix timestamp) | `1672531200`                 | Current time            |
| `exp`     | **Expiration** - JWT expiry time (Unix timestamp)  | `1672531500`                 | Max 5 minutes after iat |
| `jti`     | **JWT ID** - Unique identifier for request         | `"uuid-v4-string"`           | Prevents replay attacks |

### 🌐 OAuth Parameters (Required)

| Parameter       | Description                      | Value                            | Notes                                     |
| --------------- | -------------------------------- | -------------------------------- | ----------------------------------------- |
| `client_id`     | Application client ID            | `"your-client-id"`               | Must match iss                            |
| `redirect_uri`  | Callback URL after authorization | `"https://yourapp.com/callback"` | Must be pre-registered                    |
| `response_type` | Desired response type            | `"code"`                         | Always "code" for Authorization Code flow |
| `scope`         | Requested access permissions     | `"openid profile email"`         | Minimum requires "openid"                 |
| `state`         | CSRF protection parameter        | `"random-string-32-chars"`       | Protects against CSRF                     |

### 🔐 PKCE Parameters (REQUIRED for Public Clients)

| Parameter               | Description                   | Example                                         | Notes             |
| ----------------------- | ----------------------------- | ----------------------------------------------- | ----------------- |
| `code_challenge`        | SHA256 hash of code\_verifier | `"E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"` | Base64URL encoded |
| `code_challenge_method` | Hash method                   | `"S256"`                                        | Always "S256"     |

### 🎨 UI/UX Parameters (Optional)

| Parameter    | Description                         | Default Value | Examples                                             |
| ------------ | ----------------------------------- | ------------- | ---------------------------------------------------- |
| `prompt`     | UI display behavior                 | `"consent"`   | `"none"`, `"login"`, `"consent"`, `"select_account"` |
| `ui_locales` | Interface language                  | `"en-US"`     | `"vi-VN"`, `"en-US"`, `"ja-JP"`                      |
| `login_hint` | Email/username hint                 | -             | `"user@example.com"`                                 |
| `max_age`    | Max time since last login (seconds) | `3600`        | `0` (force re-auth), `7200`                          |

### 🏢 Oten Specific Parameters (Optional)

| Parameter        | Description                   | Example                | Notes                 |
| ---------------- | ----------------------------- | ---------------------- | --------------------- |
| `workspace_hint` | Workspace ID hint             | `"workspace-123"`      | Auto-select workspace |
| `nonce`          | Random value to link ID token | `"random-nonce-value"` | Additional security   |

### 📝 Complete JAR Payload Example

```javascript
// JAR Payload for CONFIDENTIAL CLIENTS ONLY
const jarPayload = {
  // === JWT Claims (Required) ===
  iss: "your-client-id",                    // Issuer
  aud: "https://account.oten.com", // Audience
  iat: 1672531200,                          // Issued at (now)
  exp: 1672531500,                          // Expires (5 minutes later)
  jti: "550e8400-e29b-41d4-a716-446655440000", // Unique ID

  // === OAuth Parameters (Required) ===
  client_id: "your-client-id",              // Client ID
  redirect_uri: "https://yourapp.com/callback", // Callback URL
  response_type: "code",                    // Authorization code flow
  scope: "openid profile email",           // Requested scopes
  state: "abc123def456ghi789",              // CSRF protection

  // === PKCE Parameters (Optional for confidential clients) ===
  code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
  code_challenge_method: "S256",

  // === UI/UX Parameters (Optional) ===
  prompt: "consent",                        // Always show consent screen
  ui_locales: "en-US",                      // English interface
  login_hint: "user@company.com",           // Email hint
  max_age: 3600,                           // Re-auth if > 1 hour

  // === Oten Specific (Optional) ===
  workspace_hint: "workspace-123",          // Workspace hint
  nonce: "random-nonce-for-security"        // ID token security
};

// ⚠️ PUBLIC CLIENTS: DO NOT USE JAR - Use direct parameters instead
// See PKCE without JAR guide for public client implementation
```

## 🎯 JAR Parameter Usage Examples

### Prompt Parameter Values

```javascript
// Prompt parameter values and their meanings:
const promptOptions = {
  "none": "No UI shown, auto-authorize if already logged in",
  "login": "Force user to login again",
  "consent": "Show consent screen (default)",
};

// Usage example:
const jarPayload = {
  // ... other parameters
  prompt: "login",  // Force re-login
  max_age: 0       // Combined with max_age=0 to force re-authentication
};
```

### UI Locales Support (coming soon)

```javascript
// Supported languages:
const supportedLocales = {
  "en-US": "English (United States)",
  "vi-VN": "Tiếng Việt (Vietnam)",
  "ar-AE": "United Arab Emirates",
};

// Multi-locale example:
const jarPayload = {
  // ... other parameters
  ui_locales: "vi-VN en-US",  // Prefer Vietnamese, fallback to English
};
```

### Workspace Hint Usage (coming soon)

```javascript
// Auto-select workspace for user:
const jarPayload = {
  // ... other parameters
  workspace_hint: "workspace-abc123",  // User will be redirected to this workspace
  login_hint: "user@company.com"       // Combined with login hint (coming soon)
};
```

### Advanced Security with Nonce

```javascript
// Generate nonce for ID token security:
function generateNonce() {
  return crypto.randomBytes(16).toString('hex');
}

const jarPayload = {
  // ... other parameters
  nonce: generateNonce(),  // Will be included in ID token
};

// Verify nonce in ID token after receiving callback
function verifyIDToken(idToken, expectedNonce) {
  const decoded = jwt.decode(idToken);
  if (decoded.nonce !== expectedNonce) {
    throw new Error('Nonce mismatch - possible token replay attack');
  }
}
```

### JAR Implementation (Confidential Clients Only)

Oten IDP supports **two signing methods** for confidential clients:

#### Method 1: HS256 (Client Secret) - Simpler

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

async function createJAR_HS256(authParams, clientSecret) {
  const now = Math.floor(Date.now() / 1000);

  const jarPayload = {
    // Standard JWT claims
    iss: authParams.client_id, // Issuer (your client ID)
    aud: 'https://account.oten.com', // Audience (Oten)
    iat: now, // Issued at
    exp: now + 300, // Expires in 5 minutes
    jti: crypto.randomUUID(), // Unique identifier

    // OAuth parameters (ALL parameters must be in JAR)
    client_id: authParams.client_id,
    redirect_uri: authParams.redirect_uri,
    response_type: authParams.response_type,
    scope: authParams.scope,
    state: authParams.state,

    // PKCE parameters (if using)
    code_challenge: authParams.code_challenge,
    code_challenge_method: authParams.code_challenge_method,

    // Additional parameters
    ui_locales: 'en-US',
    prompt: 'consent'
  };

  // Sign the JWT with client secret (HS256)
  const requestJWT = jwt.sign(jarPayload, clientSecret, {
    algorithm: 'HS256'
  });

  return requestJWT;
}
```

#### Method 2: EdDSA (Ed25519 Key Pair) - More Secure

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

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

  const jarPayload = {
    // Standard JWT claims
    iss: authParams.client_id, // Issuer (your client ID)
    aud: 'https://account.oten.com', // Audience (Oten)
    iat: now, // Issued at
    exp: now + 300, // Expires in 5 minutes
    jti: crypto.randomUUID(), // Unique identifier

    // OAuth parameters (ALL parameters must be in JAR)
    client_id: authParams.client_id,
    redirect_uri: authParams.redirect_uri,
    response_type: authParams.response_type,
    scope: authParams.scope,
    state: authParams.state,

    // PKCE parameters (if using)
    code_challenge: authParams.code_challenge,
    code_challenge_method: authParams.code_challenge_method,

    // Additional parameters
    ui_locales: 'en-US',
    prompt: 'consent'
  };

  // Sign the JWT with Ed25519 private key
  const requestJWT = jwt.sign(jarPayload, privateKey, {
    algorithm: 'EdDSA',
    keyid: 'jar-key-1' // Must match your JWKS
  });

  return requestJWT;
}

### Complete JAR Authorization Flow

```javascript
const crypto = require('crypto');

function generateState() {
  return crypto.randomBytes(32).toString('hex');
}

function generatePKCE() {
  // Generate code verifier (43-128 characters)
  const codeVerifier = crypto.randomBytes(96).toString('base64url');

  // Generate code challenge
  const codeChallenge = crypto
    .createHash('sha256')
    .update(codeVerifier)
    .digest('base64url');

  return {
    codeVerifier,
    codeChallenge,
    codeChallengeMethod: 'S256'
  };
}

// Option 1: Using HS256 (Client Secret)
async function createAuthorizationURL_HS256() {
  const state = generateState();
  const pkce = generatePKCE();

  // Store session data
  req.session.oauthState = state;
  req.session.codeVerifier = pkce.codeVerifier;

  const authParams = {
    client_id: process.env.OTEN_CLIENT_ID,
    redirect_uri: process.env.OTEN_REDIRECT_URI,
    response_type: 'code',
    scope: 'openid profile email',
    state: state,
    code_challenge: pkce.codeChallenge,
    code_challenge_method: pkce.codeChallengeMethod
  };

  // Create JAR with client secret (HS256)
  const requestJWT = await createJAR_HS256(authParams, process.env.OTEN_CLIENT_SECRET);

  // Create authorization URL with JAR
  const authURL = new URL('https://account.oten.com/v1/oauth/authorize');
  authURL.searchParams.set('client_id', authParams.client_id);
  authURL.searchParams.set('request', requestJWT);

  return authURL.toString();
}

// Option 2: Using EdDSA (Ed25519 Key Pair)
async function createAuthorizationURL_EdDSA() {
  const state = generateState();
  const pkce = generatePKCE();

  // Store session data
  req.session.oauthState = state;
  req.session.codeVerifier = pkce.codeVerifier;

  const authParams = {
    client_id: process.env.OTEN_CLIENT_ID,
    redirect_uri: process.env.OTEN_REDIRECT_URI,
    response_type: 'code',
    scope: 'openid profile email',
    state: state,
    code_challenge: pkce.codeChallenge,
    code_challenge_method: pkce.codeChallengeMethod
  };

  // Create JAR with Ed25519 private key
  const requestJWT = await createJAR_EdDSA(authParams, process.env.JAR_PRIVATE_KEY_PATH);

  // Create authorization URL with JAR
  const authURL = new URL('https://account.oten.com/v1/oauth/authorize');
  authURL.searchParams.set('client_id', authParams.client_id);
  authURL.searchParams.set('request', requestJWT);

  return authURL.toString();
}
````

## 🔑 JAR Key Management

### For HS256 (Client Secret) - No Key Generation Needed

When using HS256, you use your existing client secret:

```javascript
// No key generation needed - use your client secret
const clientSecret = process.env.OTEN_CLIENT_SECRET;

// Create JAR with HS256
const requestJWT = await createJAR_HS256(authParams, clientSecret);
```

### For EdDSA (Ed25519) - Key Generation Required

When using EdDSA, you need to generate Ed25519 key pairs:

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

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

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

  return { publicKey, privateKey };
}

// Create JWKS for your Ed25519 public key
function createJWKS(publicKey) {
  const jwk = crypto.createPublicKey(publicKey).export({
    format: 'jwk'
  });

  return {
    keys: [{
      ...jwk,
      kid: 'jar-key-1', // Key ID
      use: 'sig', // Signature use
      alg: 'EdDSA' // Algorithm for Ed25519
    }]
  };
}
```

## 🌐 Implementation Examples

### Express.js Implementation

#### Option 1: Using HS256 (Client Secret)

```javascript
const express = require('express');
const session = require('express-session');
const jwt = require('jsonwebtoken');
const app = express();

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 30 * 60 * 1000 // 30 minutes
  }
}));

// Login route with HS256 JAR
app.get('/login', async (req, res) => {
  try {
    const authURL = await createAuthorizationURL_HS256();
    res.redirect(authURL);
  } catch (error) {
    console.error('JAR login error:', error);
    res.status(500).send('Login failed');
  }
});

// No JWKS endpoint needed for HS256
```

#### Option 2: Using EdDSA (Ed25519 Key Pair)

```javascript
const express = require('express');
const session = require('express-session');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const app = express();

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 30 * 60 * 1000 // 30 minutes
  }
}));

// JWKS endpoint (REQUIRED for EdDSA)
app.get('/.well-known/jwks.json', (req, res) => {
  const jwks = createJWKS();
  res.json(jwks);
});

// Login route with EdDSA JAR
app.get('/login', async (req, res) => {
  try {
    const authURL = await createAuthorizationURL_EdDSA();
    res.redirect(authURL);
  } catch (error) {
    console.error('JAR login error:', error);
    res.status(500).send('Login failed');
  }
});

function createJWKS() {
  const publicKeyPem = fs.readFileSync('jar-public-key.pem', 'utf8');
  const publicKey = crypto.createPublicKey(publicKeyPem);
  const jwk = publicKey.export({ format: 'jwk' });

  return {
    keys: [{
      ...jwk,
      kid: 'jar-key-1',
      use: 'sig',
      alg: 'EdDSA'
    }]
  };
}
```

### React SPA Implementation

**Note**: For SPAs, you'll need a backend service to create JAR since private keys cannot be stored in browsers.

```javascript
// AuthService.js
class AuthService {
  constructor() {
    this.clientId = process.env.REACT_APP_CLIENT_ID;
    this.redirectUri = `${window.location.origin}/callback`;
  }

  async login() {
    try {
      // Generate PKCE parameters
      const pkce = await this.generatePKCE();
      const state = this.generateState();

      // Store in session storage
      sessionStorage.setItem('code_verifier', pkce.codeVerifier);
      sessionStorage.setItem('oauth_state', state);

      // Call backend to create JAR
      const jarResponse = await fetch('/api/create-jar', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          client_id: this.clientId,
          redirect_uri: this.redirectUri,
          response_type: 'code',
          scope: 'openid profile email',
          state: state,
          code_challenge: pkce.codeChallenge,
          code_challenge_method: 'S256'
        })
      });

      const { requestJWT } = await jarResponse.json();

      // Redirect to Oten with JAR
      const authURL = new URL('https://account.oten.com/v1/oauth/authorize');
      authURL.searchParams.set('client_id', this.clientId);
      authURL.searchParams.set('request', requestJWT);

      window.location.href = authURL.toString();
    } catch (error) {
      console.error('Login failed:', error);
      throw error;
    }
  }

  async generatePKCE() {
    const codeVerifier = this.generateRandomString(128);
    const encoder = new TextEncoder();
    const data = encoder.encode(codeVerifier);
    const digest = await window.crypto.subtle.digest('SHA-256', data);
    const codeChallenge = this.base64URLEncode(digest);

    return { codeVerifier, codeChallenge };
  }

  generateState() {
    return this.generateRandomString(32);
  }

  generateRandomString(length) {
    const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
    let result = '';
    for (let i = 0; i < length; i++) {
      result += charset.charAt(Math.floor(Math.random() * charset.length));
    }
    return result;
  }

  base64URLEncode(buffer) {
    const bytes = new Uint8Array(buffer);
    const binary = String.fromCharCode(...bytes);
    return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
  }
}

// Backend endpoint to create JAR
app.post('/api/create-jar', async (req, res) => {
  try {
    const authParams = req.body;
    const requestJWT = await createJAR(authParams, process.env.JAR_PRIVATE_KEY_PATH);
    res.json({ requestJWT });
  } catch (error) {
    console.error('JAR creation failed:', error);
    res.status(500).json({ error: 'Failed to create JAR' });
  }
});
```

### Python Flask Implementation

```python
from flask import Flask, session, redirect, url_for, request
from authlib.integrations.flask_client import OAuth
import secrets
import hashlib
import base64

app = Flask(__name__)
app.secret_key = os.environ.get('SESSION_SECRET')

oauth = OAuth(app)
oten = oauth.register(
    name='oten',
    client_id=os.environ.get('OTEN_CLIENT_ID'),
    client_secret=os.environ.get('OTEN_CLIENT_SECRET'),
    server_metadata_url='https://account.oten.com/.well-known/openid_configuration',
    client_kwargs={'scope': 'openid profile email'}
)

def generate_pkce():
    """Generate PKCE code verifier and challenge"""
    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(96)).decode('utf-8').rstrip('=')
    code_challenge = base64.urlsafe_b64encode(
        hashlib.sha256(code_verifier.encode('utf-8')).digest()
    ).decode('utf-8').rstrip('=')
    
    return code_verifier, code_challenge

@app.route('/login')
def login():
    # Generate PKCE parameters
    code_verifier, code_challenge = generate_pkce()
    
    # Store code verifier in session
    session['code_verifier'] = code_verifier
    
    # Create authorization URL with PKCE
    redirect_uri = url_for('callback', _external=True)
    return oten.authorize_redirect(
        redirect_uri,
        code_challenge=code_challenge,
        code_challenge_method='S256'
    )

@app.route('/callback')
def callback():
    # Handle the callback (covered in next step)
    pass
```

## 🎛️ Advanced JAR Parameters

### Custom Parameters in JAR

```javascript
async function createAdvancedJAR(authParams, privateKeyPath, options = {}) {
  const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
  const now = Math.floor(Date.now() / 1000);

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

    // Required OAuth parameters
    client_id: authParams.client_id,
    redirect_uri: authParams.redirect_uri,
    response_type: authParams.response_type,
    scope: authParams.scope,
    state: authParams.state,

    // PKCE parameters
    code_challenge: authParams.code_challenge,
    code_challenge_method: authParams.code_challenge_method,

    // Advanced parameters
    prompt: options.prompt || 'consent',
    ui_locales: options.uiLocales || 'en-US',
    login_hint: options.loginHint,
    max_age: options.maxAge || 3600,

    // Custom Oten parameters
    workspace_hint: options.workspaceHint,

    // Additional security parameters
    nonce: crypto.randomBytes(16).toString('hex')
  };

  // Remove undefined values
  Object.keys(jarPayload).forEach(key => {
    if (jarPayload[key] === undefined) {
      delete jarPayload[key];
    }
  });

  return jwt.sign(jarPayload, privateKey, {
    algorithm: 'RS256',
    keyid: process.env.JAR_KEY_ID || 'jar-key-1'
  });
}
```

### JAR with Different Options

```javascript
async function createAuthorizationURL(options = {}) {
  const state = generateState();
  const pkce = generatePKCE();

  // Store session data
  req.session.oauthState = state;
  req.session.codeVerifier = pkce.codeVerifier;

  const authParams = {
    client_id: process.env.OTEN_CLIENT_ID,
    redirect_uri: process.env.OTEN_REDIRECT_URI,
    response_type: 'code',
    scope: options.scope || 'openid profile email',
    state: state,
    code_challenge: pkce.codeChallenge,
    code_challenge_method: 'S256'
  };

  // Create JAR with custom options
  const requestJWT = await createAdvancedJAR(authParams, process.env.JAR_PRIVATE_KEY_PATH, {
    prompt: options.prompt,
    loginHint: options.loginHint,
    workspaceHint: options.workspaceHint,
    uiLocales: options.uiLocales,
    maxAge: options.maxAge
  });

  // Create authorization URL (JAR is REQUIRED)
  const authURL = new URL('https://account.oten.com/v1/oauth/authorize');
  authURL.searchParams.set('client_id', authParams.client_id);
  authURL.searchParams.set('request', requestJWT);

  return authURL.toString();
}

// Usage examples
const basicAuthURL = await createAuthorizationURL();
const forcedConsentURL = await createAuthorizationURL({ prompt: 'consent' });
const workspaceHintURL = await createAuthorizationURL({ workspaceHint: 'workspace-123' });
```

## JAR Parameter Validation

### Required Parameter Validation

```javascript
function validateJARParameters(authParams) {
  const errors = [];

  // Validate required OAuth parameters
  const requiredParams = ['client_id', 'redirect_uri', 'response_type', 'scope', 'state'];
  requiredParams.forEach(param => {
    if (!authParams[param]) {
      errors.push(`Missing required parameter: ${param}`);
    }
  });

  // Validate response_type
  if (authParams.response_type !== 'code') {
    errors.push('response_type must be "code" for Authorization Code flow');
  }

  // Validate scope
  if (authParams.scope && !authParams.scope.includes('openid')) {
    errors.push('scope must include "openid" for OpenID Connect');
  }

  // Validate redirect_uri format
  if (authParams.redirect_uri) {
    try {
      new URL(authParams.redirect_uri);
    } catch (e) {
      errors.push('redirect_uri must be a valid URL');
    }
  }

  // Validate PKCE parameters
  if (authParams.code_challenge) {
    if (!authParams.code_challenge_method) {
      errors.push('code_challenge_method is required when using PKCE');
    } else if (authParams.code_challenge_method !== 'S256') {
      errors.push('code_challenge_method must be "S256"');
    }
  }

  // Validate state length (recommended 32+ characters)
  if (authParams.state && authParams.state.length < 32) {
    errors.push('state parameter should be at least 32 characters for security');
  }

  return errors;
}

// Usage example:
const authParams = {
  client_id: process.env.OTEN_CLIENT_ID,
  redirect_uri: process.env.OTEN_REDIRECT_URI,
  response_type: 'code',
  scope: 'openid profile email',
  state: generateState(),
  code_challenge: pkce.codeChallenge,
  code_challenge_method: 'S256'
};

const validationErrors = validateJARParameters(authParams);
if (validationErrors.length > 0) {
  throw new Error('JAR validation failed: ' + validationErrors.join(', '));
}
```

### JWT Claims Validation

```javascript
function validateJWTClaims(jarPayload) {
  const errors = [];
  const now = Math.floor(Date.now() / 1000);

  // Validate required JWT claims
  const requiredClaims = ['iss', 'aud', 'iat', 'exp', 'jti'];
  requiredClaims.forEach(claim => {
    if (!jarPayload[claim]) {
      errors.push(`Missing required JWT claim: ${claim}`);
    }
  });

  // Validate issuer matches client_id
  if (jarPayload.iss !== jarPayload.client_id) {
    errors.push('iss (issuer) must match client_id');
  }

  // Validate audience
  if (jarPayload.aud !== 'https://account.oten.com') {
    errors.push('aud (audience) must be Oten IDP endpoint');
  }

  // Validate timing
  if (jarPayload.iat > now + 60) {
    errors.push('iat (issued at) cannot be in the future');
  }

  if (jarPayload.exp <= now) {
    errors.push('exp (expiration) must be in the future');
  }

  if (jarPayload.exp > jarPayload.iat + 300) {
    errors.push('exp (expiration) should not be more than 5 minutes after iat');
  }

  // Validate JTI format (should be UUID)
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  if (jarPayload.jti && !uuidRegex.test(jarPayload.jti)) {
    errors.push('jti should be a valid UUID format');
  }

  return errors;
}
```

## 🚨 Common JAR Parameter Errors

### Error: "Invalid request parameter"

```javascript
// ❌ Problem: Missing required parameters in JAR
const incompleteJAR = {
  iss: "client-id",
  aud: "https://account.oten.com",
  // Missing: iat, exp, jti, client_id, redirect_uri, etc.
};

// ✅ Solution: Include all required parameters
const completeJAR = {
  // JWT Claims
  iss: "client-id",
  aud: "https://account.oten.com",
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 300,
  jti: crypto.randomUUID(),

  // OAuth Parameters
  client_id: "client-id",
  redirect_uri: "https://app.com/callback",
  response_type: "code",
  scope: "openid profile email",
  state: "secure-random-state"
};
```

### Error: "JWT signature verification failed"

```javascript
// ❌ Problem: Wrong signing algorithm or key
const wrongAlgorithm = jwt.sign(payload, secret, { algorithm: 'RS256' }); // Not supported

// ✅ Solution: Use supported algorithms
const correctHS256 = jwt.sign(payload, clientSecret, { algorithm: 'HS256' });
const correctEdDSA = jwt.sign(payload, privateKey, { algorithm: 'EdDSA', keyid: 'jar-key-1' });
```

### Error: "Request JWT expired"

```javascript
// ❌ Problem: JAR expired or wrong timing
const expiredJAR = {
  iat: Math.floor(Date.now() / 1000) - 600, // 10 minutes ago
  exp: Math.floor(Date.now() / 1000) - 300, // 5 minutes ago (expired)
};

// ✅ Solution: Proper timing
const validJAR = {
  iat: Math.floor(Date.now() / 1000),       // Now
  exp: Math.floor(Date.now() / 1000) + 300, // 5 minutes from now
};
```

### Error: "Invalid redirect\_uri"

```javascript
// ❌ Problem: Redirect URI not registered or mismatched
const wrongRedirectURI = {
  redirect_uri: "https://different-domain.com/callback" // Not registered
};

// ✅ Solution: Use exact registered URI
const correctRedirectURI = {
  redirect_uri: "https://yourapp.com/callback" // Must match registration exactly
};
```

## 🧪 Testing JAR Implementation

### Test JAR Creation

```javascript
async function testJARCreation() {
  console.log('=== Testing JAR Creation ===');

  try {
    const authParams = {
      client_id: process.env.OTEN_CLIENT_ID,
      redirect_uri: process.env.OTEN_REDIRECT_URI,
      response_type: 'code',
      scope: 'openid profile email',
      state: 'test-state-123',
      code_challenge: 'test-challenge',
      code_challenge_method: 'S256'
    };

    const requestJWT = await createJAR(authParams, process.env.JAR_PRIVATE_KEY_PATH);
    console.log('✅ JAR created successfully');
    console.log('JAR length:', requestJWT.length);

    // Decode JWT to verify structure (without verification)
    const decoded = jwt.decode(requestJWT, { complete: true });
    console.log('JAR header:', decoded.header);
    console.log('JAR payload keys:', Object.keys(decoded.payload));

    return requestJWT;
  } catch (error) {
    console.error('❌ JAR creation failed:', error);
    throw error;
  }
}
```

### Test JWKS Endpoint

```javascript
async function testJWKSEndpoint() {
  console.log('=== Testing JWKS Endpoint ===');

  try {
    const response = await fetch('https://yourapp.com/.well-known/jwks.json');

    if (!response.ok) {
      throw new Error(`JWKS endpoint returned ${response.status}`);
    }

    const jwks = await response.json();

    // Validate JWKS structure
    if (!jwks.keys || !Array.isArray(jwks.keys)) {
      throw new Error('Invalid JWKS structure');
    }

    if (jwks.keys.length === 0) {
      throw new Error('No keys in JWKS');
    }

    const key = jwks.keys[0];
    const requiredFields = ['kty', 'use', 'kid', 'alg', 'n', 'e'];
    const missingFields = requiredFields.filter(field => !key[field]);

    if (missingFields.length > 0) {
      throw new Error(`Missing JWKS fields: ${missingFields.join(', ')}`);
    }

    console.log('✅ JWKS endpoint is valid');
    console.log('Key ID:', key.kid);
    console.log('Algorithm:', key.alg);

  } catch (error) {
    console.error('❌ JWKS endpoint test failed:', error);
    throw error;
  }
}
```

### Validate JAR Authorization URL

```javascript
function validateJARAuthorizationURL(url) {
  const urlObj = new URL(url);
  const params = urlObj.searchParams;

  // For JAR, only client_id and request parameters are required
  const required = ['client_id', 'request'];
  const missing = required.filter(param => !params.has(param));

  if (missing.length > 0) {
    throw new Error(`Missing required JAR parameters: ${missing.join(', ')}`);
  }

  const requestJWT = params.get('request');

  // Basic JWT format validation
  const jwtParts = requestJWT.split('.');
  if (jwtParts.length !== 3) {
    throw new Error('Invalid JWT format in request parameter');
  }

  try {
    // Decode header and payload (without verification)
    const header = JSON.parse(atob(jwtParts[0]));
    const payload = JSON.parse(atob(jwtParts[1]));

    // Validate header algorithm
    const supportedAlgorithms = ['HS256', 'EdDSA'];
    if (!supportedAlgorithms.includes(header.alg)) {
      throw new Error('JAR must use HS256 or EdDSA algorithm');
    }

    // Kid is only required for EdDSA (not for HS256)
    if (header.alg === 'EdDSA' && !header.kid) {
      throw new Error('JAR header must include kid (key ID) for EdDSA');
    }

    // Validate payload
    const requiredClaims = ['iss', 'aud', 'iat', 'exp', 'client_id', 'redirect_uri', 'response_type'];
    const missingClaims = requiredClaims.filter(claim => !payload[claim]);

    if (missingClaims.length > 0) {
      throw new Error(`Missing JAR claims: ${missingClaims.join(', ')}`);
    }

    console.log('✅ JAR authorization URL validation passed');
    return true;

  } catch (error) {
    if (error.message.includes('Missing JAR claims')) {
      throw error;
    }
    throw new Error('Failed to decode JAR: ' + error.message);
  }
}
```

## Authorization Flow Checklist

Before proceeding to the callback handling, verify your implementation based on client type:

### For Confidential Clients (Server-side) - JAR Required

#### HS256 (Client Secret)

* [ ] **JAR is implemented** (REQUIRED for confidential clients)
* [ ] Client secret is stored securely
* [ ] JWT library supports HS256 signing
* [ ] State parameter is generated and included in JAR
* [ ] All OAuth parameters are included in JAR payload
* [ ] JAR expiration time is set (5 minutes recommended)
* [ ] Authorization URL only contains client\_id and request parameters
* [ ] Session management is working
* [ ] Redirect URI matches registration

#### EdDSA (Ed25519 Key Pair)

* [ ] **JAR is implemented** (REQUIRED for confidential clients)
* [ ] Ed25519 key pair generated and stored securely
* [ ] JWKS endpoint is accessible and returns valid format
* [ ] JWT library supports EdDSA signing
* [ ] Public key registered with Oten IDP
* [ ] State parameter is generated and included in JAR
* [ ] All OAuth parameters are included in JAR payload
* [ ] JAR expiration time is set (5 minutes recommended)
* [ ] Authorization URL only contains client\_id and request parameters
* [ ] Session management is working
* [ ] Redirect URI matches registration

### For Public Clients (SPAs/Mobile) - PKCE Required, JAR Forbidden

* [ ] **PKCE is implemented** (REQUIRED for public clients)
* [ ] **JAR is NOT used** (FORBIDDEN for public clients)
* [ ] Code verifier generated with sufficient entropy (128 characters)
* [ ] Code challenge generated using SHA256 + Base64URL
* [ ] Code verifier stored securely (sessionStorage for SPAs, Keychain/Keystore for mobile)
* [ ] State parameter is generated and validated
* [ ] All OAuth parameters sent directly in authorization URL
* [ ] Authorization URL contains all required parameters (no JAR)
* [ ] HTTPS is enforced for all requests
* [ ] Redirect URI matches registration
* [ ] Client registered as public client type

## 📚 JAR Parameters Best Practices

### 🔒 Security Best Practices

```javascript
// ✅ Secure JAR parameter generation
function createSecureJAR(authParams) {
  const now = Math.floor(Date.now() / 1000);

  return {
    // JWT Claims - Always include these
    iss: authParams.client_id,
    aud: 'https://account.oten.com',
    iat: now,
    exp: now + 300, // 5 minutes max
    jti: crypto.randomUUID(), // Unique for each request

    // OAuth Parameters - All required
    client_id: authParams.client_id,
    redirect_uri: authParams.redirect_uri,
    response_type: 'code',
    scope: authParams.scope,
    state: crypto.randomBytes(32).toString('hex'), // 32+ chars

    // PKCE - Always use for public clients
    code_challenge: authParams.code_challenge,
    code_challenge_method: 'S256',

    // Security enhancements
    nonce: crypto.randomBytes(16).toString('hex'),
    max_age: 3600 // Force re-auth after 1 hour
  };
}
```

### 🎯 Parameter Selection Guide

| Use Case           | Recommended Parameters        | Example                                       |
| ------------------ | ----------------------------- | --------------------------------------------- |
| **Basic Login**    | Standard OAuth + PKCE         | `scope: "openid profile email"`               |
| **Force Re-auth**  | Add max\_age=0 + prompt=login | `max_age: 0, prompt: "login"`                 |
| **Silent Auth**    | prompt=none                   | `prompt: "none"` (will fail if not logged in) |
| **Multi-language** | ui\_locales                   | `ui_locales: "vi-VN en-US"`                   |
| **Workspace App**  | workspace\_hint               | `workspace_hint: "workspace-123"`             |
| **High Security**  | Short expiry + nonce          | `exp: now + 60, nonce: "random"`              |

### 🔄 Parameter Lifecycle Management

```javascript
class JARParameterManager {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.activeRequests = new Map(); // Track active JAR requests
  }

  async createJAR(authParams, options = {}) {
    // Validate parameters
    const validationErrors = this.validateParameters(authParams);
    if (validationErrors.length > 0) {
      throw new Error(`Invalid parameters: ${validationErrors.join(', ')}`);
    }

    const now = Math.floor(Date.now() / 1000);
    const jti = crypto.randomUUID();

    const jarPayload = {
      // Standard claims
      iss: this.clientId,
      aud: 'https://account.oten.com',
      iat: now,
      exp: now + (options.expirySeconds || 300),
      jti: jti,

      // OAuth parameters
      ...authParams,

      // Optional parameters
      ...options.additionalParams
    };

    // Track this request
    this.activeRequests.set(jti, {
      created: now,
      state: authParams.state,
      codeVerifier: authParams.code_verifier
    });

    // Clean up expired requests
    this.cleanupExpiredRequests();

    return jwt.sign(jarPayload, this.clientSecret, { algorithm: 'HS256' });
  }

  validateParameters(authParams) {
    const errors = [];

    // Required parameters
    if (!authParams.client_id) errors.push('client_id is required');
    if (!authParams.redirect_uri) errors.push('redirect_uri is required');
    if (!authParams.state || authParams.state.length < 32) {
      errors.push('state must be at least 32 characters');
    }

    return errors;
  }

  cleanupExpiredRequests() {
    const now = Math.floor(Date.now() / 1000);
    for (const [jti, request] of this.activeRequests) {
      if (now - request.created > 600) { // 10 minutes
        this.activeRequests.delete(jti);
      }
    }
  }

  getRequestData(state) {
    for (const [jti, request] of this.activeRequests) {
      if (request.state === state) {
        return request;
      }
    }
    return null;
  }
}
```

### JAR Parameter Monitoring

```javascript
// Monitor JAR creation and usage
class JARMonitor {
  constructor() {
    this.metrics = {
      created: 0,
      expired: 0,
      failed: 0,
      algorithms: { HS256: 0, EdDSA: 0 }
    };
  }

  logJARCreation(algorithm, expiryTime) {
    this.metrics.created++;
    this.metrics.algorithms[algorithm]++;

    console.log(`JAR created: algorithm=${algorithm}, expires_in=${expiryTime}s`);
  }

  logJARFailure(error, parameters) {
    this.metrics.failed++;

    console.error('JAR creation failed:', {
      error: error.message,
      client_id: parameters.client_id,
      scope: parameters.scope,
      timestamp: new Date().toISOString()
    });
  }

  getMetrics() {
    return {
      ...this.metrics,
      success_rate: this.metrics.created / (this.metrics.created + this.metrics.failed)
    };
  }
}
```

### ⚠️ Common Mistakes to Avoid

#### For Confidential Clients (JAR)

* ❌ **Don't send parameters in URL query** - Oten will reject them for confidential clients
* ❌ **Don't use unsupported algorithms** - Only HS256 and EdDSA are supported
* ❌ **Don't forget kid in JWT header for EdDSA** - Must match your JWKS
* ❌ **Don't make JAR expire too long** - 5 minutes maximum recommended
* ❌ **Don't store private keys in client-side code** - Use backend for SPAs
* ❌ **Don't use RSA keys** - Oten only supports HS256 and EdDSA
* ❌ **Don't reuse JTI values** - Each JAR must have unique identifier
* ❌ **Don't include sensitive data** - JAR is base64 encoded, not encrypted

#### For Public Clients (PKCE)

* ❌ **Don't use JAR** - JAR is forbidden for public clients
* ❌ **Don't use weak code verifiers** - Must be 128 characters with sufficient entropy
* ❌ **Don't store code verifier insecurely** - Use sessionStorage (SPA) or Keychain/Keystore (mobile)
* ❌ **Don't skip state validation** - Always validate state parameter to prevent CSRF
* ❌ **Don't use HTTP** - HTTPS is required for all OAuth flows
* ❌ **Don't include client\_secret** - Public clients must not use client secrets

***

**Next**: [Step 4: Handle Callback](broken://pages/ptByYQGxY6FoBjvmWxhC)
