fixed oauth docs and or redirect (#123)

This commit is contained in:
Zai Shi 2024-07-01 16:17:11 -07:00 committed by GitHub
parent 10969c96eb
commit cbf409ab25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 29 deletions

View File

@ -7,7 +7,13 @@ With Stack, users can connect multiple OAuth accounts, enabling access to provid
## Connecting with an OAuth Provider Post Sign-in
Users can connect with an OAuth provider during sign-in or after signing in using other methods. For the latter, use the `user.useConnectedAccount('<provider_name>', { or: 'redirect' })` method. If the account is already connected, the method returns a Connection object; otherwise, it redirects the user to the provider's authorization page. Heres how to connect with Google:
Users can connect with an OAuth provider during sign-in or after signing in using other methods. For the latter, use the `user.useConnectedAccount('<provider_name>', { or: 'redirect' })` method. If the account is already connected, the method returns a Connection object; otherwise, it redirects the user to the provider's authorization page.
<Note>
In order to connect accounts after sign-in, you need to set up your own OAuth client ID and client secret with the provider. See more details in the section [Going to Production](../getting-started/production#oauth-providers)
</Note>
Here's how to connect with Google:
```jsx
'use client';
@ -25,7 +31,7 @@ export default function Page() {
## Connecting with Extra Scopes
You can request extra scopes when connecting with an OAuth provider. For instance, to access Google Drive, pass the `https://www.googleapis.com/auth/drive` scope. Heres an example:
You can request extra scopes when connecting with an OAuth provider. For instance, to access Google Drive, pass the `https://www.googleapis.com/auth/drive.readonly` scope. Here's an example:
```jsx
'use client';
@ -35,7 +41,7 @@ import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
// Redirects to the Google authorization page, requesting access to Google Drive
const account = user.useConnectedAccount('google', { or: 'redirect' });
const account = user.useConnectedAccount('google', { or: 'redirect', scopes: ['https://www.googleapis.com/authdrive.readonly'] });
// Account is always defined because of the redirect
return <div>Google Drive connected</div>;
}
@ -43,7 +49,7 @@ export default function Page() {
## Retrieving the Access Token
Once connected with an OAuth provider, obtain the access token using the `account.getAccessToken()` method. Use this token to access the providers API endpoints. Heres an example of using the access token to interact with Google Drive API:
Once connected with an OAuth provider, obtain the access token using the `account.getAccessToken()` method. Use this token to access the provider's API endpoints. Here's an example of using the access token to interact with Google Drive API:
```jsx
'use client';
@ -53,18 +59,17 @@ import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
const account = user.useConnectedAccount('google', { or: 'redirect' });
const tokens = account.getAccessToken();
const [response, setResponse] = useState(null);
const account = user.useConnectedAccount('google', { or: 'redirect', scopes: ['https://www.googleapis.com/auth/drive.readonly'] });
const tokens = account.useAccessToken();
const [response, setResponse] = useState<any>();
useEffect(() => {
fetch('https://www.googleapis.com/drive/v3/files', {
headers: {
Authorization: `Bearer ${tokens.access_token}`
}
headers: { Authorization: `Bearer ${tokens.accessToken}` }
})
.then(res => res.json())
.then(data => setResponse(data));
.then((res) => res.json())
.then((data) => setResponse(data))
.catch((err) => console.error(err));
}, [tokens]);
return <div>{response ? JSON.stringify(response) : 'Loading...'}</div>;
@ -83,7 +88,7 @@ Configure the `StackServerApp` with the required scopes:
export const stackServerApp = new StackServerApp({
// your other settings ...
oauthScopesOnSignIn: {
google: ['https://www.googleapis.com/auth/drive']
google: ['https://www.googleapis.com/authdrive.readonly']
}
});
```

View File

@ -0,0 +1,22 @@
'use client';
import { useEffect, useState } from 'react';
import { useUser } from "@stackframe/stack";
export default function Page() {
const user = useUser({ or: 'redirect' });
const account = user.useConnectedAccount('google', { or: 'redirect', scopes: ['https://www.googleapis.com/auth/drive.readonly'] });
const tokens = account.useAccessToken();
const [response, setResponse] = useState<any>();
useEffect(() => {
fetch('https://www.googleapis.com/drive/v3/files', {
headers: { Authorization: `Bearer ${tokens.accessToken}` }
})
.then((res) => res.json())
.then((data) => setResponse(data))
.catch((err) => console.error(err));
}, [tokens]);
return <div>{response ? JSON.stringify(response) : 'Loading...'}</div>;
}

View File

@ -1,11 +0,0 @@
import * as React from "react";
export function useTrigger(callback: () => void) {
const [hasTriggered, setHasTriggered] = React.useState(false);
React.useEffect(() => {
if (hasTriggered) {
callback();
}
}, [hasTriggered]);
return () => setHasTriggered(true);
}

View File

@ -23,7 +23,6 @@ import { scrambleDuringCompileTime } from "@stackframe/stack-shared/dist/utils/c
import { isReactServer } from "@stackframe/stack-sc";
import * as cookie from "cookie";
import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
import { useTrigger } from "@stackframe/stack-shared/dist/hooks/use-trigger";
import { mergeScopeStrings } from "@stackframe/stack-shared/dist/utils/strings";
@ -990,14 +989,13 @@ class _StackClientAppImpl<HasTokenStore extends boolean, ProjectId extends strin
const router = NextNavigation.useRouter();
const session = this._useSession(options?.tokenStore);
const userJson = useAsyncCache(this._currentUserCache, [session], "useUser()");
const triggerRedirectToSignIn = useTrigger(() => router.replace(this.urls.signIn));
if (userJson === null) {
switch (options?.or) {
case 'redirect': {
// Updating the router is not allowed during the component render function, so we do it in a different async tick
// The error would be: "Cannot update a component (`Router`) while rendering a different component."
triggerRedirectToSignIn();
setTimeout(() => router.replace(this.urls.signIn), 0);
suspend();
throw new StackAssertionError("suspend should never return");
}
@ -1535,14 +1533,13 @@ class _StackServerAppImpl<HasTokenStore extends boolean, ProjectId extends strin
const router = NextNavigation.useRouter();
const session = this._getSession(options?.tokenStore);
const userJson = useAsyncCache(this._currentServerUserCache, [session], "useUser()");
const triggerRedirectToSignIn = useTrigger(() => router.replace(this.urls.signIn));
if (userJson === null) {
switch (options?.or) {
case 'redirect': {
// Updating the router is not allowed during the component render function, so we do it in a different async tick
// The error would be: "Cannot update a component (`Router`) while rendering a different component."
triggerRedirectToSignIn();
setTimeout(() => router.replace(this.urls.signIn), 0);
suspend();
throw new StackAssertionError("suspend should never return");
}