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:
exportclassMyTokenStoreextendsTokenStore { getAuthToken() {// get token from a cookie named "session"constauthToken=document.cookie.split('; ').find((row) =>row.startsWith('session='))?.split('=')[1];returnPromise.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:
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:
interfaceAuthResult { 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();constbaseUrl=`${publicRuntimeConfig.apiUrl}/users`;constuserSubject=newBehaviorSubject(process.browser &&JSON.parse(localStorage.getItem('legacy_token')));exportclassMyTokenStoreextendsTokenStore {getAuthToken() {constlegacyToken=localStorage.getItem('legacy_token')returnPromise.resolve(JSON.parse(legacyToken)?.token); }setTokens(authResult){// Get the PassageAuth result and swap it with your legacy token// then set it in localstorageconstpassageToken=authResult.auth_token;returnfetchWrapper.post(`${baseUrl}/token`, {passageToken}).then(token => {userSubject.next(token);localStorage.setItem('legacy_token',JSON.stringify(token));return token; }) }}
Cookies Example
exportclassMyTokenStoreextendsTokenStore {getAuthToken() {// get token from a cookie named "session"constauthToken=document.cookie.split('; ').find((row) =>row.startsWith('session='))?.split('=')[1];returnPromise.resolve(authToken); }setTokens(authResult) {awaitfetch('/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 herereturnPromise.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"])defpassage():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()ifnot user:returnredirect(url_for('auth.login'))# We have a user!login_user(user)returnredirect(url_for('main.profile'))except PassageError as e:returnredirect(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].