---
title: "Decode Single Task JWT"
description: "This document outlines the execution flow when a user initiates the download of task evidence from the SingleTask component, leading to the decoding of a JSON Web Token (JWT) payload. The primary g..."
last_updated: "2026-05-06T07:30:58.159818+00:00"
canonical_url: "https://www.doc0.dev/docs/49c89830-117b-4def-8edc-b5bcc50766e0/technical/how-it-works/decode-single-task-jwt"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx](https://github.com/blade47/comp/blob/main/apps/app/src/app/(app)/%5BorgId%5D/tasks/%5BtaskId%5D/components/SingleTask.tsx)
- [apps/app/src/lib/evidence-download.ts](https://github.com/blade47/comp/blob/main/apps/app/src/lib/evidence-download.ts)
- [apps/app/src/utils/jwt-manager.ts](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts)
</details>

This document outlines the execution flow when a user initiates the download of task evidence from the `SingleTask` component, leading to the decoding of a JSON Web Token (JWT) payload. The primary goal of this process is to securely fetch a file from the backend API, which requires a valid authentication token.

The flow begins with a user interaction in the `SingleTask` UI, triggering a file download. To ensure the request is authenticated, the system relies on a `jwtManager` utility. This utility is responsible for providing a valid JWT, proactively refreshing it if it's missing or nearing expiration. A crucial part of this refresh mechanism involves decoding the JWT's payload to extract its expiration timestamp, thereby allowing the system to manage token validity and schedule future refreshes efficiently. This ensures that authenticated operations, like downloading sensitive evidence, proceed seamlessly without requiring the user to re-authenticate frequently.

### 1. User Initiates Evidence Download

The process begins within the `SingleTask` React component when a user clicks the "Download task evidence" button. This action triggers an asynchronous operation to fetch a ZIP archive containing all evidence related to the current task. The component catches potential errors during the download and provides user feedback via `toast` notifications.

<Callout variant="info">
The `SingleTask` component is responsible for rendering the task details and providing interactive elements, including the download button for task evidence.
</Callout>

**Source:** [apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx:196-209](https://github.com/blade47/comp/blob/main/apps/app/src/app/(app)/%5BorgId%5D/tasks/%5BtaskId%5D/components/SingleTask.tsx#L196-L209)

### 2. Prepare Task Evidence Download

Upon the user's action, the `downloadTaskEvidenceZip` function is called. This function is part of the `evidence-download` utility library and is responsible for constructing the correct API endpoint for the task evidence ZIP file. It takes the `taskId`, `taskTitle`, `organizationId`, and an `includeJson` flag as parameters. It then delegates the actual download operation to a more generic `downloadFile` function.

**Inputs:**
*   `taskId`: The ID of the task for which evidence is being downloaded.
*   `taskTitle`: The title of the task, used for generating a user-friendly filename.
*   `organizationId`: The ID of the organization, required for API calls.
*   `includeJson`: A boolean indicating whether to include JSON metadata in the ZIP.

**Output:** A call to `downloadFile` with the constructed URL and metadata.

**Source:** [apps/app/src/lib/evidence-download.ts:46-60](https://github.com/blade47/comp/blob/main/apps/app/src/lib/evidence-download.ts#L46-L60)

### 3. Execute File Download with Authentication

The `downloadFile` function, also within the `evidence-download` utility, handles the core logic of fetching a file from a given URL. Before making the `fetch` request, it prepares the necessary HTTP headers, including the `X-Organization-Id`. Crucially, it attempts to retrieve a valid JWT token for authentication. If successful, this token is added to the `Authorization` header as a Bearer token. The function then performs the `fetch` request, handles potential HTTP errors, extracts the filename from the `Content-Disposition` header (or generates a fallback), and finally initiates the client-side download by creating a temporary `<a>` element.

**Inputs:**
*   `url`: The full API endpoint for the file to be downloaded.
*   `organizationId`: The organization ID for the `X-Organization-Id` header.
*   `fallback`: An optional object containing `fallbackBaseName` and `fallbackExtension` for filename generation.

**Data Flow:**
1.  Calls `jwtManager.getValidToken()` to obtain an authentication token.
2.  Constructs `headers` object with `X-Organization-Id` and `Authorization` (if token is available).
3.  Performs `fetch(url, { method: 'GET', headers, credentials: 'include' })`.
4.  Processes `response.blob()` and `Content-Disposition` header to get the file and its name.

**Error Handling:**
*   If `jwtManager.getValidToken()` fails, an error is logged, and a new error "Authentication failed" is thrown.
*   If the `fetch` response is not `ok`, an error is thrown with the response text or status.

**Source:** [apps/app/src/lib/evidence-download.ts:98-145](https://github.com/blade47/comp/blob/main/apps/app/src/lib/evidence-download.ts#L98-L145)

### 4. Retrieve a Valid JWT Token

The `jwtManager.getValidToken()` method is invoked to ensure that any outgoing API request is authenticated with a current and valid JWT. It first checks if a token is already stored in `localStorage` and if that token is still valid or not expiring soon (within a `REFRESH_THRESHOLD` of 5 minutes).

**Branching Logic:**
*   **If a stored token exists and is not expiring soon:** The method logs "✅ Using cached JWT token" and returns the stored token immediately.
*   **If no token is stored or the stored token is expiring soon:** The method logs "🔄 JWT token missing or expiring soon, fetching fresh token..." and proceeds to call `this.refreshToken()` to acquire a new token.

**Error Handling:**
*   If any error occurs during this process, it's logged, and `null` is returned, indicating that no valid token could be obtained.

**Source:** [apps/app/src/utils/jwt-manager.ts:25-40](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts#L25-L40)

### 5. Orchestrate Token Refresh

The `jwtManager.refreshToken()` method is responsible for managing the token refresh process. It includes logic to prevent multiple concurrent refresh attempts and enforces a cooldown period between refreshes to avoid overwhelming the authentication service.

**Concurrency and Cooldown:**
*   It checks `this.refreshPromise`: If a refresh is already in progress, it waits for that existing promise to resolve.
*   It checks `this.lastRefreshAttempt` and `REFRESH_COOLDOWN`: If a refresh was attempted too recently, it waits for the cooldown period to pass. During this wait, if a valid token is already available, it might return that token.

Once these checks pass, it records the `lastRefreshAttempt` and sets `this.refreshPromise` to the result of `this._doRefreshToken()`, ensuring that subsequent calls wait for this refresh to complete.

**Output:** Returns the new token obtained from `_doRefreshToken` or `null` if the refresh fails.

**Source:** [apps/app/src/utils/jwt-manager.ts:47-79](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts#L47-L79)

### 6. Perform Actual Token Refresh

The `jwtManager._doRefreshToken()` method executes the actual network requests to obtain a new JWT. It attempts two primary strategies:

1.  **Session-based refresh:** It first tries to get a JWT from the `authClient.getSession()` call. The `onSuccess` callback inspects the response headers for a `set-auth-jwt` header.
2.  **Explicit token endpoint:** If the session-based approach doesn't yield a token, it makes a direct `fetch` request to the `/api/auth/token` endpoint.

**Data Flow:**
*   Sends requests to authentication endpoints.
*   Receives a `newToken` string if successful.

**Subsequent Actions:**
*   If a `newToken` is successfully acquired, it calls `this.storeToken(newToken)` to save the new token and its expiry.
*   It then calls `this.scheduleRefresh(newToken)` to set up an automatic refresh before the new token expires.

**Error Handling:**
*   Logs warnings if the token endpoint fails.
*   Logs errors if the overall refresh process fails and returns `null`.

**Source:** [apps/app/src/utils/jwt-manager.ts:86-128](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts#L86-L128)

### 7. Store New Token and Expiry

The `jwtManager.storeToken()` method is responsible for persisting the newly acquired JWT and its expiration timestamp in `localStorage`. This allows the application to retrieve the token quickly for subsequent authenticated requests without needing to refresh it every time.

**Data Flow:**
*   Takes the `token` string as input.
*   Calls `this.decodeJWTPayload(token)` to extract the expiration time (`exp`) from the token's payload.
*   Stores the `token` under `this.STORAGE_KEY` and the `expiresAt` (converted to milliseconds) under `this.EXPIRY_KEY` in `localStorage`.

**Error Handling:**
*   Catches and logs any errors that occur during the storage process, particularly if `decodeJWTPayload` fails.

**Source:** [apps/app/src/utils/jwt-manager.ts:135-146](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts#L135-L146)

### 8. Decode JWT Payload

The `jwtManager.decodeJWTPayload()` method is a utility function used to parse the base64-encoded payload of a JWT. This is a client-side operation and does not involve cryptographic verification, as its purpose is simply to extract information like the expiration timestamp (`exp`) for local token management.

**Inputs:**
*   `token`: The full JWT string.

**Process:**
1.  Splits the JWT into its three parts (header, payload, signature) by the `.` delimiter.
2.  Takes the second part (the payload).
3.  Uses `atob()` to base64-decode the payload string.
4.  Parses the resulting string as JSON.

**Output:** The parsed JSON object representing the JWT payload.

**Error Handling:**
*   If the token format is invalid (e.g., not enough parts, or base64 decoding/JSON parsing fails), it throws an `Error('Invalid JWT token format')`.

**Source:** [apps/app/src/utils/jwt-manager.ts:170-177](https://github.com/blade47/comp/blob/main/apps/app/src/utils/jwt-manager.ts#L170-L177)

### Sequence Diagram

```mermaid
sequenceDiagram
    participant SingleTask as SingleTask.tsx
    participant EvidenceDownload as evidence-download.ts
    participant JWTManager as jwt-manager.ts
    participant AuthService as Auth Service/API

    SingleTask->>EvidenceDownload: downloadTaskEvidenceZip(taskId, title, orgId, ...)
    activate EvidenceDownload
    EvidenceDownload->>EvidenceDownload: Construct API URL
    EvidenceDownload->>EvidenceDownload: downloadFile(url, orgId, fallback)
    activate EvidenceDownload
    EvidenceDownload->>JWTManager: getValidToken()
    activate JWTManager
    JWTManager->>JWTManager: Check stored token & expiry
    alt Token invalid or expiring soon
        JWTManager->>JWTManager: refreshToken()
        activate JWTManager
        JWTManager->>JWTManager: Check refresh in progress / cooldown
        alt Refresh in progress or cooldown active
            JWTManager-->>JWTManager: Wait / Return existing token
        else No active refresh / cooldown
            JWTManager->>JWTManager: _doRefreshToken()
            activate JWTManager
            JWTManager->>AuthService: authClient.getSession()
            AuthService-->>JWTManager: Session response (with JWT header?)
            alt No JWT from session
                JWTManager->>AuthService: fetch('/api/auth/token')
                AuthService-->>JWTManager: Token response (JSON)
            end
            JWTManager->>JWTManager: storeToken(newToken)
            activate JWTManager
            JWTManager->>JWTManager: decodeJWTPayload(newToken)
            activate JWTManager
            JWTManager-->>JWTManager: Returns payload
            deactivate JWTManager
            JWTManager->>JWTManager: Store token & expiry in localStorage
            deactivate JWTManager
            JWTManager->>JWTManager: scheduleRefresh(newToken)
            JWTManager-->>JWTManager: Returns newToken
            deactivate JWTManager
        end
        JWTManager-->>EvidenceDownload: Returns validToken
        deactivate JWTManager
    else Token valid and not expiring soon
        JWTManager-->>EvidenceDownload: Returns storedToken
        deactivate JWTManager
    end
    EvidenceDownload->>EvidenceDownload: Add Authorization header
    EvidenceDownload->>AuthService: fetch(downloadUrl, { headers })
    AuthService-->>EvidenceDownload: File Blob & Content-Disposition
    EvidenceDownload->>EvidenceDownload: Process Blob, create download link
    EvidenceDownload-->>SingleTask: File download initiated
    deactivate EvidenceDownload
    deactivate EvidenceDownload
```

### Flowchart



### Key Observations

*   **Cross-Module Boundaries:** This flow demonstrates clear separation of concerns across different modules:
    *   `SingleTask.tsx`: Handles UI interaction and initiates the high-level action.
    *   `evidence-download.ts`: Manages the specifics of file downloading, including API endpoint construction and client-side download mechanics.
    *   `jwt-manager.ts`: Centralizes all JWT-related operations, such as token retrieval, refresh, storage, and decoding, abstracting authentication details from the download logic.
*   **Potential Failure Points and Handling:**
    *   **Authentication Failure:** If `jwtManager.getValidToken()` or `_doRefreshToken()` fails to acquire a token, the `downloadFile` function will throw an "Authentication failed" error, which is then caught by `SingleTask` and displayed as a `toast.error`.
    *   **Network Errors:** `fetch` requests in `downloadFile` and `_doRefreshToken` can fail due to network issues or API unavailability. These are caught and reported to the user via `toast.error`.
    *   **Invalid JWT Format:** `decodeJWTPayload` explicitly checks for valid JWT structure and throws an error if parsing fails, preventing corrupted tokens from being used.
    *   **Concurrent Refreshes:** The `jwtManager` uses `refreshPromise` and `REFRESH_COOLDOWN` to prevent multiple token refresh requests from being sent simultaneously, which could lead to race conditions or unnecessary load on the authentication service.
*   **Performance Considerations:**
    *   **Token Caching:** JWTs are stored in `localStorage` and reused, reducing the need for frequent authentication requests.
    *   **Proactive Refresh:** The `scheduleRefresh` mechanism attempts to refresh the token a few minutes before its actual expiration (`REFRESH_THRESHOLD`), ensuring that a valid token is usually available when an API call is made, minimizing latency for authenticated requests.
    *   **Optimistic Refresh:** The `refreshToken` method's handling of `refreshPromise` means that if multiple parts of the application simultaneously request a token refresh, only one actual refresh operation is performed, and all callers await its result.

## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/49c89830-117b-4def-8edc-b5bcc50766e0/llms.txt) for all pages in this wiki.
