# Tokens Renewal

Many OpenID Connect adapters, end up implementing token renewal with a background refresh loop.\
That approach often creates avoidable load and some tricky edge cases.\
With `oidc-spa`, token lifecycle management is handled for you and stays out of your app code.

***

**The Problem With Access Token Refresh Loops**

Access tokens are meant to be **short-lived** (typically \~5 minutes, but sometimes as little as 20 seconds for high-security apps).\
Many adapters try to **keep an access token “always fresh” in cache**, which leads to:

* Constant background refreshes
* Heavy load on your auth server
* Agravated load when mutiple tabs are open on your app.

This isn’t needed. You don’t need a valid access token cached at all times.

***

**The Better Approach (What `oidc-spa` Does)**

Whenever you need to make an authenticated request, just **ask `oidc-spa` for a token**:

```ts
const oidc = await getOidc();

if (!oidc.isUserLoggedIn) {
    throw Error("Logical error in our application flow");
}

const { accessToken } = await oidc.getTokens();
headers.set("Authorization", `Bearer ${accessToken}`);
```

* If a valid token is cached, you’ll get it.
* If it’s expired or soon to expire, `oidc-spa` silently refreshes it using the refresh token.

Example: [interceptor pattern](https://github.com/InseeFrLab/onyxia/blob/2f7bad234099719debc15ecdaba30dba116ffef9/web/src/core/adapters/onyxiaApi/onyxiaApi.ts#L34-L84)\
Example: [custom fetch](https://github.com/keycloakify/oidc-spa/blob/a1aae19e2b5a874159fbdfecaaf00be814bb4c6a/examples/tanstack-router-file-based/src/oidc.tsx#L64-L76)

**But what about session expiration?**&#x20;

Behind the scenes, `oidc-spa` ensures the session **never expires prematurely** by refreshing **at least once before the refresh token itself expires**.  \
This prevents the backend from destroying the session simply because the user wasn’t making authenticated requests (e.g., they’re filling out a form or browsing content). &#x20;

At the same time, `oidc-spa` tracks **actual user activity** (keyboard, mouse, touch). If the user is truly idle beyond the refresh token lifespan, they’re logged out as expected.

***

**Why `oidc-spa` Still Exposes `renewTokens()`**

There are two legitimate edge cases:

1. **After custom requests**: If you make a request to your OIDC server that changes claims in the `id_token` or `access_token`, call `renewTokens()` to ensure you have the latest values. (This is a rare use case. It usually happens when user info is updated outside your app. If you’re not sure, you can generally assume you don’t need this.)
2. Getting a freshly issued token: If at one point in time, you want to be sure that you have a freshly issued token with it's maximum lifetime you might want to call renewTokens() before you call getTokens()
3. **Custom token parameters**: If your OIDC server supports extra token endpoint params, you can trigger a refresh with them. (`extraTokenParams` is also available at `createOidc()` time.)

Outside of these rare cases, you never need to call `renewTokens()` manually.

{% tabs %}
{% tab title="Vanilla API" %}

```typescript
import { createOidc } from "oidc-spa/core";

const prOidc = await createOidc({ ... });

// Function to call when we want to renew the token
export function renewTokens(){

   const oidc = await prOidc;
   
   if( !oidc.isUserLoggedIn ){
      throw new Error("Logical error");
   }
   
   oidc.renewTokens(
      // Optionally you can pass extra params that will be added 
      // to the body of the POST request to the openid-connect/token endpoint.
      // { extraTokenParams: { electedCustomer: "customer123" } }
      // This parameter can also be provided as parameter to the createOidc
      // function. See: https://github.com/keycloakify/oidc-spa/blob/59b8db7db0b47c84e8f383a86677e88e884887cb/src/oidc.ts#L153-L163
   );

}

// Subscribing to token renewal

prOidc.then(oidc => {
    if( !oidc.isUserLoggedIn ){
        return;
    }
    
    const { 
       unsubscribeFromTokensChange 
    } = oidc.subscribeToTokensChange(tokens => {
       console.log("Token Renewed", tokens);
    });
    
    setTimeout(() => {
        // Call unsubscribe when you want to stop watching tokens change
        unsubscribeFromTokensChange();
    }, 10_000);
});
```

{% endtab %}

{% tab title="React API" %}
Outside of a React Component:

```typescript
import { getOidc } from "~/oidc";

// Function to call when we want to renew the token
export function renewTokens(){

   const oidc = await getOidc({ assert: "user logged in" });
   
   oidc.renewTokens(
      // Optionally you can pass extra params that will be added 
      // to the body of the POST request to the openid-connect/token endpoint.
      // { extraTokenParams: { electedCustomer: "customer123" } }
      // This parameter can also be provided as parameter to the createOidc
      // function. See: https://github.com/keycloakify/oidc-spa/blob/59b8db7db0b47c84e8f383a86677e88e884887cb/src/oidc.ts#L153-L163
   );

}

// Subscribing to token renewal

getOidc().then(oidc => {
    if( !oidc.isUserLoggedIn ){
        return;
    }
    
    const { 
       unsubscribeFromAccessTokenRotation 
    } = oidc.subscribeToAccessTokenRotation(accessToken => {
       console.log("Access Token Rotated!", accessToken);
    });
    
    const {
         unsubscribeFromDecodedIdTokenChange
     } = oidc.subscribeToDecodedIdTokenChange(decodedIdToken => {
         console.log(`Decoded id token change`, decodedIdToken);
     });
    
    
    setTimeout(() => {
        // Call unsubscribe when you want to stop watching tokens change
        unsubscribeFromAccessTokenRotation();
        unsubscribeFromDecodedIdTokenChange();
    }, 10_000);
});
```

```tsx
import { useState, useEffect } from "react";
import { assert } from "tsafe/assert";
import { useOidc, getOidc } from "~/oidc";

export function MyComponent() {
    const { renewTokens } = useOidc({ assert: "user logged in" });
    return (
        <>
            <button onClick={() => renewTokens()}>Rotate tokens</button>
        </>
    );
}
```

{% endtab %}

{% tab title="Angular" %}
{% code title="src/app/app.ts" %}

```angular-ts
@Component({
  selector: 'app-root',
  templateUrl: './app.html',
})
export class App {
  oidc = inject(Oidc);

  constructor(){

    // Subscribing to token rotation: 
    this.oidc.accessTokenRotation$.subscribe(accessToken => {
      console.log(`Access Token Rotation: ${accessToken}`);
    });

    // Triggering token rotation manually
    setTimeout(()=> {

      this.oidc.renewTokens(/* ... optionally some params */);

    }, 10_000);

  }

}
```

{% endcode %}
{% endtab %}
{% endtabs %}


---

# 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://docs.oidc-spa.dev/docs/v9/features/tokens-renewal.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.
