> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scute.io/llms.txt
> Use this file to discover all available pages before exploring further.

# JWT

A comprehensive guide for validating Scute authentication tokens in your application.

## Understanding Scute's Structure

### The Basics

Scute organizes users and permissions in a simple hierarchy:

```
Workspace (Your Organization)
  ├── Apps (Your Applications) 
  │   └── App Users (Your End Users)
  └── Users (Workspace Members)
```

**Example:**

* **Workspace:** "Acme Corp" (your company)
* **App:** "Acme Mobile App" (your customer-facing app)
* **App Users:** Your end customers ([john@customer.com](mailto:john@customer.com), [jane@customer.com](mailto:jane@customer.com))
* **Users:** Your team members ([admin@acme.com](mailto:admin@acme.com), [dev@acme.com](mailto:dev@acme.com))

### Key Concepts

* **Workspace:** Your organization's account on Scute
* **App:** Each application you build (mobile app, web app, etc.)
* **App Users:** Your end customers who log into your apps
* **Users:** Your team members who manage the workspace

## JWT Token Structure

When a user successfully authenticates, Scute issues a JWT token with the following structure:

```json theme={null}
{
  "uuid": "app_user_id",       // The authenticated user ID
  "aid": "your_app_id",        // Your application ID
  "uid": "session_id",         // Unique session identifier
  "wid": "workspace_id",       // Your workspace ID
  "crid": "credential_id",     // Webauthn credential id (optional)
  "exp": 1640995200,           // Token expiration (Unix timestamp)
  "iat": 1640991600            // Token issued at (Unix timestamp)
}
```

### Token Claims Explained

| Claim  | Description                                                  | Example           |
| ------ | ------------------------------------------------------------ | ----------------- |
| `uuid` | App User ID - identifies the authenticated user              | `"usr_1234abcd"`  |
| `aid`  | App ID - your application identifier                         | `"app_5678efgh"`  |
| `uid`  | Session ID - unique identifier for this login session        | `"sess_9012ijkl"` |
| `wid`  | Workspace ID - your organization's identifier                | `"ws_3456mnop"`   |
| `crid` | Credential ID - method used to authenticate (WebAuthn, etc.) | `"cred_7890qrst"` |
| `exp`  | Expiration - when the token expires                          | `1640995200`      |
| `iat`  | Issued At - when the token was created                       | `1640991600`      |

## Offline JWT Verification (Recommended)

Offline verification is faster and more scalable as it doesn't require API calls to validate tokens.

### Step 1: Get the Public Key

Fetch your app's public key for JWT verification (this is a **public endpoint**):

```javascript theme={null}
// Get RSA public key in PEM format
const response = await fetch(`https://api.scute.io/v1/auth/${app_id}/public_key`);
const { public_key, algorithm, key_id } = await response.json();

console.log(public_key);
// -----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
// -----END PUBLIC KEY-----
```

**Response Format:**

```json theme={null}
{
  "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG...\n-----END PUBLIC KEY-----",
  "algorithm": "RS256",
  "key_id": "ws_1234_1640991600"
}
```

### Step 2: Offline JWT Verification

```javascript theme={null}
import jwt from 'jsonwebtoken';

// Your session token from user login
const accessToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...";

try {
  // Verify the token offline
  const decoded = jwt.verify(accessToken, public_key, {
    algorithms: ['RS256']
    // Note: Current tokens don't include standard 'aud' or 'iss' claims
    // Audience validation is done manually below using the 'aid' claim
  });
  
  // Manually validate that token is for your app
  if (decoded.aid !== your_app_id) {
    throw new Error('Token not intended for this app');
  }
  
  console.log('✅ Token is valid!');
  console.log('User ID:', decoded.uuid);
  console.log('App ID:', decoded.aid);
  console.log('Session ID:', decoded.uid);
  
  // Token is valid - user is authenticated
  return {
    authenticated: true,
    userId: decoded.uuid,
    sessionId: decoded.uid,
    expiresAt: new Date(decoded.exp * 1000)
  };
  
} catch (error) {
  console.log('❌ Token is invalid:', error.message);
  return { authenticated: false, error: error.message };
}
```

### Step 3: Using JWKS (Recommended for Production)

JWKS (JSON Web Key Set) provides automatic key rotation support:

```javascript theme={null}
import jwksClient from 'jwks-rsa';

// Create JWKS client (handles caching and key rotation)
const client = jwksClient({
  jwksUri: `https://api.scute.io/v1/auth/${app_id}/jwks`,
  cache: true,
  cacheMaxAge: 3600000, // Cache for 1 hour
  rateLimit: true,
  jwksRequestsPerMinute: 5
});

// Get signing key function
function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) {
      callback(err);
      return;
    }
    const signingKey = key.getPublicKey();
    callback(null, signingKey);
  });
}

// Verify token with automatic key management
jwt.verify(accessToken, getKey, {
  algorithms: ['RS256']
}, (err, decoded) => {
  if (err) {
    console.log('❌ Token invalid:', err.message);
    return;
  }
  
  console.log('✅ Token valid:', decoded);
  // Proceed with authenticated user
});
```

### Key Points for Offline Validation

#### ✅ Benefits

* **🚀 Performance:** No API calls required for each validation
* **🔒 Security:** Cryptographically secure verification
* **⚡ Scalability:** No rate limits on token validation
* **🌐 Offline Support:** Works without internet connectivity

#### ⚠️ Important Considerations

1. **Cache Public Keys Wisely**
   ```javascript theme={null}
   // Good: Cache for reasonable time
   const cacheTime = 1000 * 60 * 60; // 1 hour

   // Bad: Cache forever (keys may rotate)
   // Don't cache indefinitely
   ```

2. **Validate Token Claims**
   ```javascript theme={null}
   // Ensure token is for your app (manual audience validation)
   if (decoded.aid !== your_app_id) {
     throw new Error('Token not intended for this app');
   }

   // Verify token hasn't expired
   if (decoded.exp < Math.floor(Date.now() / 1000)) {
     throw new Error('Token has expired');
   }
   ```

3. **Handle Key Rotation**
   ```javascript theme={null}
   // Use JWKS for automatic key rotation
   // or refresh public keys every hour
   ```

## Online Verification (Alternative)

If you prefer server-side validation or need additional user data, use online verification:

### Endpoint

```
GET https://api.scute.io/v1/auth/{app_id}/current_user
Authorization: Bearer {access_token}
```

### Example Implementation

```javascript theme={null}
async function verifyTokenOnline(accessToken, appId) {
  try {
    const response = await fetch(`https://api.scute.io/v1/auth/${appId}/current_user`, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      }
    });
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    const userData = await response.json();
    
    return {
      authenticated: true,
      user: userData.user,
      // Additional user data available:
      // - email, phone, name
      // - verification status
      // - user metadata
    };
    
  } catch (error) {
    console.log('❌ Online verification failed:', error.message);
    return { 
      authenticated: false, 
      error: error.message 
    };
  }
}

// Usage
const result = await verifyTokenOnline(accessToken, 'your_app_id');
if (result.authenticated) {
  console.log('User data:', result.user);
} else {
  console.log('Authentication failed:', result.error);
}
```

### Online vs Offline Verification

| Method      | Speed     | Data Available    | Internet Required | Rate Limits           |
| ----------- | --------- | ----------------- | ----------------- | --------------------- |
| **Offline** | ⚡ Fast    | Basic claims only | ❌ No              | ❌ None                |
| **Online**  | 🐌 Slower | Full user data    | ✅ Yes             | ✅ Standard API limits |

## Best Practices

### Security Recommendations

1. **Use HTTPS Only:** Never transmit tokens over HTTP
2. **Validate Audience:** Always check the `aid` claim matches your app
3. **Check Expiration:** Validate `exp` claim before processing
4. **Store Securely:** Use secure storage for tokens on client-side

### Performance Optimization

1. **Cache Public Keys:** Refresh every 1-6 hours
2. **Use Offline Verification:** For high-traffic applications
3. **Connection Pooling:** Reuse HTTP connections for online verification
4. **Error Handling:** Gracefully handle network failures

### Example: Complete Verification Function

```javascript theme={null}
class ScuteJWTVerifier {
  constructor(appId) {
    this.appId = appId;
    this.publicKey = null;
    this.keyLastFetched = null;
    this.cacheTime = 3600000; // 1 hour
  }
  
  async getPublicKey() {
    const now = Date.now();
    
    // Use cached key if still valid
    if (this.publicKey && this.keyLastFetched && 
        (now - this.keyLastFetched) < this.cacheTime) {
      return this.publicKey;
    }
    
    // Fetch fresh public key
    const response = await fetch(`https://api.scute.io/v1/auth/${this.appId}/public_key`);
    const data = await response.json();
    
    this.publicKey = data.public_key;
    this.keyLastFetched = now;
    
    return this.publicKey;
  }
  
  async verifyToken(token) {
    try {
      const publicKey = await this.getPublicKey();
      
      const decoded = jwt.verify(token, publicKey, {
        algorithms: ['RS256']
      });
      
      // Manually validate audience (app ID)
      if (decoded.aid !== this.appId) {
        throw new Error('Token not intended for this app');
      }
      
      return {
        valid: true,
        userId: decoded.uuid,
        sessionId: decoded.uid,
        expiresAt: new Date(decoded.exp * 1000)
      };
      
    } catch (error) {
      return {
        valid: false,
        error: error.message
      };
    }
  }
}

// Usage
const verifier = new ScuteJWTVerifier('your_app_id');
const result = await verifier.verifyToken(userToken);

if (result.valid) {
  console.log(`User ${result.userId} is authenticated`);
} else {
  console.log(`Authentication failed: ${result.error}`);
}
```

## Troubleshooting

### Common Issues

**"Invalid signature" Error**

* Check that you're using the correct public key for your app
* Ensure you're using the RS256 algorithm
* Verify the token hasn't been modified

**"Token expired" Error**

* Check system clock synchronization
* Token has a limited lifetime (typically 1 hour)
* Request a new token using refresh flow

**"Invalid audience" Error**

* Ensure the `aid` claim matches your app ID
* Token might be intended for a different app

**Network Errors (Online Verification)**

* Check API endpoint URL is correct
* Verify internet connectivity
* Ensure proper Authorization header format
