Closed davitykale-zz closed 1 year ago
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:
AuthSession.startAsync
pointing towards https://<YOUR_SUPABASE_URL>.supabase.co/auth/v1/authorize?provider=${provider}
(where provider
is one of the providers supported by Supabase)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 SupabaseWebBrowser.maybeCompleteAuthSession();
is set - in my case, it was set before my react native function for App()
- you could and should put this in a dedicated login screenauthSession.startAsync
. This will open the browser and allow the user to auth with third-partyaccess_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
/token
endpoint with arefresh_token
obtained 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 :(
@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]; };
@fkhadra Do you know if it's possible to use useAuthRequest
to login in via supabase/provider. I see that in your example with startAsync
you pass supabase's auth url. But how do you do it in useAuthRequest
? I've tried to pass the url in discovery
and it seems to work but I don't get any response ( I get 'dismiss')
The problem I'm facing with startAsync
is that works like you say, but I cannot make it work in react-native-web (with NextJS). On opening the pop-up it loads a page localhost:3000/start' instead of the
auth.exp.io.../start` proxy. Apart that with startAsync it seems you cannot disable the proxy in production, right? Thx
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 want to say thank you for this. Helped me out almost a year later :)
Is the refreshToken approach going to work in with supabase-js 2.0 when signIn
is replaced?
Is the refreshToken approach going to work in with supabase-js 2.0 when
signIn
is replaced?
You could try this in v2 instead of singIn
if (payload.params?.refresh_token) {
const { data: { session, user}, error } = await supabase.auth.setSession(payload.params.refresh_token);
}
I'm having some issues with supabase.auth.setSession
in 2.0.0-rc.10 where calling that method seems to set the session, it returns the user and a new refresh_token. But calling supabase.auth.getUser
or supabase.auth.getSession
returns null for user and session respectively.
Maybe worth noting that I'm using this in a server context.
Edit any attempts to update rows protected by RLS tied to the user id fail.
supabase.auth.setSession
doesn't seem to work for this. The docs (and pixtron's example) seems to show that setSession
takes a refresh token as its args, but according to its type defnition, it requires an entire session object:
"Sets the session data from the current session. If the current session is expired, setSession will take care of refreshing it to obtain a new session. If the refresh token in the current session is invalid and the current session has expired, an error will be thrown. If the current session does not contain at expires_at field, setSession will use the exp claim defined in the access token.
@param currentSession — The current session that minimally contains an access token, refresh token and a user."
supabase.auth.setSession
doesn't seem to work for this. The docs (and pixtron's example) seems to show thatsetSession
takes a refresh token as its args, but according to its type defnition, it requires an entire session object:
@alrightsure Your right, the signature of the method has changed in #467 shortly after i left the comment. Now it would be:
const {access_token, refresh_token} = payload.params;
if (access_token && refresh_token) {
const { data: { session, user}, error } = await supabase.auth.setSession({access_token, refresh_token});
}
Is there any way we can provide social scopes
as params to startAsync
?
Hey everyone, this issue has been open for a while but I believe most of the underlying issues have been addressed.
Please switch to v2 of the client library to get all of the goodies. I'll close the issue for now.
supabase.auth.setSession
doesn't seem to work for this. The docs (and pixtron's example) seems to show thatsetSession
takes a refresh token as its args, but according to its type defnition, it requires an entire session object:@alrightsure Your right, the signature of the method has changed in #467 shortly after i left the comment. Now it would be:
const {access_token, refresh_token} = payload.params; if (access_token && refresh_token) { const { data: { session, user}, error } = await supabase.auth.setSession({access_token, refresh_token}); }
When I try to set the session object using access and refresh tokens from an OAuth provider as above, I get this error :) [Unhandled promise rejection: ReferenceError: Can't find variable: Buffer] at node_modules/@supabase/gotrue-js/dist/main/lib/helpers.js:125:25 in decodeBase64URL
supabase.auth.setSession
doesn't seem to work for this. The docs (and pixtron's example) seems to show thatsetSession
takes a refresh token as its args, but according to its type defnition, it requires an entire session object:@alrightsure Your right, the signature of the method has changed in #467 shortly after i left the comment. Now it would be:
const {access_token, refresh_token} = payload.params; if (access_token && refresh_token) { const { data: { session, user}, error } = await supabase.auth.setSession({access_token, refresh_token}); }
When I try to set the session object using access and refresh tokens from an OAuth provider as above, I get this error :) [Unhandled promise rejection: ReferenceError: Can't find variable: Buffer] at node_modules/@supabase/gotrue-js/dist/main/lib/helpers.js:125:25 in decodeBase64URL
Try installing buffer
via your package manager and then adding this to your App.tsx
:
import { Buffer } from 'buffer';
global.Buffer = Buffer;
That worked for me, thanks ! setting the session, somehow still doesn't trigger the state change as it is not recognized as a SIGN IN I guess. But we can work round that using the data from the session object. supabase.auth.onAuthStateChange((_event, session) => { console.log(_event, session) })
With expo SDK 48 iOS is not working with redirect_to. I'm getting: Validation Error: 'redirect_to' is not allowed
. Anyone else experiencing this?
With expo SDK 48 iOS is not working with redirect_to. I'm getting:
Validation Error: 'redirect_to' is not allowed
. Anyone else experiencing this?
Did you find a solution?
With expo SDK 48 iOS is not working with redirect_to. I'm getting:
Validation Error: 'redirect_to' is not allowed
. Anyone else experiencing this?
I also experience this issue. Is there some other workflow for iOS or is this a bug in the supabase package?
I get the following warning when setting the session with my access token and refresh token (using expo-secure-store):
await supabase.auth.setSession({ access_token: authResponse.params.access_token, refresh_token: authResponse.params.refresh_token })
Warning: "Provided value to SecureStore is larger than 2048 bytes. An attempt to store such a value will throw an error in SDK 35."
Anyone else encountered this?
I get the following warning when setting the session with my access token and refresh token (using expo-secure-store):
await supabase.auth.setSession({ access_token: authResponse.params.access_token, refresh_token: authResponse.params.refresh_token })
Warning: "Provided value to SecureStore is larger than 2048 bytes. An attempt to store such a value will throw an error in SDK 35."
Anyone else encountered this?
This isn't related to Supabase - instead, it's related to Expo securestore (https://docs.expo.dev/versions/latest/sdk/securestore/). It's not something to worry about unless you're also storing significantly more pieces of data in securestore (hint: You shouldn't be - anything non-sensitive should be in AsyncStorage, and only essential private information should be in SecureStore).
You may be able to use MMKV (https://github.com/mrousavy/react-native-mmkv) with an encrypted store to achieve the same without the warning - though you'd need to create a mapping between the asynstorage / localstorage spec, and MMKV functions.
Many thanks @ChronSyn. I wasn't sure if maybe supabase was storing more than it should be.
I won't be storing anything besides the session info in secure-store. Reassuring to hear this won't be a problem. However, expo-secure-store says that it will throw an error in future SDK's, so I'm a little worried about this.
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.