Skip to content

Choose an adapter ​

An adapter translates one provider SDK onto Anchor's session contract and nothing more. You pick the one for the SDK your application already uses; your code above it, anchor.session, session.getToken() and session.end(), stays the same when the adapter changes.

ts
import { Anchor } from "@priemskiyyy/anchor";
import { accessToken, oidc } from "@priemskiyyy/anchor-oidc";
import { UserManager } from "oidc-client-ts";

const userManager = new UserManager({
  authority: "https://id.example.com/realms/acme",
  client_id: "web",
  redirect_uri: `${location.origin}/callback`,
});

export const oidcAnchor = new Anchor({
  adapter: oidc({ sdk: userManager, tokens: { api: accessToken() } }),
});

Client adapters ​

ProviderPackageObservationToken profilesForced refreshEnd: local and remoteNavigation after endRuntimes
Firebaseanchor-firebasepush, onIdTokenChangedidToken()yeslocal; no remote stepneverbrowsers, React Native through the Firebase JS SDK
Firebase on React Nativeanchor-firebase-react-nativepush, onIdTokenChangedidToken()yeslocal, this app on this device; no remote stepneveriOS and Android
Supabaseanchor-supabasepush, onAuthStateChangeaccessToken()yeslocal and remote, scope: "local"neverbrowsers, React Native and Expo, Node
Auth0anchor-auth0none: anchor.sync() after a loginaccessToken({ audience, scope })yeslocal; remote pending until the logout page is openedAuth0's /v2/logout pagebrowsers
Auth0 on React Nativeanchor-auth0-react-nativenone: anchor.sync() after a loginaccessToken(), accessToken({ audience, scope })yeslocal; remote unknown until webAuth.clearSession()neveriOS and Android
Better Authanchor-better-authpush, the session atomsessionToken(), or the session cookierefreshes the session atomlocal and remote; offline on React Native, local onlyneverbrowsers, React Native and Expo, Node
Clerkanchor-clerkpush, addListenersessionToken({ template })yeslocal and remote, signOut({ sessionId }); offline, local onlyneverbrowsers, React Native and Expo through Clerk Expo
Microsoft Entra IDanchor-msalinvalidation, addEventCallbackaccessToken({ scopes })yeslocal, that account only; remote unknownneverbrowsers
Amazon Cognitoanchor-amplifyHub.listen("auth"): signedOut pushed, the rest invalidateaccessToken(), idToken()yeslocal, this device; remote unknownnever: the hosted UI opens its ownbrowsers, React Native and Expo, Node
WorkOS AuthKitanchor-workosinvalidation, through the workosRefreshes() relayaccessToken()yeslocal; remote unknownneverbrowsers
Auth.jsanchor-authjsinvalidation from other tabs; anchor.sync() in the same tabnone: the session cookienot applicablelocal; remote unknownneverbrowsers
OpenID Connectanchor-oidcpush, userLoaded and userUnloadedaccessToken()yes, signinSilent()local; remote pending until the end-session page is openedthe provider's end-session pagebrowsers
Keycloakanchor-keycloakpush, the callbacks it chainsaccessToken()yes; UNSUPPORTED without a refresh tokenlocal; remote pending until the logout page is openedKeycloak's logout pagebrowsers, Cordova
Oktaanchor-oktapush, authStateManager.subscribeaccessToken()yeslocal, after revoking the tokens; remote pendingOkta's sign-out pagebrowsers, Node
Your own backendanchor-httpnone: anchor.sync() after a sign-in or sign-outany: one function of yours per profilepassed on as forceRefreshlocal and remote, once your end resolvesneveranywhere your client runs

Every package is named @priemskiyyy/ and the name in the table. "Push" means the adapter hands Anchor a complete answer when the SDK reports a change; "invalidation" means it asks Anchor to read; "none" means the SDK emits nothing, so call anchor.sync() after a native call that changes the session. Remote "pending" or "unknown" means the provider's own session may outlive the ending until the browser visits a page or the provider expires it; see ending a session.

Server adapters ​

For request-scoped reads with @priemskiyyy/anchor-server:

ProviderPackageReads a request through
Better Authanchor-better-auth-nodeauth.api.getSession({ headers, returnHeaders: true })
Firebaseanchor-firebase-nodefirebase-admin's verifyIdToken() and verifySessionCookie()
Supabaseanchor-supabase-nodea @supabase/ssr server client's auth.getUser()
Clerkanchor-clerk-node@clerk/backend's authenticateRequest()
WorkOSanchor-workos-node@workos-inc/node's loadSealedSession() and authenticate()

Each maps a session as its client adapter does, so one user is one principal on both sides. Auth.js has no server adapter yet: next-auth 4 exports no request-scoped read over a plain Request, and its server read answers null for a forged token, a throwing callback and a failing database alike, so it cannot tell no session from a failure.

You own the SDK ​

Every adapter takes the SDK your application already set up as its sdk option, typed structurally so the adapter names only what it calls, and imports nothing from the provider at runtime. You initialize it, configure it and sign in with it; Anchor only observes and calls it. anchor.native is that SDK with its own type, for everything Anchor does not normalize. Creating an adapter touches nothing on the SDK, so it may run at module scope and on a server.

One package serves one SDK API. Where a provider's React Native SDK has an API of its own, as React Native Firebase and react-native-auth0 do, it has its own package whose factory keeps the provider's name. Where the SDK runs its web API on React Native, as supabase-js, Better Auth, Clerk Expo and Amplify do, the web adapter serves both. See React Native and Expo.

Something else ​

A provider that speaks OpenID Connect, such as Zitadel, Authentik or Cognito's hosted UI, is served by the OpenID Connect adapter over oidc-client-ts. A backend of your own is served by the HTTP adapter over your own client. Anything else is a small adapter of your own: see writing an adapter. To move an application from one provider to another, see migrating between providers.

Released under the MIT License.