auth-js
auth-js copied to clipboard
Add Provider Refresh Token to Supabase Session Data
Feature request
Is your feature request related to a problem? Please describe.
Currently, the data returned from the supabase.auth.session()
method, or the supabase.auth.onAuthStateChange()
callback contain the following properties: access_token
, refresh_token
, provider_token
.
The access_token
and refresh_token
both apply to the Supabase API, so that your application can maintain access to Supabase without repeatedly requesting the user's credentials and permission.
The provider_token
applies to an external auth provider, such as Discord for example, so that you can access the Discord API, but there is no provider_refresh
, so it is impossible to maintain access to an external provider's API without repeatedly requesting the user's credentials and permission.
Most users expect that after they entered their credentials, reviewed the scopes that a third party application is requesting, and granted access to those scopes once, that they should not have to do it repeatedly. Because the provider refresh token is currently being omitted from the Supabase session data, despite being granted by the user and available in the external provider's OAuth2 flow, the experience for the end user is atypical and suboptimal.
Describe the solution you'd like
Most external auth providers that have implemented an OAuth2 flow return a refresh token in the response, such as Discord for example https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-response so this feature could be implemented by simply passing that value along to the Supabase session data. This would make the Supabase session data symmetrical when using an external auth provider:
Supabase Access Token -> access_token
Supabase Refresh Token -> refresh_token
Provider Access Token -> provider_token
Provider Refresh Token -> provider_refresh
(this is the proposed addition)
Describe alternatives you've considered
It would be possible to implement an external OAuth2 flow without using the supabase.auth
module, and obtain the provider's access token and refresh token directly. The downside of this solution is that in bypassing the supabase.auth
module, you would no longer be creating user records in the Supabase auth.users
table. I'm not certain if it would still be possible to implement RLS without the auth.users
table, but it seems unlikely that it would be advisable. For serverless applications, RLS, or a similar non-client means to restrict access to the DB, is generally of high importance.
For anyone running into this, apparently you can bypass the supabase.auth
module entirely and still implement RLS, which is actually not a bad solution https://github.com/supabase/supabase/discussions/1849
Can we get an update if this solution is being proposed or if there is a workaround? @fredguest it does not appear your solution gives you a refresh token
.
What I linked to is a solution for implementing RLS in the Supabase DB without using the supabase.auth
module at all. It's not meant to be a solution for obtaining a provider refresh token from Supabase, it presumes that you have already obtained provider access and refresh tokens on your own.
In other words, I'm using Supabase purely for the Postgres DB, nothing else. I'm using vanilla HTTP requests to obtain access and refresh tokens directly from Discord, and then I can also use vanilla HTTP requests for token rotation when necessary, because I have the Discord refresh token.
If you want to continue using the supabase.auth
module, the solution I linked to will not work for you. You would need the feature I originally proposed in this ticket, but there's been no response so I don't know if they plan to implement it.
Hello! I'm looking for this feature, too. I'm building an application that needs an offline access_type for Google Drive API and with the current implementation, Supabase is not helping me as I expected.
Looking for news 😇
@fredguest thanks for reporting in detail the issue. I'm creating an app using the new Spotify provider and facing the same obstacles. Auth integration experience has been great so far! Alas, having to keep signing the user in periodically is a deal breaker.
I noticed a related issue that may also contain an alternative solution. When supabase.auth.onAuthStateChange()
calls the /token?grant_type=refresh_token
endpoint, the returned payload does not contain provider_token
.
This means you cannot simply override the local session object entirely when getting the results from the refresh endpoint. Instead, the app will have to maintain a separate copy of the provider_token
elsewhere. Or perform a new signIn
to get a new provider_token
, which will reload the page.
If /token?grant_type=refresh_token
returned a provider_token
in the response, it would be possible to always have a "fresh" token. We wouldn't need a provider_refresh
token at all if the JWT Expiry authentication setting is lower than the expiration time of the provider.
If this is possible and makes sense it will probably be something that needs to be updated on the server because the full response is already set by this function:
/**
* Generates a new JWT.
* @param refreshToken A valid refresh token that was returned on login.
*/
async refreshAccessToken(
refreshToken: string
): Promise<{ data: Session | null; error: ApiError | null }> {
try {
const data: any = await post(
this.fetch,
`${this.url}/token?grant_type=refresh_token`,
{ refresh_token: refreshToken },
{ headers: this.headers }
)
const session = { ...data }
if (session.expires_in) session.expires_at = expiresAt(data.expires_in)
return { data: session, error: null }
} catch (e) {
return { data: null, error: e as ApiError }
}
}
Maybe @awalias has some feedback on the ideas outlined in this issue?
Same thing. I'm looking for this feature. Thats a deal breaker for me too.
I haven't checked this lately. Does anyone know if any updates have sorted out this issue?
Also wondering if this has been fixed recently.
Same here. I have been looking for a good workaround for react native. BTW, it is the only feature holding back from moving my app to production.
I would appreciate this too. 2 birds 1 stone. I have an app that uses supaclient.auth.signIn provider = Google. Then I use Google oauth client to get a refresh token with scopes for google services, so the app can do google api things for the user whenever. = 2x sign in UX (when initially using the app).
Same issue here. provider_token disappears after the first refreshSession() call and makes it impossible to consume OAuth provider apis which are crucial in my app.
Hey, we've started returning the provider_refresh_token
in the session now thanks to @msonnberger's PR #451. We'll also be adding it to the rc
branch soon for those trying out supabase-js/v2
. Will close this issue for now! Huge thanks to everyone for the patience and feedback!
@kangmingtay, is there a way to provide queryParams to google oauth provider though?
Trying to pass options.queryParams.access_type = "offline"
to the provider w/ SignInWithOAuthCredentials
, but it doesn't seem to get passed through to the provider on supabase-js/v2
. Hence there's no way to prompt the OAuth provider to return the refresh_token in the first place and provider_refresh_token
is empty + provider_token
disappears from the session after the first refresh.
@kangmingtay, is there a way to provide queryParams to google oauth provider though? Trying to pass
options.queryParams.access_type = "offline"
to the provider w/SignInWithOAuthCredentials
, but it doesn't seem to get passed through to the provider onsupabase-js/v2
. Hence there's no way to prompt the OAuth provider to return the refresh_token in the first place andprovider_refresh_token
is empty +provider_token
disappears from the session after the first refresh.
I observe the same behavior. However, Github seems to automatically send the provider_refresh_token
Thanks for raising this up @lauri865 and @younesbenallal! currently, you can't pass query params to most providers because gotrue doesn't support that. However, since some oauth providers only return the provider refresh token if an explicit query param is passed, we've added support for passing any query params here (https://github.com/supabase/gotrue/pull/757)
It'll take some time before this is available to all Supabase projects but do keep a lookout in the next week or so!
Hi @kangmingtay thanks for fixing this! I just started a new Supabase project where I need this behavior options.queryParams.access_type = "offline"
to get a refresh token from Google.
When can I expect https://github.com/supabase/gotrue/pull/757 to be available? I'm on the latest JS library. For the context, the API call looks like
const { data, error } = await supabaseClient.auth.signInWithOAuth({
provider: 'google',
options: {
queryParams: { access_type: 'offline' },
scopes:
'https://www.googleapis.com/auth/gmail.modify'
}
});
hey @devstein, probably this week
@kangmingtay Amazing! What's the best way to follow so I know when it's available?
@devstein you can watch the new releases by going on the repo and watching custom events :
@younesbenallal Thanks. I was referring to when the version of GoTrue is released to my Supabase instance.
For others that stumble upon this thread, I found the root cause is because supabase start
is running GoTrue v2.15.3
, but the fix is only in version GoTrue v2.19.1
Created a PR: https://github.com/supabase/cli/pull/587
@kangmingtay, is there a way to provide queryParams to google oauth provider though? Trying to pass
options.queryParams.access_type = "offline"
to the provider w/SignInWithOAuthCredentials
, but it doesn't seem to get passed through to the provider onsupabase-js/v2
. Hence there's no way to prompt the OAuth provider to return the refresh_token in the first place andprovider_refresh_token
is empty +provider_token
disappears from the session after the first refresh.
It does the samething for me too, I have been getting the provider_token
as well as provider_refresh_token
from the session object but after sometime, both gets null
as I need it for the Google APIs to work.
Is there any workaround this? @kangmingtay
hey @Pratikkapadia7, based on the google oauth docs
If you are not using a client library, you need to set the access_type HTTP query parameter to offline when redirecting the user to Google's OAuth 2.0 server. In that case, Google's authorization server returns a refresh token when you exchange an authorization code for an access token. Then, if the access token expires (or at any other time), you can use a refresh token to obtain a new access token.
it seems like you need to manage refreshing the provider_token
on your own by using the provider_refresh_token
returned initially.
hey @Pratikkapadia7, based on the google oauth docs
If you are not using a client library, you need to set the access_type HTTP query parameter to offline when redirecting the user to Google's OAuth 2.0 server. In that case, Google's authorization server returns a refresh token when you exchange an authorization code for an access token. Then, if the access token expires (or at any other time), you can use a refresh token to obtain a new access token.
it seems like you need to manage refreshing the
provider_token
on your own by using theprovider_refresh_token
returned initially.
Thanks for the further information @kangmingtay . But the thing I am facing is initially I am only receiving provider_token
and the provider_refresh_token
is null
, so is it something Supabase handles or should I look into the google docs for that too?
Hey @Pratikkapadia7, seems like you also need to pass the query param prompt: consent
in order for google to return the provider_refresh_token
. Based on this stackoverflow thread,
The refresh_token is only provided on the first authorization from the user. Subsequent authorizations, such as the kind you make while testing an OAuth2 integration, will not return the refresh_token again.
You either need to remove the authorized app from your google account or add the query param prompt: consent
as well.
cc @J0
Hi! If anyone needs it, I kinda solved the problem of using Supabase Google Auth offline.
Here are the steps:
- I prompt signInWithOAuth() with additional queryParams for access_type and prompt:
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
scopes: 'https://www.googleapis.com/auth/drive.file',
queryParams: {
access_type: 'offline',
prompt: 'consent', //<- Optional. The refresh-token gets returned
//only immediately after consent. It will not be
//re-issued on sessionRefresh or Login.
//Therefore, for test purposes I
//"force" consent every time.
},
},
})
- After login, I fetch the provider_refresh_token from existing session:
const { data: session, error } = await supabase.auth.getSession()
if (error) throw error
)
return session.session.provider_refresh_token
}```
3. And then I save it to my database:
```const { data, error } = await supabase
.from('gdrive')
.insert({
user_id: session.user.id,
provider_token: provider_token,
provider_refresh_token: provider_refresh_token,
})
.select()
.single()```
4. So once I have the refresh token saved for each user, I can edit the Google's "GetClient" util:
/utlis/drive.js:
```const { google } = require('googleapis')
const getClient = (accessToken) => {
const oAuth2Client = new google.auth.OAuth2()
// oAuth2Client.setCredentials({ access_token: authToken })
oAuth2Client.setCredentials({ access_token: accessToken })
return google.drive({
version: 'v3',
auth: oAuth2Client,
})
}
const getRefreshClient = (refreshToken) => {
const oAuth2Client = new google.auth.OAuth2(
process.env.GOOGLE_ID,
process.env.GOOGLE_CLIENT_SECRET
)
// oAuth2Client.setCredentials({ access_token: authToken })
oAuth2Client.setCredentials({ refresh_token: refreshToken })
return google.drive({
version: 'v3',
auth: oAuth2Client,
})
}
module.exports = {
getClient,
getRefreshClient,
}
- So, in places where the active provider token is available for me, I use "getClient" with normal access token. However, for offline use, I call "getRefreshClient" module with credentials and refresh token. It's enough to provide only refresh token, Google ID and Google Secret. Google automatically will change it to provider token and authenticate the client.
I'm not sure whether that's the most optimal way to do it, but it works for now so I'm good with it.
When user is authenticated with 'github' provider, provider_refresh_token
is null. Is this a bug?
(provider_token
is set to a correct, valid token).
@Warchant it is not a bug, github oauth API doesn't return a refresh token (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow)
@kangmingtay thanks for the reply. For future readers I've composed a small post with an instruction to solve provider_refresh_token is null
problem.
https://warchantua.hashnode.dev/supabase-and-github-app-authentication-done-right