PKCE flow
About authenticating with PKCE flow.
The Proof Key for Code Exchange (PKCE) flow is one of two ways that a user can authenticate and your app can receive the necessary access and refresh tokens.
The flow is an implementation detail handled for you by Supabase Auth, but understanding the difference between PKCE and implicit flow is important for understanding the difference between client-only and server-side auth.
How it works#
After a successful verification, the user is redirected to your app with a URL that looks like this:
1https://yourapp.com/...?code=<...>The code parameter is commonly known as the Auth Code and can be exchanged for an access token by calling exchangeCodeForSession(code).
For security purposes, the code has a validity of 5 minutes and can only be exchanged for an access token once. You will need to restart the authentication flow from scratch if you wish to obtain a new access token.
As the flow is run server side, localStorage may not be available. You may configure the client library to use a custom storage adapter and an alternate backing storage such as cookies by setting the storage option to an object with the following methods:
1import { type SupportedStorage } from '@supabase/supabase-js';2const supportsLocalStorage = () => true34// ---cut---5const customStorageAdapter: SupportedStorage = {6 getItem: (key) => {7 if (!supportsLocalStorage()) {8 // Configure alternate storage9 return null10 }11 return globalThis.localStorage.getItem(key)12 },13 setItem: (key, value) => {14 if (!supportsLocalStorage()) {15 // Configure alternate storage here16 return17 }18 globalThis.localStorage.setItem(key, value)19 },20 removeItem: (key) => {21 if (!supportsLocalStorage()) {22 // Configure alternate storage here23 return24 }25 globalThis.localStorage.removeItem(key)26 },27}You may also configure the client library to automatically exchange it for a session after a successful redirect. This can be done by setting the detectSessionInUrl option to true.
Putting it all together, your client library initialization may look like this:
1import { createClient } from '@supabase/supabase-js'23// ---cut---4const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...', {5 // ...6 auth: {7 // ...8 detectSessionInUrl: true,9 flowType: 'pkce',10 storage: {11 getItem: () => Promise.resolve('FETCHED_TOKEN'),12 setItem: () => {},13 removeItem: () => {},14 },15 },16 // ...17})Limitations#
Behind the scenes, the code exchange requires a code verifier. Both the code in the URL and the code verifier are sent back to the Auth server for a successful exchange.
The code verifier is created and stored locally when the Auth flow is first initiated. That means the code exchange must be initiated on the same browser and device where the flow was started.
Overlapping flows#
If more than one PKCE flow is started on the same browser before either one completes (for example, signInWithOAuth() called in two tabs), the code verifier stored for the earlier flow is overwritten by the later one, and exchanging the first flow's code fails.
Experimental
Support for overlapping flows is currently experimental and requires explicit opt-in as the API may change without notice.
To keep each flow's verifier separate, set the appendPkceFlowIdToRedirects option when creating the client:
1const supabase = createClient(supabaseUrl, supabaseKey, {2 auth: {3 experimental: { appendPkceFlowIdToRedirects: true },4 },5})With this enabled, the client library appends a sb_flow_id query parameter to redirectTo, so your OAuth callback page can read it back and use it to select the matching verifier. You can also get the flow ID directly from the response of signInWithOAuth():
1const { data, error } = await supabase.auth.signInWithOAuth({2 provider: 'github',3})45const flowId = data.flowIdPass the flow ID to exchangeCodeForSession() to make sure the correct verifier is used, whether you read it from data.flowId or from the sb_flow_id query parameter in the redirect URL:
1const { data, error } = await supabase.auth.exchangeCodeForSession(authCode, { flowId })Resources#
- OAuth 2.0 guide to PKCE flow