auth-js
auth-js copied to clipboard
Enable third party auth from access token and/or code
Feature request
Ability to use access token or other credential received from OAuth flow to enable third party auth.
Is your feature request related to a problem? Please describe.
I am using Expo for my app which takes care a lot of the nuances in handling OAuth flows in a React Native / Expo managed app: https://docs.expo.io/guides/authentication/. Right now, trying to use the built-in provider flows from Supabase JS client is not working.
Describe the solution you'd like
I would like a way to send an access token or other credential received from an OAuth flow to Supabase to facilitate the login.
Describe alternatives you've considered
Not offering third party auth.
Hey @davitykale - we do already have OAuth providers here: https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers
Is there something that isn't working with Expo?
@kiwicopple, when I try that flow with Expo, nothing happens -- "user" and "error" in that example are just null
Oh I see - I think perhaps becuase it's trying to open a new window. And actually the redirect wouldn't work 🤔 .
We don't want to build components specifically for Expo, so I wonder if there is some way to "hook into" these existing implementation?
@kiwicopple that's what I was hoping! Expo enables the OAuth providers individually (and can return an access token or other identifiers). I'm wondering if there's a way I could pass the access token to Supabase instead of having Supabase handle the new window/redirect flow.
OK cool. We're not too familiar with Expo, so for now I will label it as help wanted. Hopefully someone can spec up the implementation for us. Perhaps you have some ideas already @davitykale ?
@kiwicopple The solution shouldn’t be around calling /auth/v1/callback (https://github.com/supabase/gotrue#get-callback) directly?
Expo returns all identifiers needed (access_token, refresh_token) so we just need to expose on gotrue-js a function that receives all the identifiers and internally calls the /callback api with the correct query params for the corresponding provider.
Thoughts?
I've been attempting to get this working today, and I made a bit of progress:
- Use expo's
AuthSession.startAsyncpointing towardshttps://<YOUR_SUPABASE_URL>.supabase.co/auth/v1/authorize?provider=${provider}(whereprovideris one of the providers supported by Supabase) - The
returnUrlI set wasexp://my.local.ip:19000/--/auth/callback- this is something that needs further investigation on how we handle this in a deployed app, but that should be covered by the deep linking or auth guides, and is outside the scope of Supabase - (Possibly optional) Ensure
WebBrowser.maybeCompleteAuthSession();is set - in my case, it was set before my react native function forApp()- you could and should put this in a dedicated login screen - Hook up a button or other pressable element to call the function you made (the one which calls
authSession.startAsync. This will open the browser and allow the user to auth with third-party - Assuming that auth succeeds, it'll return things such as
access_token,expires_in,refresh_token, etc.
The access_token is a JWT, so this needs to be decoded using the key provided in your dashboard - This is something that should be handled by Supabase, as we definitely don't want to be doing that inside the app (as it means hardcoding the key which could be pulled from the react-native bundle).
From there, I tried calling AsyncStorage.setItem("supabase.auth.token", {...}) - I attempted to recreate the same structure used by Supabase (found by signing in with email and password and then pulling the details out of AsyncStorage), and passing that as the 2nd argument.
I chained a .then() onto the end of the setItem call, and tried supabaseClient.auth.refreshSession(). Unfortunately, even though the details were correctly set in AsyncStorage, Supabase still reported the user was not logged in when logging supabaseClient.auth.session. I didn't anticipate it would work, but I hoped I was on the right tracks.
In any case, I believe the solution is to expose a method which allows us to pass a few details such as access_token, refresh_token, etc. in order to auth a user - providing a callback (or async method) which returns the user's details from the Supabase platform once successful seems to be the way to go.
Expo returns all identifiers needed (access_token, refresh_token) so we just need to expose on gotrue-js a function that receives all the identifiers and internally calls the /callback api with the correct query params for the corresponding provider.
this was my initial thought. I think it's worth a shot
The access_token is a JWT, so this needs to be decoded using the key provided in your dashboard - This is something that should be handled by Supabase, as we definitely don't want to be doing that inside the app (as it means hardcoding the key which could be pulled from the react-native bundle).
it's actually possible to decode the contents of the JWT without the secret (you just can't verify that the signature is legit)
The returnUrl I set was exp://my.local.ip:19000/--/auth/callback - this is something that needs further investigation on how we handle this in a deployed app, but that should be covered by the deep linking or auth guides, and is outside the scope of Supabase
deep linking via redirects should now possible by setting the exact links in Additional Redirect URLs in the dashboard (although I haven't personally tested this in a mobile environment)

@awalias, my suggestion would be to just add access_token and refresh_token as parameters, in addition to provider, to the signIn() function. As @ChronSyn suggested gotrue-js should then call the /callback api with the tokens and return user data. Please let me know how I can assist.
yes I think this makes sense @He1nr1chK . feel free to make a PR if you have time 👍
otherwise I will try and get round it it this week
So after a bit of a wild goose chase around the moving parts here I've rm -rf'd my earlier posts since they are pointless 🤣...and come up with a tiny PR that calls the /token endpoint with a refresh_token obtained using Expo AuthSession.startAsync as @ChronSyn mentioned above to get the session back 😅
So after a bit of a wild goose chase around the moving parts here I've rm -rf'd my earlier posts since they are pointless 🤣...and come up with a tiny PR that calls the
/tokenendpoint with arefresh_tokenobtained using Expo AuthSession.startAsync as @ChronSyn mentioned above to get the session back 😅
Awesome, can't wait to see it 😄
The brick wall I hit was when trying to find a way to force the Supabase JS lib to accept some state I'd retrieved from AuthSession. If doing a call to /token with a refresh token is the way to go, and it's a method that can be exposed from within Supabase, lib then it sounds like you're on the right track 👍
Yep, it extends the auth.signIn (gotrue-js client signIn()) method to receive the refresh_token :) here's a quick example of usage:
startAsync({
authUrl: `https://MYSUPABASEAPP.supabase.co/auth/v1/authorize?provider=google&redirect_to=${redirectUri}`,
returnUrl: redirectUri,
}).then(async (response: any) => {
if (!response) return;
const supaResponse = await supabaseClient.auth.signIn({
refreshToken: response.params?.refresh_token,
});
});
@jpstrikesback thank you so much for the incredible work on this! Works like a charm for Google and Facebook auth 😀
Is it safe to assume that this should work for Apple Auth as well?
Cheers @davitykale 🙏 it does work for sign in with Apple from my experience
@davitykale - it works with Expo's AuthSession, which uses a web-based Apple authentication flow. It won't work with Expo's expo-apple-authentication, which uses the native device.
@jpstrikesback did you see any path towards using an authorization code obtained from the native device authentication flow, and passing it with user data to create a new supabase user?
I do know that Auth0 has a special code exchange endpoint in their API just for handling this case. It takes the code and user details - so I imagine a similar endpoint would need to be added to the GoTrue API to enable native flows.
Hi @heysailor I asked a similar question in Discussions if you would like to follow it there. https://github.com/supabase/supabase/discussions/2204
Just tested the approach with github provider + expo go, it worked as well. It took me a bit of time to understand how to glue all the things together to make it work but in the end, it's pretty straightforward. For those who are looking for a full example as I was, here it is 😆
import React from "react";
import { Button } from "@components/button";
import { supabase } from "@lib/supabase";
import { startAsync } from "expo-auth-session";
import * as Linking from "expo-linking";
interface ProviderResponse {
params?: {
refresh_token: string;
};
}
export function Playground() {
async function handleLogin() {
// Create a URL that works for the environment the app is currently running in
// Expo Client (dev): exp://128.0.0.1:19000/--/path
// Expo Client (prod): exp://exp.host/@yourname/your-app/--/path
const returnUrl = Linking.makeUrl("/auth/callback");
const payload = (await startAsync({
authUrl: `https://yoursupabase.supabase.co/auth/v1/authorize?provider=github&redirect_to=${returnUrl}`,
returnUrl,
})) as ProviderResponse;
const response = await supabase.auth.signIn({
refreshToken: payload.params?.refresh_token,
});
console.log(response)
}
return <Button onPress={handleLogin}>Login</Button>;
}
Update your supabase config as well

Hope this help.
Just tested the approach with github provider + expo go, it worked as well. It took me a bit of time to understand how to glue all the things together to make it work but in the end, it's pretty straightforward. For those who are looking for a full example as I was, here it is 😆
snip
Hope this help.
This is definitely a good example to put in the docs
There is also my example here if that helps: https://github.com/supabase/supabase/discussions/2489#discussioncomment-1050095
Is there any way to Prompt.SelectAccount using this approach?
Is there any way to Prompt.SelectAccount using this approach?
@eifo I'm not familiar with Prompt.SelectAccount, do you have an example to share maybe? What are you trying to accomplish?
@fkhadra trying to change the google account to sign in when you have multiple accounts, it's connecting with primary account by default without letting you choose.The only way I found it to work is with Expo's useAuthRequest hook but can't get it to work with supabase. This is how it's used...
const [request, response, promptAsync] = useAuthRequest( { clientId: '[GUID].apps.googleusercontent.com', redirectUri, prompt: Prompt.SelectAccount, scopes: ['openid', 'profile'], }, discovery, ); return [request, response, promptAsync]; };
@eifo weird I don't have this issue, I'm able to select which account to use.
https://user-images.githubusercontent.com/5574267/128074015-5c565ac8-8269-4934-aae2-63f4ab014740.MP4
I'm not using the useAuthRequest hook, don't know if this makes any difference.
@fkhadra thanks for sharing! So weird, I can't understand why I'm not being prompted for my account, something is off.
@fkhadra thanks.
How would the authUrl look when using a simple email or phone signin?
Hey @10000multiplier, the example I provided above is what I use for third party login(github, google, etc...). For email authentication, I simply use the supabase client as follow:
supabase.auth.signIn(
password
? {
email,
password,
}
// password less login
: { email }
);
Haven't tried the phone signin yet.
@fkhadra thank you.
I think it just works doing
supabase.auth.signIn(
{
email,
password,
}
);
@fkhadra thanks for sharing your example.. I'm using expo go + google and facebook. everything works great except the redirect. my app's default page is opening after authentication, instead of the path specified. I have configured deep links in my app and they work correctly. I have added it to the list of redirect url as well in supabase any help here is appreciated.
Just tested the approach with github provider + expo go, it worked as well. It took me a bit of time to understand how to glue all the things together to make it work but in the end, it's pretty straightforward. For those who are looking for a full example as I was, here it is 😆
import React from "react"; import { Button } from "@components/button"; import { supabase } from "@lib/supabase"; import { startAsync } from "expo-auth-session"; import * as Linking from "expo-linking"; interface ProviderResponse { params?: { refresh_token: string; }; } export function Playground() { async function handleLogin() { // Create a URL that works for the environment the app is currently running in // Expo Client (dev): exp://128.0.0.1:19000/--/path // Expo Client (prod): exp://exp.host/@yourname/your-app/--/path const returnUrl = Linking.makeUrl("/auth/callback"); const payload = (await startAsync({ authUrl: `https://yoursupabase.supabase.co/auth/v1/authorize?provider=github&redirect_to=${returnUrl}`, returnUrl, })) as ProviderResponse; const response = await supabase.auth.signIn({ refreshToken: payload.params?.refresh_token, }); console.log(response) } return <Button onPress={handleLogin}>Login</Button>; }Update your supabase config as well
Hope this help.
Sign in based on refresh token will just work once. How can I auto-refresh the token? I have a use-case where I have to pass a refresh token to a subdomain and this could be any number of different subdomains. How do I ensure I can keep signing in with the refresh token for every new subdomain created?
One possible solution would be to use the access_token with setAuth
const { user, error } = supabase.auth.setAuth(access_token)
but this doesn't work :(
