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


---

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