TokenStore and Session Management

The TokenStore establishes the process to exchange session tokens between your application and Passage. By providing a custom TokenStore to PassageJS, you can declare how the underlying PassageJS methods will get, set, and clear session tokens. For more details on the PassageJS TokenStore, see here.

Get a token

First, you will need to implement the getAuthToken method that tells Passage where to access the legacy session token. This method is used when Passage needs to retrieve a session token to perform an authenticated action (such as adding or removing a passkey). If your application uses a cookie called session to store tokens, your TokenStore would look like this:

export class MyTokenStore extends TokenStore {    
    getAuthToken() {
        // get token from a cookie named "session"
        const authToken = document.cookie
            .split('; ')
            .find((row) => row.startsWith('session='))
            ?.split('=')[1];
        return Promise.resolve(authToken);
    }
};

If your application uses cookies, you need to make sure the cookie is NOT marked as httpOnly. This is required in order to read the cookies from JavaScript and send them to Passage.

As another example, if your application uses JWTs stored in localStorage to set HTTP Authorization headers, your getAuthToken method might look like this:


export class MyTokenStore extends TokenStore {
    getAuthToken() {
        const legacyToken = localStorage.getItem('legacy_token')
        return Promise.resolve(JSON.parse(legacyToken)?.token);
    }
};

Set a token

Next, you will implement a setTokens method that is executed after a successful login. This method provides you with the authResult from Passage which has a form of:

interface AuthResult {
  redirect_url: string;
  auth_token: string;
  refresh_token: string; // if refresh tokens are enabled
  refresh_token_expiration: number; // if refresh tokens are enabled
}

With this data object you can decide what you want to do on success: set the token in localStorage, exchange the PassageJWT for a legacy session token, etc.

Local Storage Example
import { BehaviorSubject } from 'rxjs';
import getConfig from 'next/config';
import { fetchWrapper } from 'helpers';
import { TokenStore } from '@passageidentity/passage-js';

const { publicRuntimeConfig } = getConfig();
const baseUrl = `${publicRuntimeConfig.apiUrl}/users`;
const userSubject = new BehaviorSubject(process.browser && JSON.parse(localStorage.getItem('legacy_token')));

export class MyTokenStore extends TokenStore {
    getAuthToken() {
        const legacyToken = localStorage.getItem('legacy_token')
        return Promise.resolve(JSON.parse(legacyToken)?.token);
    }
    
    setTokens(authResult){
        // Get the PassageAuth result and swap it with your legacy token
        // then set it in localstorage
        const passageToken = authResult.auth_token;
        return fetchWrapper.post(`${baseUrl}/token`, {passageToken})
            .then(token => {
                userSubject.next(token);
                localStorage.setItem('legacy_token', JSON.stringify(token));
                return token;
        }) 
    }
}
Cookies Example

export class MyTokenStore extends TokenStore {
    getAuthToken() {
        // get token from a cookie named "session"
        const authToken = document.cookie
            .split('; ')
            .find((row) => row.startsWith('session='))
            ?.split('=')[1];
        return Promise.resolve(authToken);
    }
    
    setTokens(authResult) {
        await fetch(
            '/passage',
            {
                method: "GET",
                redirect: 'follow',
                headers: {
                    Cookie: "psg_auth_token="+authResult.auth_token,
                    "Content-Type": "application/json; charset=UTF-8",
                },
            }
        ).then(
            response => {
                // cookie gets automatically set by backend here
                return Promise.resolve(authResult.auth_token)
            }
        )
    }
};

Swap Token Endpoint

The final point to consider is what happens after a user logs in with Passage. After a successful login, Passage will provide the client with a Passage JWT. The easiest option is to create an endpoint that verififes a Passage JWT and returns a legacy session token for the user. By "swapping" the Passage JWT for a legacy session token, you will not need to modify your existing user authorization checks.

As an example, here is a Python Flask endpoint that exchanges a Passage JWT for a flask-login session. You can see examples for other languages in our GitHub examples.

import Passage, PassageError from passageidentity

@auth.route("/passage", methods=["GET"])
def passage():
    try:
        # Verify Passage JWT and retrieve user's email from Passage
        userID = psg.authenticateRequest(request)
        psgUser = psg.getUser(userID)
        
        # Lookup user by email
        user = User.query.filter_by(email=psgUser.email).first()
        if not user:
            return redirect(url_for('auth.login')) 

        # We have a user!
        login_user(user)
        return redirect(url_for('main.profile'))
        
    except PassageError as e:
        return redirect(url_for('auth.login'))

If you don't want to make an "exchange token" endpoint, or your existing authentication provider won't allow for it, you can also use a Passage SDK to directly authorize requests to your backend. Your authorization code will need to support Passage auth tokens alongside your existing token solution. More details on this model coming soon, but feel free to reach out to us if you want more details at [email protected].

Last updated

Change request #337: react native