-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Google Signin #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
IronTony
wants to merge
6
commits into
main
Choose a base branch
from
feat/google-signin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,911
−2,075
Open
feat: Google Signin #211
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
179c360
feat: add Google Sign-In module for React Auth
IronTony b053188
feat: enhance Google Sign-In module with additional configuration opt…
IronTony 905818a
feat: update Google Sign-In package with new dependencies and documen…
IronTony e45dd58
fix: improve error handling in Google Sign-In module for iOS
IronTony c558ee0
feat: enhance Google Sign-In module with new web and native features
IronTony 889eed2
feat: add customizable label to GoogleSignInButton component
IronTony File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,358 @@ | ||
| # React Auth - Google Sign-In | ||
|
|
||
| > Google Sign-In adapter for [@forward-software/react-auth](https://github.com/forwardsoftware/react-auth) - Web and React Native | ||
|
|
||
| Self-contained Google Sign-In integration with no external auth wrapper dependencies. Provides a ready-made `AuthClient` implementation and a drop-in `GoogleSignInButton` for both platforms. | ||
|
|
||
| --- | ||
|
|
||
| ## Install | ||
|
|
||
| ```sh | ||
| npm install @forward-software/react-auth @forward-software/react-auth-google | ||
| ``` | ||
|
|
||
| ### Platform requirements | ||
|
|
||
| **Web** - No additional dependencies. The package loads the Google Identity Services (GSI) script automatically (with a 10-second timeout). | ||
|
|
||
| **React Native / Expo** - Requires a development build (not compatible with Expo Go): | ||
|
|
||
| - iOS: Add the `GoogleSignIn` CocoaPod (included automatically via autolinking) | ||
| - Android: Uses Android Credential Manager with Google Identity (included via `build.gradle`) | ||
| - Run `npx expo prebuild` or use EAS Build to compile native code | ||
|
|
||
| --- | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Create the auth client | ||
|
|
||
| The setup is identical on both platforms - the bundler automatically resolves the correct implementation. | ||
|
|
||
| ```ts | ||
| // auth.ts | ||
| import { createAuth } from '@forward-software/react-auth'; | ||
| import { GoogleAuthClient } from '@forward-software/react-auth-google'; | ||
|
|
||
| const googleAuth = new GoogleAuthClient({ | ||
| clientId: 'YOUR_GOOGLE_CLIENT_ID', | ||
| }); | ||
|
|
||
| export const { AuthProvider, authClient, useAuthClient } = createAuth(googleAuth); | ||
| ``` | ||
|
|
||
| ### 2. Wrap your app with AuthProvider | ||
|
|
||
| ```tsx | ||
| // App.tsx | ||
| import { AuthProvider } from './auth'; | ||
|
|
||
| function App() { | ||
| return ( | ||
| <AuthProvider> | ||
| <YourApp /> | ||
| </AuthProvider> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### 3. Add the sign-in button | ||
|
|
||
| ```tsx | ||
| import { GoogleSignInButton } from '@forward-software/react-auth-google'; | ||
| import { useAuthClient } from './auth'; | ||
|
|
||
| function LoginScreen() { | ||
| const authClient = useAuthClient(); | ||
|
|
||
| return ( | ||
| <GoogleSignInButton | ||
| config={{ clientId: 'YOUR_GOOGLE_CLIENT_ID' }} | ||
| onCredential={(credentials) => authClient.login(credentials)} | ||
| onError={(err) => console.error(err)} | ||
| /> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| That's it - the button handles the full Google Sign-In flow and the auth client manages tokens, persistence, and state. | ||
|
|
||
| --- | ||
|
|
||
| ## Web Setup | ||
|
|
||
| ### Configuration | ||
|
|
||
| ```ts | ||
| import { GoogleAuthClient } from '@forward-software/react-auth-google'; | ||
|
|
||
| const googleAuth = new GoogleAuthClient({ | ||
| clientId: 'YOUR_GOOGLE_CLIENT_ID', | ||
|
|
||
| // Optional | ||
| scopes: ['openid', 'profile', 'email'], // default | ||
| persistTokens: true, // default - stores tokens in localStorage | ||
| storageKey: '@react-auth/google-tokens', // default | ||
| ux_mode: 'popup', // 'popup' | 'redirect' | ||
| redirect_uri: undefined, // required if ux_mode is 'redirect' | ||
| hosted_domain: undefined, // restrict to a G Suite domain | ||
| nonce: undefined, // binds the ID token to a session (replay attack prevention) | ||
| }); | ||
| ``` | ||
|
|
||
| ### Custom storage | ||
|
|
||
| By default, the web adapter uses `localStorage`. You can provide a custom storage: | ||
|
|
||
| ```ts | ||
| const googleAuth = new GoogleAuthClient({ | ||
| clientId: 'YOUR_GOOGLE_CLIENT_ID', | ||
| storage: { | ||
| getItem: (key) => sessionStorage.getItem(key), | ||
| setItem: (key, value) => sessionStorage.setItem(key, value), | ||
| removeItem: (key) => sessionStorage.removeItem(key), | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ### GoogleSignInButton (Web) | ||
|
|
||
| Renders Google's official branded sign-in button via the GSI script. | ||
|
|
||
| ```tsx | ||
| <GoogleSignInButton | ||
| config={{ clientId: 'YOUR_GOOGLE_CLIENT_ID' }} | ||
| onCredential={(credentials) => authClient.login(credentials)} | ||
| onError={(err) => console.error(err)} | ||
|
|
||
| // Optional - Google button customization | ||
| theme="outline" // 'outline' | 'filled_blue' | 'filled_black' | ||
| size="large" // 'large' | 'medium' | 'small' | ||
| text="signin_with" // 'signin_with' | 'signup_with' | 'continue_with' | 'signin' | ||
| shape="rectangular" // 'rectangular' | 'pill' | 'circle' | 'square' | ||
| width={300} | ||
| /> | ||
| ``` | ||
|
|
||
| ### Manual integration (without GoogleSignInButton) | ||
|
|
||
| If you prefer full control over the UI, use the GSI utilities directly: | ||
|
|
||
| ```tsx | ||
| import { useEffect, useRef } from 'react'; | ||
| import { loadGsiScript, initializeGsi, renderGsiButton } from '@forward-software/react-auth-google/web/gsi'; | ||
| import { useAuthClient } from './auth'; | ||
|
|
||
| function CustomLogin() { | ||
| const authClient = useAuthClient(); | ||
| const buttonRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| async function setup() { | ||
| await loadGsiScript(); | ||
| initializeGsi({ | ||
| client_id: 'YOUR_GOOGLE_CLIENT_ID', | ||
| callback: (response) => { | ||
| authClient.login({ idToken: response.credential }); | ||
| }, | ||
| }); | ||
| if (buttonRef.current) { | ||
| renderGsiButton(buttonRef.current, { theme: 'outline', size: 'large' }); | ||
| } | ||
| } | ||
| setup(); | ||
| }, []); | ||
|
|
||
| return <div ref={buttonRef} />; | ||
| } | ||
| ``` | ||
|
|
||
| ### Token refresh on web | ||
|
|
||
| Google Identity Services on the web does **not** provide refresh tokens. When the ID token expires, the user must sign in again. The adapter handles this automatically - `onInit()` returns `null` when stored tokens are expired, which transitions the auth state to unauthenticated. | ||
|
|
||
| --- | ||
|
|
||
| ## React Native / Expo Setup | ||
|
|
||
| ### Configuration | ||
|
|
||
| ```ts | ||
| import { GoogleAuthClient } from '@forward-software/react-auth-google'; | ||
| import { MMKV } from 'react-native-mmkv'; | ||
|
|
||
| const mmkv = new MMKV(); | ||
|
|
||
| const googleAuth = new GoogleAuthClient({ | ||
| clientId: 'YOUR_GOOGLE_CLIENT_ID', | ||
| webClientId: 'YOUR_WEB_CLIENT_ID', // required for ID token retrieval on Android | ||
| iosClientId: 'YOUR_IOS_CLIENT_ID', // iOS-specific client ID (if different) | ||
|
|
||
| // Required on React Native - no default storage | ||
| storage: { | ||
| getItem: (key) => mmkv.getString(key) ?? null, | ||
| setItem: (key, value) => mmkv.set(key, value), | ||
| removeItem: (key) => mmkv.delete(key), | ||
| }, | ||
|
|
||
| // Optional | ||
| scopes: ['openid', 'profile', 'email'], | ||
| persistTokens: true, | ||
| storageKey: '@react-auth/google-tokens', | ||
| offlineAccess: false, | ||
| }); | ||
| ``` | ||
|
|
||
| > **Note:** On React Native, `storage` is a **required** field in the TypeScript type. Your project will not compile without providing a `TokenStorage` implementation. Use [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) (recommended) or wrap AsyncStorage with the `TokenStorage` interface. | ||
| > | ||
| > **Android scopes:** Android Credential Manager does not support OAuth scopes directly. If you request scopes beyond `openid`, `profile`, and `email`, the adapter will include a `serverAuthCode` in the response. Exchange this code on your backend for scoped access tokens via the Google OAuth2 token endpoint. | ||
|
|
||
| ### GoogleSignInButton (React Native) | ||
|
|
||
| Renders a styled button that triggers the native Google Sign-In flow. | ||
|
|
||
| ```tsx | ||
| <GoogleSignInButton | ||
| config={{ | ||
| clientId: 'YOUR_GOOGLE_CLIENT_ID', | ||
| webClientId: 'YOUR_WEB_CLIENT_ID', | ||
| storage: myStorage, | ||
| }} | ||
| onCredential={(credentials) => authClient.login(credentials)} | ||
| onError={(err) => Alert.alert('Error', err.message)} | ||
| style={{ marginTop: 20 }} | ||
| disabled={false} | ||
| /> | ||
| ``` | ||
|
|
||
| ### Manual integration (without GoogleSignInButton) | ||
|
|
||
| ```tsx | ||
| import { GoogleSignInModule } from '@forward-software/react-auth-google'; | ||
| import { useAuthClient } from './auth'; | ||
|
|
||
| function LoginScreen() { | ||
| const authClient = useAuthClient(); | ||
|
|
||
| const handleSignIn = async () => { | ||
| try { | ||
| const credentials = await GoogleSignInModule.signIn(); | ||
| await authClient.login(credentials); | ||
| } catch (err) { | ||
| console.error(err); | ||
| } | ||
| }; | ||
|
|
||
| return <Button title="Sign in with Google" onPress={handleSignIn} />; | ||
| } | ||
| ``` | ||
|
|
||
| ### Token refresh on React Native | ||
|
|
||
| The native adapter implements `onRefresh()` which calls `signInSilently()` to refresh tokens without user interaction. This is handled automatically by the react-auth core library when you call `authClient.refresh()`. | ||
|
|
||
| --- | ||
|
|
||
| ## Google Cloud Console Setup | ||
|
|
||
| To use Google Sign-In, you need OAuth 2.0 credentials from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials): | ||
|
|
||
| 1. Create a new project (or use an existing one) | ||
| 2. Navigate to **APIs & Services > Credentials** | ||
| 3. Click **Create Credentials > OAuth client ID** | ||
|
|
||
| ### For Web | ||
|
|
||
| - Application type: **Web application** | ||
| - Add your domain to **Authorized JavaScript origins** (e.g., `http://localhost:3000` for development) | ||
| - Copy the **Client ID** - this is your `clientId` | ||
|
|
||
| ### For iOS | ||
|
|
||
| - Application type: **iOS** | ||
| - Enter your app's **Bundle ID** | ||
| - Copy the **Client ID** - this is your `iosClientId` | ||
| - Add the reversed client ID as a URL scheme in your `Info.plist` | ||
|
|
||
| ### For Android | ||
|
|
||
| - Application type: **Android** | ||
| - Enter your app's **Package name** and **SHA-1 certificate fingerprint** | ||
| - For the `webClientId`, use the **Web application** client ID (not the Android one) | ||
|
|
||
| --- | ||
|
|
||
| ## API Reference | ||
|
|
||
| ### Types | ||
|
|
||
| ```ts | ||
| type GoogleAuthTokens = { | ||
| idToken: string; | ||
| accessToken?: string; | ||
| refreshToken?: string; | ||
| serverAuthCode?: string; | ||
| expiresAt?: number; | ||
| }; | ||
|
|
||
| type GoogleAuthCredentials = { | ||
| idToken: string; | ||
| accessToken?: string; | ||
| serverAuthCode?: string; | ||
| }; | ||
|
|
||
| interface TokenStorage { | ||
| getItem(key: string): string | null | Promise<string | null>; | ||
| setItem(key: string, value: string): void | Promise<void>; | ||
| removeItem(key: string): void | Promise<void>; | ||
| } | ||
| ``` | ||
|
|
||
| ### GoogleAuthClient | ||
|
|
||
| Implements `AuthClient<GoogleAuthTokens, GoogleAuthCredentials>` from `@forward-software/react-auth`. | ||
|
|
||
| | Method | Web | Native | Description | | ||
| |--------|-----|--------|-------------| | ||
| | `onInit()` | Yes | Yes | Restores tokens from storage. Configures native module on RN. | | ||
| | `onLogin(credentials)` | Yes | Yes | Validates and persists tokens from Google Sign-In result. | | ||
| | `onRefresh(tokens)` | No | Yes | Refreshes tokens via silent sign-in (native only). | | ||
| | `onLogout()` | Yes | Yes | Clears tokens. Calls native signOut on RN. | | ||
|
|
||
| ### GoogleSignInButton | ||
|
|
||
| | Prop | Type | Required | Description | | ||
| |------|------|----------|-------------| | ||
| | `config` | `GoogleWebAuthConfig` / `GoogleNativeAuthConfig` | Yes | Google Sign-In configuration | | ||
| | `onCredential` | `(credentials: GoogleAuthCredentials) => void` | Yes | Called with credentials after successful sign-in | | ||
| | `onError` | `(error: Error) => void` | No | Called when sign-in fails | | ||
| | `theme` | `'outline' \| 'filled_blue' \| 'filled_black'` | No | Button theme (web only) | | ||
| | `size` | `'large' \| 'medium' \| 'small'` | No | Button size (web only) | | ||
| | `text` | `'signin_with' \| 'signup_with' \| 'continue_with' \| 'signin'` | No | Button text (web only) | | ||
| | `shape` | `'rectangular' \| 'pill' \| 'circle' \| 'square'` | No | Button shape (web only) | | ||
| | `width` | `number` | No | Button width in pixels (web only) | | ||
| | `style` | `ViewStyle` | No | Custom styles (native only) | | ||
| | `disabled` | `boolean` | No | Disable the button (native only) | | ||
|
|
||
| ### GoogleSignInModule (Native only) | ||
|
|
||
| Available via `import { GoogleSignInModule } from '@forward-software/react-auth-google'` on React Native. | ||
|
|
||
| | Method | Description | | ||
| |--------|-------------| | ||
| | `configure(config)` | Initialize the native Google Sign-In SDK | | ||
| | `signIn()` | Present the Google account picker and return credentials | | ||
| | `signInSilently()` | Attempt sign-in without UI (for token refresh) | | ||
| | `getTokens()` | Get current tokens (refreshes if needed) | | ||
| | `signOut()` | Sign out the current user | | ||
|
|
||
| --- | ||
|
|
||
| ## License | ||
|
|
||
| MIT | ||
|
|
||
| --- | ||
|
|
||
| Made with ✨ & ❤️ by [ForWarD Software](https://github.com/forwardsoftware) and [contributors](https://github.com/forwardsoftware/react-auth/graphs/contributors) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| apply plugin: 'com.android.library' | ||
| apply plugin: 'kotlin-android' | ||
|
|
||
| group = 'expo.modules.googlesignin' | ||
| version = '1.0.0' | ||
|
|
||
| android { | ||
| namespace "expo.modules.googlesignin" | ||
| compileSdkVersion safeExtGet("compileSdkVersion", 34) | ||
|
|
||
| defaultConfig { | ||
| minSdkVersion safeExtGet("minSdkVersion", 23) | ||
| targetSdkVersion safeExtGet("targetSdkVersion", 34) | ||
| } | ||
|
|
||
| compileOptions { | ||
| sourceCompatibility JavaVersion.VERSION_17 | ||
| targetCompatibility JavaVersion.VERSION_17 | ||
| } | ||
|
|
||
| kotlinOptions { | ||
| jvmTarget = "17" | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation project(':expo-modules-core') | ||
| implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.9.24')}" | ||
|
|
||
| implementation 'androidx.credentials:credentials:1.3.0' | ||
| implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' | ||
| implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' | ||
| } | ||
|
|
||
| def safeExtGet(prop, fallback) { | ||
| rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.