diff --git a/README.md b/README.md
index 3b62ac6..f061d84 100644
--- a/README.md
+++ b/README.md
@@ -129,7 +129,7 @@ useGoogleOneTapLogin({
});
```
-### Custom login button (implicit & authorization code flow)
+### Custom login button (implicit, authorization code & credential flow)
#### Implicit flow
@@ -140,7 +140,9 @@ const login = useGoogleLogin({
onSuccess: tokenResponse => console.log(tokenResponse),
});
- login()}>Sign in with Google 🚀;
+ login()}>
+ Sign in with Google 🚀{' '}
+;
```
#### Authorization code flow
@@ -155,7 +157,26 @@ const login = useGoogleLogin({
flow: 'auth-code',
});
- login()}>Sign in with Google 🚀;
+ login()}>
+ Sign in with Google 🚀{' '}
+;
+```
+
+#### Credential flow
+
+This will return a JWT token in `tokenResponse.credential`.
+
+```jsx
+import { useGoogleLogin } from '@react-oauth/google';
+
+const login = useGoogleLogin({
+ onSuccess: credentialsResponse => console.log(tokenResponse),
+ flow: 'credential',
+});
+
+ login()}>
+ Sign in with Google 🚀{' '}
+;
```
#### Checks if the user granted all the specified scope or scopes
@@ -182,8 +203,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
);
```
-#### [Content Security Policy (if needed)](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy)
-
## API
### GoogleOAuthProvider
@@ -191,7 +210,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| Required | Prop | Type | Description |
| :------: | ------------------- | ---------- | --------------------------------------------------------------------------- |
| ✓ | clientId | `string` | [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) |
-| | nonce | `string` | Nonce applied to GSI script tag. Propagates to GSI's inline style tag |
| | onScriptLoadSuccess | `function` | Callback fires on load gsi script success |
| | onScriptLoadError | `function` | Callback fires on load gsi script failure |
@@ -225,7 +243,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| | intermediate_iframe_close_callback | `function` | Overrides the default intermediate iframe behavior when users manually close One Tap |
| | itp_support | `boolean` | Enables upgraded One Tap UX on ITP browsers |
| | hosted_domain | `string` | If your application knows the Workspace domain the user belongs to, use this to provide a hint to Google. For more information, see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs |
-| | use_fedcm_for_prompt | `boolean` | Allow the browser to control user sign-in prompts and mediate the sign-in flow between your website and Google. |
### useGoogleLogin (Both implicit & authorization code flow)
@@ -265,5 +282,3 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description |
| | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt |
| | hosted_domain | `string` | If your application knows the Workspace domain the user belongs to, use this to provide a hint to Google. For more information, see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs |
-| | disabled | `boolean` | Controls whether to cancel the popup in cases such as when the user is already logged in |
-| | use_fedcm_for_prompt | `boolean` | Allow the browser to control user sign-in prompts and mediate the sign-in flow between your website and Google. |
diff --git a/packages/@react-oauth/google/README.md b/packages/@react-oauth/google/README.md
index 3b62ac6..3f6db0f 100644
--- a/packages/@react-oauth/google/README.md
+++ b/packages/@react-oauth/google/README.md
@@ -12,7 +12,7 @@ $ npm install @react-oauth/google@latest
$ yarn add @react-oauth/google@latest
```
-## Demo & How to use to fetch user details
+## Demo
https://react-oauth.vercel.app/
@@ -129,7 +129,7 @@ useGoogleOneTapLogin({
});
```
-### Custom login button (implicit & authorization code flow)
+### Custom login button (implicit, authorization code & credential flow)
#### Implicit flow
@@ -140,7 +140,9 @@ const login = useGoogleLogin({
onSuccess: tokenResponse => console.log(tokenResponse),
});
- login()}>Sign in with Google 🚀;
+ login()}>
+ Sign in with Google 🚀{' '}
+;
```
#### Authorization code flow
@@ -155,7 +157,26 @@ const login = useGoogleLogin({
flow: 'auth-code',
});
- login()}>Sign in with Google 🚀;
+ login()}>
+ Sign in with Google 🚀{' '}
+;
+```
+
+#### Credential flow
+
+This will return a JWT token in `tokenResponse.credential`.
+
+```jsx
+import { useGoogleLogin } from '@react-oauth/google';
+
+const login = useGoogleLogin({
+ onSuccess: credentialsResponse => console.log(tokenResponse),
+ flow: 'credential',
+});
+
+ login()}>
+ Sign in with Google 🚀{' '}
+;
```
#### Checks if the user granted all the specified scope or scopes
@@ -182,8 +203,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
);
```
-#### [Content Security Policy (if needed)](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy)
-
## API
### GoogleOAuthProvider
@@ -191,7 +210,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| Required | Prop | Type | Description |
| :------: | ------------------- | ---------- | --------------------------------------------------------------------------- |
| ✓ | clientId | `string` | [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) |
-| | nonce | `string` | Nonce applied to GSI script tag. Propagates to GSI's inline style tag |
| | onScriptLoadSuccess | `function` | Callback fires on load gsi script success |
| | onScriptLoadError | `function` | Callback fires on load gsi script failure |
@@ -225,7 +243,6 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| | intermediate_iframe_close_callback | `function` | Overrides the default intermediate iframe behavior when users manually close One Tap |
| | itp_support | `boolean` | Enables upgraded One Tap UX on ITP browsers |
| | hosted_domain | `string` | If your application knows the Workspace domain the user belongs to, use this to provide a hint to Google. For more information, see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs |
-| | use_fedcm_for_prompt | `boolean` | Allow the browser to control user sign-in prompts and mediate the sign-in flow between your website and Google. |
### useGoogleLogin (Both implicit & authorization code flow)
@@ -265,5 +282,3 @@ const hasAccess = hasGrantedAnyScopeGoogle(
| | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description |
| | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt |
| | hosted_domain | `string` | If your application knows the Workspace domain the user belongs to, use this to provide a hint to Google. For more information, see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs |
-| | disabled | `boolean` | Controls whether to cancel the popup in cases such as when the user is already logged in |
-| | use_fedcm_for_prompt | `boolean` | Allow the browser to control user sign-in prompts and mediate the sign-in flow between your website and Google. |
diff --git a/packages/@react-oauth/google/src/hooks/useGoogleLogin.ts b/packages/@react-oauth/google/src/hooks/useGoogleLogin.ts
index 0a6cdf1..61d7ced 100644
--- a/packages/@react-oauth/google/src/hooks/useGoogleLogin.ts
+++ b/packages/@react-oauth/google/src/hooks/useGoogleLogin.ts
@@ -1,16 +1,22 @@
-/* eslint-disable import/export */
import { useCallback, useEffect, useRef } from 'react';
-
import { useGoogleOAuth } from '../GoogleOAuthProvider';
-import {
- TokenClientConfig,
- TokenResponse,
+import type {
CodeClientConfig,
CodeResponse,
- OverridableTokenClientConfig,
+ CredentialResponse,
+ GoogleCredentialResponse,
+ IdConfiguration,
+ MomentListener,
NonOAuthError,
+ OverridableTokenClientConfig,
+ TokenClientConfig,
+ TokenResponse,
} from '../types';
+import { extractClientId } from '../utils';
+type ImplicitOnError = (
+ onError: Pick,
+) => void;
interface ImplicitFlowOptions
extends Omit {
onSuccess?: (
@@ -19,17 +25,15 @@ interface ImplicitFlowOptions
'error' | 'error_description' | 'error_uri'
>,
) => void;
- onError?: (
- errorResponse: Pick<
- TokenResponse,
- 'error' | 'error_description' | 'error_uri'
- >,
- ) => void;
+ onError?: ImplicitOnError;
onNonOAuthError?: (nonOAuthError: NonOAuthError) => void;
scope?: TokenClientConfig['scope'];
overrideScope?: boolean;
}
+type AuthCodeOnError = (
+ onError: Pick,
+) => void;
interface AuthCodeFlowOptions
extends Omit {
onSuccess?: (
@@ -38,17 +42,27 @@ interface AuthCodeFlowOptions
'error' | 'error_description' | 'error_uri'
>,
) => void;
- onError?: (
- errorResponse: Pick<
- CodeResponse,
- 'error' | 'error_description' | 'error_uri'
- >,
- ) => void;
+ onError?: AuthCodeOnError;
onNonOAuthError?: (nonOAuthError: NonOAuthError) => void;
scope?: CodeResponse['scope'];
overrideScope?: boolean;
}
+type CredentialOnSuccess = (
+ credentialResponse: Omit<
+ CredentialResponse,
+ 'error' | 'error_description' | 'error_uri'
+ >,
+) => void;
+type CredentialOnError = () => void;
+interface CredentialFlowOptions
+ extends Omit {
+ onSuccess?: CredentialOnSuccess;
+ onError?: CredentialOnError;
+ state?: never;
+ promptMomentNotification?: MomentListener;
+}
+
export type UseGoogleLoginOptionsImplicitFlow = {
flow?: 'implicit';
} & ImplicitFlowOptions;
@@ -57,9 +71,14 @@ export type UseGoogleLoginOptionsAuthCodeFlow = {
flow?: 'auth-code';
} & AuthCodeFlowOptions;
+export type UseGoogleLoginOptionsCredentialFlow = {
+ flow?: 'credential';
+} & CredentialFlowOptions;
+
export type UseGoogleLoginOptions =
| UseGoogleLoginOptionsImplicitFlow
- | UseGoogleLoginOptionsAuthCodeFlow;
+ | UseGoogleLoginOptionsAuthCodeFlow
+ | UseGoogleLoginOptionsCredentialFlow;
export default function useGoogleLogin(
options: UseGoogleLoginOptionsImplicitFlow,
@@ -67,17 +86,29 @@ export default function useGoogleLogin(
export default function useGoogleLogin(
options: UseGoogleLoginOptionsAuthCodeFlow,
): () => void;
+export default function useGoogleLogin(
+ options: UseGoogleLoginOptionsCredentialFlow,
+): () => void;
export default function useGoogleLogin({
flow = 'implicit',
- scope = '',
onSuccess,
onError,
- onNonOAuthError,
- overrideScope,
state,
...props
}: UseGoogleLoginOptions): unknown {
+ const {
+ scope = '',
+ onNonOAuthError,
+ overrideScope,
+ ...implicitOrAuthProps
+ } = props as
+ | UseGoogleLoginOptionsImplicitFlow
+ | UseGoogleLoginOptionsAuthCodeFlow;
+
+ const { promptMomentNotification, ...credentialsProps } =
+ props as UseGoogleLoginOptionsCredentialFlow;
+
const { clientId, scriptLoadedSuccessfully } = useGoogleOAuth();
const clientRef = useRef();
@@ -90,30 +121,63 @@ export default function useGoogleLogin({
const onNonOAuthErrorRef = useRef(onNonOAuthError);
onNonOAuthErrorRef.current = onNonOAuthError;
+ const promptMomentNotificationRef = useRef(promptMomentNotification);
+ promptMomentNotificationRef.current = promptMomentNotification;
+
useEffect(() => {
if (!scriptLoadedSuccessfully) return;
- const clientMethod =
- flow === 'implicit' ? 'initTokenClient' : 'initCodeClient';
-
- const client = window?.google?.accounts?.oauth2[clientMethod]({
- client_id: clientId,
- scope: overrideScope ? scope : `openid profile email ${scope}`,
- callback: (response: TokenResponse | CodeResponse) => {
- if (response.error) return onErrorRef.current?.(response);
-
- onSuccessRef.current?.(response as any);
- },
- error_callback: (nonOAuthError: NonOAuthError) => {
- onNonOAuthErrorRef.current?.(nonOAuthError);
- },
- state,
- ...props,
- });
-
- clientRef.current = client;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [clientId, scriptLoadedSuccessfully, flow, scope, state]);
+ if (flow !== 'credential') {
+ const clientMethod =
+ flow === 'implicit' ? 'initTokenClient' : 'initCodeClient';
+
+ clientRef.current = window?.google?.accounts.oauth2[clientMethod]({
+ client_id: clientId,
+ scope: overrideScope ? scope : `openid profile email ${scope}`,
+ callback: (response: TokenResponse | CodeResponse) => {
+ if (response.error)
+ return (onErrorRef.current as ImplicitOnError | AuthCodeOnError)?.(
+ response,
+ );
+
+ onSuccessRef.current?.(response as any);
+ },
+ error_callback: (nonOAuthError: NonOAuthError) => {
+ onNonOAuthErrorRef.current?.(nonOAuthError);
+ },
+ state,
+ ...implicitOrAuthProps,
+ });
+ } else {
+ window?.google?.accounts?.id?.initialize({
+ client_id: clientId,
+ callback: (credentialResponse: GoogleCredentialResponse) => {
+ if (!credentialResponse?.credential) {
+ return (onErrorRef.current as CredentialOnError)?.();
+ }
+
+ const { credential, select_by } = credentialResponse;
+ (onSuccessRef.current as CredentialOnSuccess)?.({
+ credential,
+ clientId: extractClientId(credentialResponse),
+ select_by,
+ });
+ },
+ ...credentialsProps,
+ });
+
+ clientRef.current = window?.google?.accounts?.id;
+ }
+ }, [
+ clientId,
+ credentialsProps,
+ flow,
+ implicitOrAuthProps,
+ overrideScope,
+ scope,
+ scriptLoadedSuccessfully,
+ state,
+ ]);
const loginImplicitFlow = useCallback(
(overrideConfig?: OverridableTokenClientConfig) =>
@@ -126,5 +190,17 @@ export default function useGoogleLogin({
[],
);
- return flow === 'implicit' ? loginImplicitFlow : loginAuthCodeFlow;
+ const loginCredentialFlow = useCallback(
+ () => clientRef.current?.prompt(promptMomentNotificationRef.current),
+ [],
+ );
+
+ switch (flow) {
+ case 'implicit':
+ return loginImplicitFlow;
+ case 'auth-code':
+ return loginAuthCodeFlow;
+ case 'credential':
+ return loginCredentialFlow;
+ }
}