@@ -4,11 +4,24 @@ import { CredentialResponse } from 'google-one-tap'
44import { useRouter } from 'next/router'
55import { useEffect , useCallback , useRef } from 'react'
66
7+ // generate nonce to use for google id token sign-in
8+ const generateNonce = async ( ) : Promise < string [ ] > => {
9+ const nonce = btoa ( String . fromCharCode ( ...crypto . getRandomValues ( new Uint8Array ( 32 ) ) ) )
10+ const encoder = new TextEncoder ( )
11+ const encodedNonce = encoder . encode ( nonce )
12+ const hashBuffer = await crypto . subtle . digest ( 'SHA-256' , encodedNonce )
13+ const hashArray = Array . from ( new Uint8Array ( hashBuffer ) )
14+ const hashedNonce = hashArray . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' )
15+
16+ return [ nonce , hashedNonce ]
17+ }
18+
719const OneTapComponent = ( ) => {
820 const supabase = createClient ( )
921 const router = useRouter ( )
1022 const initializationRef = useRef ( false )
1123 const abortControllerRef = useRef < AbortController | null > ( null )
24+ const nonceRef = useRef < string | null > ( null )
1225
1326 const handleCredentialResponse = useCallback (
1427 async ( response : CredentialResponse ) => {
@@ -18,27 +31,28 @@ const OneTapComponent = () => {
1831 return
1932 }
2033
21- const { data, error } = await supabase . auth
22- . signInWithIdToken ( {
23- provider : 'google' ,
24- token : response . credential
25- } )
26- . catch ( ( err ) => {
27- throw new Error ( `Authentication failed: ${ err . message } ` )
28- } )
29-
30- if ( error ) {
31- console . error ( 'Supabase auth error:' , error . message )
32- throw error
34+ if ( ! nonceRef . current ) {
35+ console . error ( 'Nonce not available' )
36+ return
3337 }
3438
35- console . info ( 'Successfully logged in with Google One Tap' )
36- await router . reload ( )
39+ // send id token returned in response.credential to supabase
40+ const { data, error } = await supabase . auth . signInWithIdToken ( {
41+ provider : 'google' ,
42+ token : response . credential ,
43+ nonce : nonceRef . current
44+ } )
45+
46+ if ( error ) throw error
47+ console . log ( 'Session data:' , data )
48+ console . log ( 'Successfully logged in with Google One Tap' )
49+ // TODO: many states depend on profile auth, so silently updating the profile isn't enough. we need a better solution or a different call flow later.
50+ router . reload ( )
3751 } catch ( error ) {
3852 console . error ( 'Error logging in with Google One Tap:' , error )
3953 }
4054 } ,
41- [ supabase . auth , router ]
55+ [ supabase . auth ]
4256 )
4357
4458 const initializeGoogleOneTap = useCallback ( async ( ) => {
@@ -67,6 +81,10 @@ const OneTapComponent = () => {
6781 return
6882 }
6983
84+ // Generate nonce
85+ const [ nonce , hashedNonce ] = await generateNonce ( )
86+ nonceRef . current = nonce
87+
7088 // Wait for Google script with retry mechanism
7189 let attempts = 0
7290 const maxAttempts = 20 // 4 seconds max wait
@@ -96,6 +114,7 @@ const OneTapComponent = () => {
96114 window . google . accounts . id . initialize ( {
97115 client_id : process . env . NEXT_PUBLIC_GOOGLE_CLIENT_ID ,
98116 callback : handleCredentialResponse ,
117+ nonce : hashedNonce ,
99118 auto_select : false ,
100119 cancel_on_tap_outside : true ,
101120 use_fedcm_for_prompt : true
0 commit comments