atko-cross-app-access-sdk
Version:
SDK for Okta Cross-App Access using Identity Assertion Authorization Grant (ID-JAG)
275 lines (211 loc) โข 7.92 kB
Markdown
# Atko Cross-App Access SDK
A TypeScript/JavaScript SDK for implementing **Identity Assertion Authorization Grant (ID-JAG)** with Okta, enabling secure cross-application access patterns.
## Overview
This SDK implements the **ID-JAG** (Identity Assertion Authorization Grant) flow as defined in OAuth 2.0 extensions, allowing applications to exchange Okta ID tokens for specialized access tokens that can be used for cross-app authorization.
## Key Features
- ๐ **Token Exchange**: Convert Okta ID tokens to ID-JAG tokens
- ๐ก๏ธ **Secure**: Follows OAuth 2.0 Token Exchange (RFC 8693) standards
- ๐ฏ **Cross-App Access**: Enable secure application-to-application communication
- ๐ฆ **TypeScript**: Full TypeScript support with type definitions
- ๐งช **Testing**: Built-in test utilities and examples
## Installation
### Stable Release
```bash
npm install atko-cross-app-access-sdk
```
### Beta Release (Latest Features)
```bash
npm install atko-cross-app-access-sdk@beta
```
> **Note**: This is currently a beta release. For production use, please wait for the stable 1.0.0 release or thoroughly test the beta version in your environment.
## Quick Start
### Basic Usage
```typescript
import { exchangeIdTokenForIdJag } from 'atko-cross-app-access-sdk';
const idJagToken = await exchangeIdTokenForIdJag({
subject_token: 'your-okta-id-token',
audience: 'http://localhost:5001',
client_id: '0oap6a3kputUHyW1R1d7',
client_secret: 'your-client-secret'
}, 'https://your-domain.oktapreview.com'); // Explicit Okta domain required
console.log('ID-JAG Token:', idJagToken.access_token);
```
### Using the SDK Class
```typescript
import { AtkoCrossAppAccessSDK } from 'atko-cross-app-access-sdk';
const sdk = new AtkoCrossAppAccessSDK('https://your-domain.oktapreview.com');
const result = await sdk.exchangeIdTokenForIdJag({
subject_token: idToken,
audience: 'http://your-target-app',
client_id: 'your-client-id',
client_secret: 'your-client-secret'
});
```
### Token Verification
```typescript
import { verifyIdJagToken } from 'atko-cross-app-access-sdk';
// Verify an ID-JAG token
const verificationResult = await verifyIdJagToken(idJagToken, {
issuer: 'https://your-domain.oktapreview.com',
audience: 'http://localhost:5001'
});
if (verificationResult.valid) {
console.log('Token is valid!');
console.log('Subject:', verificationResult.sub);
console.log('Email:', verificationResult.email);
} else {
console.log('Token verification failed:', verificationResult.error);
}
```
## API Reference
### `exchangeIdTokenForIdJag(request: IdJagTokenRequest, oktaBaseUrl: string): Promise<IdJagTokenResponse>`
Exchanges an Okta ID token for an ID-JAG token.
**Parameters:**
- `request` (object):
- `subject_token` (string): The Okta ID token to exchange
- `audience` (string): Target audience/application URL
- `client_id` (string): Okta application client ID
- `client_secret` (string): Okta application client secret
- `oktaBaseUrl` (string): Your Okta domain URL (e.g., "https://your-domain.oktapreview.com")
**Returns:**
```typescript
{
access_token: string; // The ID-JAG token
issued_token_type: string; // "urn:ietf:params:oauth:token-type:id-jag"
token_type: string; // "Bearer"
expires_in?: number; // Token expiration in seconds
scope?: string; // Token scope
}
```
### `verifyIdJagToken(token: string, options: IdJagTokenVerificationOptions): Promise<IdJagTokenVerificationResult>`
Verifies an ID-JAG token using the issuer's public keys (JWKS).
**Parameters:**
- `token` (string): The ID-JAG token to verify
- `options` (object):
- `issuer` (string): Expected token issuer (your Okta domain URL)
- `audience` (string): Expected token audience (e.g., "http://localhost:5001")
- `jwksUri` (string, optional): JWKS URI, defaults to issuer + "/oauth2/v1/keys"
**Returns:**
```typescript
{
valid: boolean;
payload?: any; // Full JWT payload if valid
sub?: string; // Subject claim
email?: string; // Email claim
aud?: string; // Audience claim
iss?: string; // Issuer claim
exp?: number; // Expiration timestamp
error?: string; // Error message if invalid
}
```
## Token Exchange Flow
```mermaid
sequenceDiagram
participant App as Client App
participant Okta as Okta Authorization Server
participant Target as Target Application
App->>Okta: 1. Authenticate & get ID token
App->>SDK: 2. exchangeIdTokenForIdJag(id_token, audience, client_id, secret)
SDK->>Okta: 3. POST /oauth2/v1/token (Token Exchange)
Okta->>SDK: 4. ID-JAG token
SDK->>App: 5. Return ID-JAG token
App->>Target: 6. Use ID-JAG token for cross-app access
```
## Testing
### Manual Testing
1. **Update test configuration:**
```javascript
// In test-id-jag.js
const TEST_CONFIG = {
subject_token: 'YOUR_ACTUAL_ID_TOKEN',
audience: 'http://localhost:5001',
client_id: '0oap6a3kputUHyW1R1d7',
client_secret: 'YOUR_ACTUAL_CLIENT_SECRET'
};
```
2. **Run the test:**
```bash
npm test
```
### Curl Example
The SDK implements this curl command programmatically:
```bash
curl --location 'https://ijtestcustom.oktapreview.com/oauth2/v1/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:id-jag' \
--data-urlencode 'subject_token=YOUR_ID_TOKEN' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
--data-urlencode 'audience=http://localhost:5001' \
--data-urlencode 'client_id=0oap6a3kputUHyW1R1d7' \
--data-urlencode 'client_secret=YOUR_SECRET'
```
## Integration Examples
### With Express.js Application
```typescript
import express from 'express';
import { exchangeIdTokenForIdJag } from 'atko-cross-app-access-sdk';
const app = express();
app.post('/cross-app-access', async (req, res) => {
try {
const { idToken } = req.body;
const idJagToken = await exchangeIdTokenForIdJag({
subject_token: idToken,
audience: 'http://target-service:5001',
client_id: process.env.OKTA_CLIENT_ID,
client_secret: process.env.OKTA_CLIENT_SECRET
});
// Use the ID-JAG token to access target service
res.json({ success: true, token: idJagToken.access_token });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
```
### With MCP Authorization Server
```typescript
// In your MCP auth server
import { validateIdJagToken } from 'atko-cross-app-access-sdk';
app.post('/oauth/token', async (req, res) => {
const { id_jag_token } = req.body;
if (validateIdJagToken(id_jag_token)) {
// Issue MCP access token
const mcpToken = await generateMcpAccessToken(id_jag_token);
res.json({ access_token: mcpToken });
} else {
res.status(401).json({ error: 'Invalid ID-JAG token' });
}
});
```
## Error Handling
The SDK provides detailed error messages for common issues:
```typescript
try {
const result = await exchangeIdTokenForIdJag(config);
} catch (error) {
if (error.message.includes('invalid_client')) {
console.error('Invalid client credentials');
} else if (error.message.includes('invalid_grant')) {
console.error('Invalid or expired ID token');
} else {
console.error('Token exchange failed:', error.message);
}
}
```
## Standards Compliance
- **RFC 8693**: OAuth 2.0 Token Exchange
- **RFC 6749**: OAuth 2.0 Authorization Framework
- **OpenID Connect Core 1.0**: ID Token specifications
## Development
```bash
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run dev
# Clean build artifacts
npm run clean
```
## License
MIT License - see LICENSE file for details.