npm package: @superapp_men/token-provider
Description: Secure token provider for SuperApp Partner Apps
Author: Your Organization
Latest version: 1.0.12

## @superapp_men/token-provider

Secure token management for SuperApp Partner Apps. Get authentication tokens from the SuperApp without accessing sensitive APIs directly.

## Features

✅ **Secure** - Partner Apps never handle authentication directly  
✅ **Simple** - Single function call to get tokens  
✅ **TypeScript** - Full type safety and IntelliSense  
✅ **Cross-Platform** - Works on Web, iOS, and Android  
✅ **Zero Dependencies** - Lightweight and efficient

## Installation

```bash
npm install @superapp_men/token-provider
```

or

```bash
yarn add @superapp_men/token-provider
```

## Basic Usage

### Get Access Token

```typescript
import { TokenProvider } from "@superapp_men/token-provider";

const provider = new TokenProvider();

// Get default access token
const token = await provider.getToken();

console.log(token.token); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
console.log(token.tokenType); // "Bearer"
```

## Advanced Usage

### With Event Listeners

```typescript
const provider = new TokenProvider({
  debug: true,
});

// Listen to token events
provider.on("tokenReceived", ({ token }) => {
  console.log("Token received from SuperApp");
  console.log("Token:", token.token);
});

provider.on("error", ({ code, message }) => {
  console.error("Token error:", code, message);
});

provider.on("stateChange", ({ state, previousState }) => {
  console.log(`State: ${previousState} → ${state}`);
});

// Get token
const token = await provider.getToken();
```

### Get User Information

```typescript
const provider = new TokenProvider();

// Get user info
const userInfo = await provider.getUserInfo();

console.log(userInfo.studentId);
console.log(userInfo.firstNameFr, userInfo.firstNameAr);
console.log(userInfo.gradeCode, userInfo.gradeDisplayNameFr);
console.log(userInfo.academicYearCode);
// Progression (current period, current week, skills) may be null
if (userInfo.progression?.currentWeek) {
  console.log(userInfo.progression.currentWeek.weekNumber, userInfo.progression.currentWeek.status);
}
```

## Configuration

```typescript
interface TokenProviderConfig {
  // Default timeout for requests (default: 5000ms)
  timeout?: number;

  // Enable debug logging (default: false)
  debug?: boolean;
}
```

### Example Configuration

```typescript
const provider = new TokenProvider({
  timeout: 10000, // 10 seconds
  debug: true, // Debug logging enabled
});
```

## API Reference

### TokenProvider Class

#### Constructor

```typescript
new TokenProvider(config?: TokenProviderConfig)
```

#### Methods

##### `getToken(options?: TokenRequestOptions): Promise<TokenResponse>`

Get a token from the SuperApp. Each call requests a fresh token from the SuperApp. The package returns the token as-is; token validation should be performed in your partner app using JWKS.

**Options:**

```typescript
interface TokenRequestOptions {
  type?: TokenType; // Token type (default: ACCESS)
  timeout?: number; // Request timeout
}
```

**Returns:**

```typescript
interface TokenResponse {
  token: string; // The actual token
  tokenType: string; // "Bearer", etc.
}
```

##### `getUserInfo(): Promise<UserInfo>`

Get current user information (student profile, grade, and progression).

**Returns:**

```typescript
interface UserInfo {
  studentId: string;
  firstNameFr: string;
  firstNameAr: string;
  gradeCode: string;
  gradeDisplayNameFr: string;
  gradeDisplayNameAr: string;
  academicYearCode: string;
  progression: {
    currentPeriod: {
      code: string;
      nameFr: string;
      nameAr: string;
    } | null;
    currentWeek: {
      weekNumber: number;
      weekTitleFr: string;
      weekTitleAr: string;
      status: number;        // WeekProgressStatus (see below)
      statusText: string;
      skills: {
        code: string;
        titleFr: string;
        titleAr: string;
        skillOrder: number;
        status: number;      // StudentSkillStatus (see below)
        statusText: string;
        subSkills: string[];
      }[];
    } | null;
  } | null;
}
```

**Grade codes** (possible values for `gradeCode`):

| Code  | Description |
| ----- | ----------- |
| 1APG  | 1ère année primaire |
| 2APG  | 2ème année primaire |
| 3APG  | 3ème année primaire |
| 4APG  | 4ème année primaire |
| 5APG  | 5ème année primaire |
| 6APG  | 6ème année primaire |

**Current week status** (`currentWeek.status` — `WeekProgressStatus`):

| Value | Name         | Description    |
| ----- | ------------ | -------------- |
| 0     | NotIncluded  | Not in scope   |
| 1     | Upcoming     | Upcoming week  |
| 2     | Current      | Current week   |
| 3     | Completed    | Week completed |

**Skill status** (`skills[].status` — `StudentSkillStatus`):

| Value | Name        | Description     |
| ----- | ----------- | --------------- |
| 0     | NotStarted  | Not started    |
| 1     | InProgress  | In progress    |
| 2     | Validated   | Validated      |
| 3     | Failed      | Failed         |
| 4     | Skipped     | Skipped        |

##### `getState(): TokenProviderState`

Get the current provider state.

**Returns:** `'idle' | 'requesting' | 'ready' | 'error'`

##### `on<T>(event: TokenEventType, callback: EventListener<T>): () => void`

Add an event listener. Returns unsubscribe function.

##### `off<T>(event: TokenEventType, callback: EventListener<T>): void`

Remove an event listener.

##### `removeAllListeners(event?: TokenEventType): void`

Remove all event listeners.

##### `setDebug(enabled: boolean): void`

Enable or disable debug logging.

##### `destroy(): void`

Cleanup and destroy the provider.

## Token Validation

The `getToken()` method returns a token from the SuperApp. **Token validation should be performed in your partner app** using JWKS (JSON Web Key Set).

### Validation Steps (in your app)

1. **Get Token**: Call `getToken()` to receive the token from SuperApp
2. **Fetch JWKS**: Fetch public keys from `https://supper-app.azurewebsites.net/.well-known/jwks.json`
3. **Extract Key ID**: Extract the `kid` (Key ID) from the JWT token header
4. **Match Key**: Find the corresponding public key from the JWKS using the `kid`
5. **Validate Signature**: Validate the token signature using the matched public key

### JWKS Endpoint

The JWKS endpoint returns a JSON structure like:

```json
{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "WUjXNSKJU69KU-8Ccc9wSKoGjVnVpx9SeoF_ih6TNyE",
      "n": "rTknkikAVBH3tWd8p_KaXKOqHx6wBWcbC6DQzoizLhPnZH1kHynEs3EKvN227okxTKfGdmtQgUrC-C-fk5LztMWYR428sao5TmEuhy-bvoEeReh4oDXUqsG7Vc1ilVtjGS6AVpxZtz9Cph4AonLebEyKWHFM9qsqAsmjZNh8L3rqo_uIzz1DVYFer3a1mckgxv1h_EHOFM7SBwiNL3qR4tvsTQNMdHxW49QNg70r69dYy1mbNpZnBBfP-A0bvFrWVnhY3JXEJ25DelvB7UvjlkY3UODuKnlY9fQCrm1nq_aUUYIJJsQRH3DI4N1h3mKYPafrS9iMYf4vi4u85EjESw",
      "e": "AQAB"
    }
  ]
}
```

### Example: Validating Token (Recommended: Backend)

**⚠️ Important**: Token validation should be performed on your **backend server**, not in the client-side code. Client-side validation can be bypassed and should only be used for display purposes.

#### Backend Validation (Recommended)

```typescript
// Backend API endpoint (Node.js/Express example)
import express from "express";
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";

const app = express();

const client = jwksClient({
  jwksUri: "https://supper-app.azurewebsites.net/.well-known/jwks.json",
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

app.post("/api/protected", (req, res) => {
  const token = req.headers.authorization?.replace("Bearer ", "");

  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  jwt.verify(token, getKey, (err, decoded) => {
    if (err) {
      return res
        .status(401)
        .json({ error: "Invalid token", details: err.message });
    }

    // Token is valid, proceed with request
    res.json({ message: "Access granted", user: decoded });
  });
});
```

#### Client-Side Usage

```typescript
// Frontend: Get token and send to backend
import { TokenProvider } from "@superapp_men/token-provider";

const provider = new TokenProvider();

// Get token from SuperApp
const tokenResponse = await provider.getToken();

// Send token to your backend API
const response = await fetch("https://your-api.com/api/protected", {
  method: "POST",
  headers: {
    Authorization: `${tokenResponse.tokenType} ${tokenResponse.token}`,
  },
});

if (response.ok) {
  const data = await response.json();
  // Use the validated data
}
```

## Events

### tokenReceived

Fired when a token is received from the SuperApp.

```typescript
provider.on("tokenReceived", ({ token }) => {
  console.log("Token:", token.token);
});
```

### stateChange

Fired when the provider state changes.

```typescript
provider.on("stateChange", ({ state, previousState }) => {
  console.log(`State: ${previousState} → ${state}`);
});
```

### error

Fired when an error occurs.

```typescript
provider.on("error", ({ code, message, details }) => {
  console.error(`Error [${code}]: ${message}`);
});
```

## Complete Examples

### React Hook

```typescript
import { useState, useEffect } from "react";
import {
  TokenProvider,
  type TokenResponse,
} from "@superapp_men/token-provider";

export function useToken() {
  const [provider] = useState(() => new TokenProvider());
  const [token, setToken] = useState<TokenResponse | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const unsubscribe = provider.on("tokenReceived", ({ token }) => {
      setToken(token);
      setLoading(false);
    });

    const errorUnsub = provider.on("error", ({ message }) => {
      setError(message);
      setLoading(false);
    });

    return () => {
      unsubscribe();
      errorUnsub();
      provider.destroy();
    };
  }, [provider]);

  const getToken = async () => {
    setLoading(true);
    setError(null);
    try {
      const token = await provider.getToken();
      return token;
    } catch (err) {
      setError(err.message);
      throw err;
    }
  };

  return { token, loading, error, getToken, provider };
}

// Usage
function MyComponent() {
  const { token, loading, getToken } = useToken();

  useEffect(() => {
    getToken();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <p>Token: {token?.token.substring(0, 20)}...</p>
      <button onClick={getToken}>Get New Token</button>
    </div>
  );
}
```

### Vue Composable

```typescript
import { ref, onMounted, onUnmounted } from "vue";
import { TokenProvider } from "@superapp_men/token-provider";

export function useToken() {
  const provider = new TokenProvider();
  const token = ref(null);
  const loading = ref(false);
  const error = ref(null);

  const getToken = async () => {
    loading.value = true;
    error.value = null;
    try {
      token.value = await provider.getToken();
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  };

  onMounted(() => {
    provider.on("tokenReceived", ({ token: t }) => {
      token.value = t;
    });

    provider.on("error", ({ message }) => {
      error.value = message;
    });
  });

  onUnmounted(() => {
    provider.destroy();
  });

  return { token, loading, error, getToken };
}
```

## Utility Functions

### decodeJWT(token: string)

Decode a JWT token payload (without verification - for display only).

```typescript
import { decodeJWT } from "@superapp_men/token-provider";

const token = await provider.getToken();
const payload = decodeJWT(token.token);

console.log(payload.sub); // User ID
console.log(payload.exp); // Expiration
console.log(payload.iat); // Issued at
```

## Error Handling

```typescript
import { TokenProvider, TokenError } from "@superapp_men/token-provider";

const provider = new TokenProvider();

try {
  const token = await provider.getToken();
} catch (error) {
  switch (error.code) {
    case TokenError.TIMEOUT:
      console.error("Request timeout");
      break;
    case TokenError.UNAUTHORIZED:
      console.error("Not authorized");
      break;
    case TokenError.SUPERAPP_NOT_AVAILABLE:
      console.error("SuperApp not responding");
      break;
    case TokenError.TOKEN_EXPIRED:
      console.error("Token expired - get a new one");
      const newToken = await provider.getToken();
      break;
    default:
      console.error("Unknown error:", error.message);
  }
}
```

## Security Considerations

### 1. Never Store Tokens in localStorage

```typescript
// ❌ Bad - vulnerable to XSS
localStorage.setItem("token", token.token);

// ✅ Good - get fresh token when needed
const token = await provider.getToken(); // Fresh from SuperApp
```

### 2. Always Use HTTPS

Ensure all communication happens over HTTPS in production.

### 3. Validate Tokens in Your App

The package returns tokens from the SuperApp without validation. **You should validate tokens in your partner app** using JWKS (JSON Web Key Set) from `https://supper-app.azurewebsites.net/.well-known/jwks.json`:

- Extract the `kid` from the JWT header
- Fetch the corresponding public key from JWKS
- Validate the token signature

**Important**: Always validate tokens before using them. For production applications, also validate tokens on your backend server.

### 4. Get Fresh Tokens When Needed

Since tokens are not cached, call `getToken()` whenever you need a new token. The SuperApp will handle token generation securely.

## Token Signing: RS256 (RSA256) Algorithm

### Why RS256?

This package uses **RS256** (RSA Signature with SHA-256) for token signing, which is an industry-standard asymmetric cryptographic algorithm. Here's why RS256 was chosen:

#### 1. **Asymmetric Key Pair Security**

- **Private Key**: Kept secret by the SuperApp (never shared)
- **Public Key**: Published via JWKS endpoint (safe to share)
- Only the SuperApp can **sign** tokens (using private key)
- Anyone can **verify** tokens (using public key)
- If the public key is compromised, tokens can still be verified but new tokens cannot be forged

#### 2. **Key Rotation Support**

- SuperApp can rotate keys without breaking existing tokens
- Multiple keys can coexist (identified by `kid` in JWT header)
- Partner Apps automatically fetch the correct key from JWKS
- Enables secure key rotation without service interruption

#### 3. **Industry Standard & Compatibility**

- RS256 is the most widely supported JWT signing algorithm
- Recommended by OAuth 2.0 and OpenID Connect specifications
- Supported by all major JWT libraries and platforms
- Better security than symmetric algorithms (HS256) for distributed systems

#### 4. **Non-Repudiation**

- Each token signature is cryptographically unique
- Cannot be forged without the private key
- Provides strong audit trail and accountability

### How RS256 Works

#### Token Signing Process (SuperApp)

1. **Create JWT Structure**:

   ```
   Header.Payload.Signature
   ```

2. **Header** (contains algorithm info):

   ```json
   {
     "alg": "RS256",
     "typ": "JWT",
     "kid": "superapp-key-2025-01"
   }
   ```

3. **Payload** (token claims):

   ```json
   {
     "sub": "user-123",
     "exp": 1735689600,
     "iat": 1735686000,
     "aud": "partner-app-id"
   }
   ```

4. **Signing**:
   - SuperApp creates: `base64Url(header) + "." + base64Url(payload)`
   - Signs this string using **RSA private key** with SHA-256 hash
   - Appends signature: `header.payload.signature`

#### Token Verification Process (Partner App)

1. **Extract Key ID**:

   - Decode JWT header to get `kid` (Key ID)
   - Example: `"kid": "superapp-key-2025-01"`

2. **Fetch Public Key**:

   - Request JWKS from: `https://supper-app.azurewebsites.net/.well-known/jwks.json`
   - Find key matching the `kid` from JWKS response

3. **Verify Signature**:
   - Recreate: `base64Url(header) + "." + base64Url(payload)`
   - Use **RSA public key** to verify the signature
   - If signature matches → token is authentic and unmodified

#### Cryptographic Flow

```
┌─────────────────────────────────────────────────────────┐
│                    SuperApp (Signer)                    │
├─────────────────────────────────────────────────────────┤
│  1. Generate RSA Key Pair                               │
│     - Private Key (SECRET) → Used to SIGN tokens      │
│     - Public Key → Published in JWKS                   │
│                                                          │
│  2. Create JWT Token                                    │
│     Header: { alg: "RS256", kid: "key-2025-01" }       │
│     Payload: { sub, exp, iat, aud, ... }               │
│                                                          │
│  3. Sign Token                                          │
│     signature = RSA_SIGN(                               │
│       SHA256(header + "." + payload),                   │
│       private_key                                       │
│     )                                                   │
│                                                          │
│  4. Return: header.payload.signature                    │
└─────────────────────────────────────────────────────────┘
                        │
                        │ Token (JWT)
                        ▼
┌─────────────────────────────────────────────────────────┐
│                 Partner App (Verifier)                   │
├─────────────────────────────────────────────────────────┤
│  1. Receive JWT Token                                   │
│                                                          │
│  2. Extract kid from Header                            │
│     Decode header → Get "kid": "key-2025-01"           │
│                                                          │
│  3. Fetch Public Key from JWKS                          │
│     GET /.well-known/jwks.json                         │
│     Find key where kid = "key-2025-01"                 │
│                                                          │
│  4. Verify Signature                                    │
│     RSA_VERIFY(                                         │
│       signature,                                        │
│       SHA256(header + "." + payload),                   │
│       public_key                                        │
│     )                                                   │
│                                                          │
│  5. If valid → Token is authentic ✅                    │
│     If invalid → Token is forged/rejected ❌            │
└─────────────────────────────────────────────────────────┘
```

### Security Properties

#### ✅ **Integrity Protection**

- Any modification to header or payload invalidates the signature
- Detects tampering immediately during verification

#### ✅ **Authentication**

- Only SuperApp (with private key) can create valid tokens
- Partner Apps can verify authenticity using public key

#### ✅ **Key Isolation**

- Private key never leaves SuperApp servers
- Public key can be safely shared via JWKS
- Compromise of public key doesn't allow token forgery

#### ✅ **Forward Security**

- Old tokens remain valid even after key rotation
- New tokens use new keys (identified by different `kid`)
- Enables secure key rotation without breaking existing sessions

### Comparison with Other Algorithms

| Algorithm | Type       | Security | Key Distribution    | Use Case                                           |
| --------- | ---------- | -------- | ------------------- | -------------------------------------------------- |
| **RS256** | Asymmetric | High     | Public key via JWKS | ✅ **Distributed systems** (SuperApp architecture) |
| HS256     | Symmetric  | Medium   | Shared secret       | Single service                                     |
| ES256     | Asymmetric | High     | Public key via JWKS | Alternative to RS256                               |

**Why RS256 over HS256?**

- HS256 requires sharing a secret key → security risk if compromised
- RS256 uses public/private key pair → public key can be safely shared
- RS256 enables key rotation without re-deploying Partner Apps

**Why RS256 over ES256?**

- RS256 has broader library support
- RS256 keys are larger but more compatible
- RS256 is the OAuth 2.0 recommended default

### Best Practices

1. **Always Verify Tokens**: Never trust tokens without signature verification
2. **Cache JWKS**: Fetch JWKS periodically and cache (with TTL) to reduce requests
3. **Handle Key Rotation**: If `kid` not found, refresh JWKS and retry verification
4. **Validate Claims**: After signature verification, validate `exp`, `aud`, `iss` claims
5. **Use HTTPS**: Always fetch JWKS over HTTPS to prevent MITM attacks

### Example: JWKS Structure

```json
{
  "keys": [
    {
      "kty": "RSA", // Key type: RSA
      "use": "sig", // Use: signature
      "alg": "RS256", // Algorithm: RS256
      "kid": "superapp-key-2025-01", // Key ID (matches JWT header)
      "n": "rTknkikAVBH3tWd8p_KaXKOqHx6wBWcbC6DQzoizLhPnZH1kHynEs3EKvN227okxTKfGdmtQgUrC-C-fk5LztMWYR428sao5TmEuhy-bvoEeReh4oDXUqsG7Vc1ilVtjGS6AVpxZtz9Cph4AonLebEyKWHFM9qsqAsmjZNh8L3rqo_uIzz1DVYFer3a1mckgxv1h_EHOFM7SBwiNL3qR4tvsTQNMdHxW49QNg70r69dYy1mbNpZnBBfP-A0bvFrWVnhY3JXEJ25DelvB7UvjlkY3UODuKnlY9fQCrm1nq_aUUYIJJsQRH3DI4N1h3mKYPafrS9iMYf4vi4u85EjESw",
      "e": "AQAB" // RSA public exponent
    }
  ]
}
```

The `n` (modulus) and `e` (exponent) values together form the RSA public key used for signature verification.

## Browser Support

- Chrome/Edge 80+
- Firefox 75+
- Safari 14+
- iOS Safari 14+ (via Capacitor)
- Android WebView (via Capacitor)

## Troubleshooting

### "SuperApp not available"

**Cause:** Partner App is not loaded within SuperApp  
**Solution:** Ensure app is running in iframe or Capacitor WebView

### "Request timeout"

**Cause:** SuperApp not responding or slow network  
**Solution:** Increase timeout in configuration

```typescript
const provider = new TokenProvider({ timeout: 10000 });
```

### "Token expired"

**Cause:** Token has expired  
**Solution:** Get a new token by calling `getToken()` again

```typescript
try {
  const token = await provider.getToken();
  // Use token...
} catch (error) {
  if (error.code === TokenError.TOKEN_EXPIRED) {
    // Get a new token
    const newToken = await provider.getToken();
  }
}
```

## License

MIT

## Support

<!-- - GitHub: https://github.com/your-org/token-provider -->
<!-- - Documentation: https://docs.your-superapp.com -->

- Email: h.afifi@alexsys.solutions

---

**Ready to use secure tokens?** Install the package and get started! 🔐