├── .changeset ├── README.md └── config.json ├── .commitlintrc.json ├── .editorconfig ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierrc.json ├── LICENSE ├── README.md ├── apps └── playground │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── images │ │ ├── Code-snap.png │ │ ├── CredentialResponse-snap.png │ │ ├── Decoded-snap.png │ │ ├── GoogleLogin-snap.png │ │ ├── Implicit-snap.png │ │ ├── TokenResponse-snap.png │ │ ├── codeResponse-snap.png │ │ ├── google-logo.png │ │ ├── og-image.png │ │ ├── react-logo.svg │ │ ├── serverTokens-snap.png │ │ └── userInfo-snap.png │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.tsx │ ├── components │ │ ├── AuthorizationFeatures.tsx │ │ ├── CodeBlock.tsx │ │ ├── CodeFlow.tsx │ │ ├── GoogleFeatures.tsx │ │ ├── Header.tsx │ │ ├── ImplicitFlow.tsx │ │ └── SignInFlows.tsx │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── setupTests.ts │ └── types │ │ ├── enums.ts │ │ └── index.ts │ └── tsconfig.json ├── package.json ├── packages ├── @react-oauth │ └── google │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ ├── GoogleLogin.tsx │ │ ├── GoogleOAuthProvider.tsx │ │ ├── google-auth-window.d.ts │ │ ├── googleLogout.ts │ │ ├── hasGrantedAllScopesGoogle.ts │ │ ├── hasGrantedAnyScopeGoogle.ts │ │ ├── hooks │ │ │ ├── useGoogleLogin.ts │ │ │ ├── useGoogleOneTapLogin.ts │ │ │ └── useLoadGsiScript.ts │ │ ├── index.ts │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ │ └── index.ts │ │ └── tsconfig.json ├── rollup-config-generator │ ├── CHANGELOG.md │ ├── index.js │ └── package.json └── tsconfig │ ├── library.json │ └── package.json ├── turbo.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "MomenSherif/react-oauth" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "restricted", 11 | "baseBranch": "master", 12 | "updateInternalDependencies": "patch", 13 | "ignore": [] 14 | } 15 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Use Node.js 16.x 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 16.x 18 | 19 | - name: Install Dependencies 20 | run: yarn 21 | 22 | - name: Create Release Pull Request & Publish to npm 23 | uses: changesets/action@v1 24 | with: 25 | publish: yarn release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | .turbo 25 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,js,ts,tsx}": [ 3 | "prettier --write", 4 | "cross-env CI=true npm run test --if-present -- --findRelatedTests --bail" 5 | ], 6 | "*.{html,json,md,mdx,yml,yaml}": ["prettier --write"] 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "jsxSingleQuote": false, 8 | "quoteProps": "as-needed", 9 | "trailingComma": "all", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "avoid", 13 | "proseWrap": "preserve", 14 | "endOfLine": "lf" 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mo'men Sherif 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React OAuth2 | Google 2 | 3 | Google OAuth2 using the new [**Google Identity Services SDK**](https://developers.google.com/identity/gsi/web) for React [@react-oauth/google](https://www.npmjs.com/package/@react-oauth/google)🚀 4 | 5 | ## Install 6 | 7 | ```sh 8 | $ npm install @react-oauth/google@latest 9 | 10 | # or 11 | 12 | $ yarn add @react-oauth/google@latest 13 | ``` 14 | 15 | ## Demo & How to use to fetch user details 16 | 17 | https://react-oauth.vercel.app/ 18 | 19 | ## Seamless sign-in and sign-up flows 20 | 21 | ### Sign In With Google 22 | 23 | Add a personalized and customizable sign-up or sign-in button to your website. 24 | 25 | ![personalized button](https://developers.google.com/identity/gsi/web/images/personalized-button-single_480.png) 26 | 27 | ### One-tap sign-up 28 | 29 | Sign up new users with just one tap, without interrupting them with a sign-up screen. Users get a secure, token-based, passwordless account on your site, protected by their Google Account. 30 | 31 | ![One-tap sign-up](https://developers.google.com/identity/gsi/web/images/one-tap-sign-in_480.png) 32 | 33 | ### Automatic sign-in 34 | 35 | Sign users in automatically when they return to your site on any device or browser, even after their session expires. 36 | 37 | ![Automatic sign-in](https://developers.google.com/identity/gsi/web/images/auto-sign-in_480.png) 38 | 39 | ## User authorization for Google APIs (with custom button) 40 | 41 | OAuth 2.0 implicit and authorization code flows for web apps 42 | 43 | > The Google Identity Services JavaScript library helps you to quickly and safely obtain access tokens necessary to call Google APIs. Your web application, complete either the OAuth 2.0 implicit flow, or to initiate the authorization code flow which then finishes on your backend platform. 44 | 45 | ## How to use 46 | 47 | 1. Get your [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) 48 | 49 | > Key Point: Add both `http://localhost` and `http://localhost:` to the Authorized JavaScript origins box for local tests or development. 50 | 51 | 2. Configure your OAuth [**Consent Screen**](https://console.cloud.google.com/apis/credentials/consent) 52 | 53 | 3. Wrap your application with `GoogleOAuthProvider` 54 | 55 | ```jsx 56 | import { GoogleOAuthProvider } from '@react-oauth/google'; 57 | 58 | ...; 59 | ``` 60 | 61 | ### Sign In With Google 62 | 63 | ```jsx 64 | import { GoogleLogin } from '@react-oauth/google'; 65 | 66 | { 68 | console.log(credentialResponse); 69 | }} 70 | onError={() => { 71 | console.log('Login Failed'); 72 | }} 73 | />; 74 | ``` 75 | 76 | > If you are using popup mode (default), set the proper [Cross Origin Opener Policy](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#cross_origin_opener_policy) to avoid the [blank window issue](https://github.com/google/google-api-javascript-client/issues/796): `cross-origin-opener-policy: same-origin-allow-popups`. 77 | 78 | ### One-tap 79 | 80 | ```jsx 81 | import { useGoogleOneTapLogin } from '@react-oauth/google'; 82 | 83 | useGoogleOneTapLogin({ 84 | onSuccess: credentialResponse => { 85 | console.log(credentialResponse); 86 | }, 87 | onError: () => { 88 | console.log('Login Failed'); 89 | }, 90 | }); 91 | ``` 92 | 93 | or 94 | 95 | ```jsx 96 | import { GoogleLogin } from '@react-oauth/google'; 97 | 98 | { 100 | console.log(credentialResponse); 101 | }} 102 | onError={() => { 103 | console.log('Login Failed'); 104 | }} 105 | useOneTap 106 | />; 107 | ``` 108 | 109 | > If you are using one tap login, when logging user out consider [this issue](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out#sign-out) may happen, to prevent it call `googleLogout` when logging user out from your application. 110 | 111 | ```jsx 112 | import { googleLogout } from '@react-oauth/google'; 113 | 114 | googleLogout(); 115 | ``` 116 | 117 | ### Automatic sign-in 118 | 119 | > `auto_select` prop `true` 120 | 121 | ```jsx 122 | 123 | 127 | 128 | useGoogleOneTapLogin({ 129 | ... 130 | auto_select 131 | }); 132 | ``` 133 | 134 | ### Custom login button (implicit & authorization code flow) 135 | 136 | #### Implicit flow 137 | 138 | ```jsx 139 | import { useGoogleLogin } from '@react-oauth/google'; 140 | 141 | const login = useGoogleLogin({ 142 | onSuccess: tokenResponse => console.log(tokenResponse), 143 | }); 144 | 145 | login()}>Sign in with Google 🚀; 146 | ``` 147 | 148 | #### Authorization code flow 149 | 150 | > Requires backend to exchange code with access and refresh token. 151 | 152 | ```jsx 153 | import { useGoogleLogin } from '@react-oauth/google'; 154 | 155 | const login = useGoogleLogin({ 156 | onSuccess: codeResponse => console.log(codeResponse), 157 | flow: 'auth-code', 158 | }); 159 | 160 | login()}>Sign in with Google 🚀; 161 | ``` 162 | 163 | #### Checks if the user granted all the specified scope or scopes 164 | 165 | ```jsx 166 | import { hasGrantedAllScopesGoogle } from '@react-oauth/google'; 167 | 168 | const hasAccess = hasGrantedAllScopesGoogle( 169 | tokenResponse, 170 | 'google-scope-1', 171 | 'google-scope-2', 172 | ); 173 | ``` 174 | 175 | #### Checks if the user granted any of the specified scope or scopes 176 | 177 | ```jsx 178 | import { hasGrantedAnyScopeGoogle } from '@react-oauth/google'; 179 | 180 | const hasAccess = hasGrantedAnyScopeGoogle( 181 | tokenResponse, 182 | 'google-scope-1', 183 | 'google-scope-2', 184 | ); 185 | ``` 186 | 187 | #### [Content Security Policy (if needed)](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy) 188 | 189 | ## API 190 | 191 | ### GoogleOAuthProvider 192 | 193 | | Required | Prop | Type | Description | 194 | | :------: | ------------------- | ---------- | --------------------------------------------------------------------------- | 195 | | ✓ | clientId | `string` | [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) | 196 | | | nonce | `string` | Nonce applied to GSI script tag. Propagates to GSI's inline style tag | 197 | | | onScriptLoadSuccess | `function` | Callback fires on load gsi script success | 198 | | | onScriptLoadError | `function` | Callback fires on load gsi script failure | 199 | 200 | ### GoogleLogin 201 | 202 | | Required | Prop | Type | Description | 203 | | :------: | ---------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 204 | | ✓ | onSuccess | `(response: CredentialResponse) => void` | Callback fires with credential response after successfully login | 205 | | | onError | `function` | Callback fires after login failure | 206 | | | type | `standard` \| `icon` | Button type [type](https://developers.google.com/identity/gsi/web/reference/js-reference#type) | 207 | | | theme | `outline` \| `filled_blue` \| `filled_black` | Button [theme](https://developers.google.com/identity/gsi/web/reference/js-reference#theme) | 208 | | | size | `large` \| `medium` \| `small` | Button [size](https://developers.google.com/identity/gsi/web/reference/js-reference#size) | 209 | | | text | `signin_with` \| `signup_with` \| `continue_with` \| `signin` | Button [text](https://developers.google.com/identity/gsi/web/reference/js-reference#text). For example, "Sign in with Google", "Sign up with Google" or "Sign in" | 210 | | | shape | `rectangular` \| `pill` \| `circle` \| `square` | Button [shape](https://developers.google.com/identity/gsi/web/reference/js-reference#shape) | 211 | | | logo_alignment | `left` \| `center` | Google [logo alignment](https://developers.google.com/identity/gsi/web/reference/js-reference#logo_alignment) | 212 | | | width | `string` | button [width](https://developers.google.com/identity/gsi/web/reference/js-reference#width), in pixels | 213 | | | locale | `string` | If set, then the button [language](https://developers.google.com/identity/gsi/web/reference/js-reference#locale) is rendered | 214 | | | useOneTap | `boolean` | Activate One-tap sign-up or not | 215 | | | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description | 216 | | | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt | 217 | | | auto_select | `boolean` | Enables automatic selection on Google One Tap | 218 | | | ux_mode | `popup` \| `redirect` | The Sign In With Google button UX flow | 219 | | | login_uri | `string` | The URL of your login endpoint | 220 | | | native_login_uri | `string` | The URL of your password credential handler endpoint | 221 | | | native_callback | `(response: { id: string; password: string }) => void` | The JavaScript password credential handler function name | 222 | | | prompt_parent_id | `string` | The DOM ID of the One Tap prompt container element | 223 | | | nonce | `string` | A random string for ID tokens | 224 | | | context | `signin` \| `signup` \| `use` | The title and words in the One Tap prompt | 225 | | | state_cookie_domain | `string` | If you need to call One Tap in the parent domain and its subdomains, pass the parent domain to this attribute so that a single shared cookie is used | 226 | | | allowed_parent_origin | `string` \| `string[]` | The origins that are allowed to embed the intermediate iframe. One Tap will run in the intermediate iframe mode if this attribute presents | 227 | | | intermediate_iframe_close_callback | `function` | Overrides the default intermediate iframe behavior when users manually close One Tap | 228 | | | itp_support | `boolean` | Enables upgraded One Tap UX on ITP browsers | 229 | | | 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 | 230 | | | 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. | 231 | | | use_fedcm_for_button | `boolean` | Enable FedCM Button flow. | 232 | 233 | ### useGoogleLogin (Both implicit & authorization code flow) 234 | 235 | | Required | Prop | Type | Description | 236 | | :------: | --------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 237 | | | flow | `implicit` \| `auth-code` | [Two flows](https://developers.google.com/identity/oauth2/web/guides/how-user-authz-works), implicit and authorization code are discussed. Both return an access token suitable for use with Google APIs | 238 | | | onSuccess | `(response: TokenResponse\|CodeResponse) => void` | Callback fires with response ([token](https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse) \| [code](https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse)) based on flow selected after successfully login | 239 | | | onError | `(errorResponse: {error: string; error_description?: string,error_uri?: string}) => void` | Callback fires after login failure | 240 | | | onNonOAuthError | `(nonOAuthError: NonOAuthError) => void` | Some non-OAuth errors, such as the popup window is failed to open or closed before an OAuth response is returned. `popup_failed_to_open` \| `popup_closed` \| `unknown` | 241 | | | scope | `string` | A space-delimited list of scopes that are approved by the user | 242 | | | enable_serial_consent | `boolean` | defaults to true. If set to false, [more granular Google Account permissions](https://developers.googleblog.com/2018/10/more-granular-google-account.html) will be disabled for clients created before 2019. No effect for newer clients, since more granular permissions is always enabled for them. | 243 | | | hint | `string` | If your application knows which user should authorize the request, it can use this property to provide a hint to Google. The email address for the target user. For more information, see the [login_hint](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs | 244 | | | 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 | 245 | 246 | ### useGoogleLogin (Extra implicit flow props) 247 | 248 | | Required | Prop | Type | Description | 249 | | :------: | ------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 250 | | | prompt | `''` \| `none` \| `consent` \| `select_account` | defaults to 'select_account'. A space-delimited, case-sensitive list of prompts to present the user | 251 | | | state | `string` | Not recommended. Specifies any string value that your application uses to maintain state between your authorization request and the authorization server's response | 252 | 253 | ### useGoogleLogin (Extra authorization code flow props) 254 | 255 | | Required | Prop | Type | Description | 256 | | :------: | -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 257 | | | ux_mode | `popup` \| `redirect` | The UX mode to use for the authorization flow. By default, it will open the consent flow in a popup. Valid values are popup and redirect | 258 | | | redirect_uri | `string` | Required for redirect UX. Determines where the API server redirects the user after the user completes the authorization flow The value must exactly match one of the authorized redirect URIs for the OAuth 2.0 client which you configured in the API Console and must conform to our [Redirect URI validation](https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation) rules. The property will be ignored by the popup UX | 259 | | | state | `string` | Recommended for redirect UX. Specifies any string value that your application uses to maintain state between your authorization request and the authorization server's response | 260 | | | select_account | `boolean` | defaults to 'false'. Boolean value to prompt the user to select an account | 261 | 262 | ### useGoogleOneTapLogin 263 | 264 | | Required | Prop | Type | Description | 265 | | :------: | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 266 | | ✓ | onSuccess | `(response: CredentialResponse) => void` | Callback fires with credential response after successfully login | 267 | | | onError | `function` | Callback fires after login failure | 268 | | | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description | 269 | | | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt | 270 | | | 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 | 271 | | | disabled | `boolean` | Controls whether to cancel the popup in cases such as when the user is already logged in | 272 | | | 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. | 273 | | | use_fedcm_for_button | `boolean` | Enable FedCM Button flow. | 274 | -------------------------------------------------------------------------------- /apps/playground/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /apps/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@chakra-ui/icons": "^2.0.0", 7 | "@chakra-ui/react": "^2.0.2", 8 | "@emotion/react": "^11", 9 | "@emotion/styled": "^11", 10 | "@react-oauth/google": "*", 11 | "@testing-library/jest-dom": "^5.16.4", 12 | "@testing-library/react": "^13.2.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "@types/jest": "^27.5.1", 15 | "@types/node": "^16.11.35", 16 | "@types/react": "^18.0.9", 17 | "@types/react-dom": "^18.0.4", 18 | "axios": "^0.27.2", 19 | "framer-motion": "^6", 20 | "jwt-decode": "^3.1.2", 21 | "react": "^18.1.0", 22 | "react-dom": "^18.1.0", 23 | "react-scripts": "5.0.1", 24 | "react-use-clipboard": "^1.0.8", 25 | "typescript": "^4.6.4", 26 | "web-vitals": "^2.1.4" 27 | }, 28 | "scripts": { 29 | "dev": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test", 32 | "eject": "react-scripts eject" 33 | }, 34 | "eslintConfig": { 35 | "extends": [ 36 | "react-app", 37 | "react-app/jest" 38 | ] 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/favicon.ico -------------------------------------------------------------------------------- /apps/playground/public/images/Code-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/Code-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/CredentialResponse-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/CredentialResponse-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/Decoded-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/Decoded-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/GoogleLogin-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/GoogleLogin-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/Implicit-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/Implicit-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/TokenResponse-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/TokenResponse-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/codeResponse-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/codeResponse-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/google-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/google-logo.png -------------------------------------------------------------------------------- /apps/playground/public/images/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/og-image.png -------------------------------------------------------------------------------- /apps/playground/public/images/react-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/playground/public/images/serverTokens-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/serverTokens-snap.png -------------------------------------------------------------------------------- /apps/playground/public/images/userInfo-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/images/userInfo-snap.png -------------------------------------------------------------------------------- /apps/playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 45 | @react-oauth/google 46 | 47 | 48 | 60 | 61 | 62 | 63 | 64 | 72 | 73 | 74 |
75 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /apps/playground/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/logo192.png -------------------------------------------------------------------------------- /apps/playground/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MomenSherif/react-oauth/4bcf49488c8c0beb80a49871508038148b8ce8bf/apps/playground/public/logo512.png -------------------------------------------------------------------------------- /apps/playground/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /apps/playground/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Button, Flex } from '@chakra-ui/react'; 3 | 4 | import Header from './components/Header'; 5 | import GoogleFeatures from './components/GoogleFeatures'; 6 | import SignInFlows from './components/SignInFlows'; 7 | 8 | import { Flows } from './types/enums'; 9 | import AuthorizationFeatures from './components/AuthorizationFeatures'; 10 | 11 | function App() { 12 | const [flow, setFlow] = useState(null); 13 | 14 | return ( 15 | 23 |
24 | 25 | {!flow ? ( 26 | 27 | ) : ( 28 | 37 | )} 38 | {flow === Flows.SignIn && } 39 | {flow === Flows.Authorization && } 40 | 41 | ); 42 | } 43 | 44 | export default App; 45 | -------------------------------------------------------------------------------- /apps/playground/src/components/AuthorizationFeatures.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { 3 | Button, 4 | Flex, 5 | List, 6 | ListIcon, 7 | ListItem, 8 | Stack, 9 | StackDivider, 10 | } from '@chakra-ui/react'; 11 | import { SmallAddIcon, SmallCloseIcon } from '@chakra-ui/icons'; 12 | import { Feature } from '../types'; 13 | import ImplicitFlow from './ImplicitFlow'; 14 | import CodeFlow from './CodeFlow'; 15 | 16 | const implicitFlowFeatures: Feature[] = [ 17 | { 18 | label: 'User info', 19 | available: true, 20 | }, 21 | { 22 | label: 'Access token', 23 | available: true, 24 | }, 25 | { 26 | label: 'ID token (JWT)', 27 | available: false, 28 | }, 29 | { 30 | label: 'Refresh token', 31 | available: false, 32 | }, 33 | { 34 | label: 'Backend required', 35 | available: false, 36 | }, 37 | ]; 38 | 39 | const authorizationCodeFlowFeatures: Feature[] = [ 40 | { label: 'Backend required', available: true }, 41 | { label: 'User info', available: true }, 42 | { label: 'Access token', available: true }, 43 | { label: 'ID token (JWT)', available: true }, 44 | { label: 'Refresh token', available: true }, 45 | ]; 46 | 47 | export default function AuthorizationFeatures() { 48 | const [flow, setFlow] = useState<'implict' | 'code' | null>(null); 49 | return ( 50 | <> 51 | {!flow && ( 52 | } 56 | mt="5" 57 | > 58 | 59 | 60 | {implicitFlowFeatures.map(feat => ( 61 | 62 | 66 | {feat.label} 67 | 68 | ))} 69 | 70 | 77 | 78 | 79 | 80 | {authorizationCodeFlowFeatures.map(feat => ( 81 | 82 | 86 | {feat.label} 87 | 88 | ))} 89 | 90 | 97 | 98 | 99 | )} 100 | {flow === 'implict' && } 101 | {flow === 'code' && } 102 | 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /apps/playground/src/components/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Img, IconButton } from '@chakra-ui/react'; 2 | import { CopyIcon, CheckIcon } from '@chakra-ui/icons'; 3 | import useClipboard from 'react-use-clipboard'; 4 | 5 | type CodeBlockProps = { 6 | imgSrc: string; 7 | code?: string; 8 | }; 9 | 10 | export default function CodeBlock({ imgSrc, code = '' }: CodeBlockProps) { 11 | const [isCopied, setCopied] = useClipboard(code, { successDuration: 3000 }); 12 | 13 | return ( 14 | 15 | 16 | {code && ( 17 | : } 19 | aria-label="Copy" 20 | position="absolute" 21 | right="1" 22 | top="1" 23 | variant="ghost" 24 | colorScheme="whiteAlpha" 25 | onClick={() => !isCopied && setCopied()} 26 | /> 27 | )} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /apps/playground/src/components/CodeFlow.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Button, Link, Text, VStack } from '@chakra-ui/react'; 3 | import { useGoogleLogin, CodeResponse } from '@react-oauth/google'; 4 | 5 | import CodeBlock from './CodeBlock'; 6 | 7 | const code = ` 8 | const googleLogin = useGoogleLogin({ 9 | flow: 'auth-code', 10 | onSuccess: async (codeResponse) => { 11 | console.log(codeResponse); 12 | const tokens = await axios.post( 13 | 'http://localhost:3001/auth/google', { 14 | code: codeResponse.code, 15 | }); 16 | 17 | console.log(tokens); 18 | }, 19 | onError: errorResponse => console.log(errorResponse), 20 | }); 21 | `; 22 | 23 | export default function CodeFlow() { 24 | const [codeResponse, setCodeResponse] = useState(); 25 | 26 | const googleLogin = useGoogleLogin({ 27 | flow: 'auth-code', 28 | onSuccess: async codeResponse => { 29 | setCodeResponse(codeResponse); 30 | }, 31 | onError: errorResponse => console.log(errorResponse), 32 | }); 33 | 34 | return ( 35 | 36 | 43 | useGoogleLogin Props 44 | 45 | 48 | 49 | 50 | 54 | 55 | 56 | Exchange "code" for tokens from your backend 57 | 58 | 59 | 60 | 61 | 66 | Check backend (express) implementation 67 | 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /apps/playground/src/components/GoogleFeatures.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Flex, 4 | List, 5 | ListIcon, 6 | ListItem, 7 | Stack, 8 | StackDivider, 9 | } from '@chakra-ui/react'; 10 | import { SmallAddIcon, SmallCloseIcon } from '@chakra-ui/icons'; 11 | 12 | import { Feature } from '../types'; 13 | import { Flows } from '../types/enums'; 14 | 15 | const signInFlowsFeatures: Feature[] = [ 16 | { 17 | label: 'Simple authentication', 18 | available: true, 19 | }, 20 | { 21 | label: 'Personalized button', 22 | available: true, 23 | }, 24 | { 25 | label: 'One tap prompt', 26 | available: true, 27 | }, 28 | { 29 | label: 'ID token (JWT)', 30 | available: true, 31 | }, 32 | { 33 | label: 'Access token', 34 | available: false, 35 | }, 36 | { 37 | label: 'Refresh token', 38 | available: false, 39 | }, 40 | { 41 | label: 'Define extra scopes', 42 | available: false, 43 | }, 44 | { 45 | label: 'Custom button', 46 | available: false, 47 | }, 48 | ]; 49 | 50 | const authorizationFlowsFeatures: Feature[] = [ 51 | { label: 'Custom button', available: true }, 52 | { label: 'Authentication & Authorization', available: true }, 53 | { label: 'Define extra scopes', available: true }, 54 | { label: 'ID token (JWT)', available: true }, 55 | { label: 'Access token', available: true }, 56 | { label: 'Refresh token', available: true }, 57 | { 58 | label: 'Personalized button', 59 | available: false, 60 | }, 61 | { 62 | label: 'One tap prompt', 63 | available: false, 64 | }, 65 | ]; 66 | 67 | type GoogleFeaturesProps = { 68 | setFlow: (flow: Flows) => void; 69 | }; 70 | 71 | export default function GoogleFeatures({ setFlow }: GoogleFeaturesProps) { 72 | return ( 73 | } 77 | mt="5" 78 | > 79 | 80 | 81 | {signInFlowsFeatures.map(feat => ( 82 | 83 | 87 | {feat.label} 88 | 89 | ))} 90 | 91 | 98 | 99 | 100 | 101 | {authorizationFlowsFeatures.map(feat => ( 102 | 103 | 107 | {feat.label} 108 | 109 | ))} 110 | 111 | 118 | 119 | 120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /apps/playground/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Heading, Flex, Img, Text } from '@chakra-ui/react'; 2 | 3 | export default function Header() { 4 | return ( 5 | <> 6 | 12 | 13 | @react-oauth/google 14 | 15 | 16 | 17 | Google OAuth2 using the new Google Identity Services SDK for React 🚀 18 | 19 | 20 | 21 | React logo 22 | Google logo 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/playground/src/components/ImplicitFlow.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Button, Link, Text, VStack } from '@chakra-ui/react'; 3 | import { useGoogleLogin, TokenResponse } from '@react-oauth/google'; 4 | import axios from 'axios'; 5 | 6 | import CodeBlock from './CodeBlock'; 7 | 8 | const code = ` 9 | const googleLogin = useGoogleLogin({ 10 | onSuccess: async (tokenResponse) => { 11 | console.log(tokenResponse); 12 | const userInfo = await axios.get( 13 | 'https://www.googleapis.com/oauth2/v3/userinfo', 14 | { headers: { Authorization: 'Bearer ' } }, 15 | ); 16 | 17 | console.log(userInfo); 18 | }, 19 | onError: errorResponse => console.log(errorResponse), 20 | }); 21 | `; 22 | 23 | export default function ImplicitFlow() { 24 | const [tokenResponse, setTokenResponse] = useState(); 25 | const [user, setUser] = useState(null); 26 | 27 | const googleLogin = useGoogleLogin({ 28 | onSuccess: async tokenResponse => { 29 | const userInfo = await axios 30 | .get('https://www.googleapis.com/oauth2/v3/userinfo', { 31 | headers: { Authorization: `Bearer ${tokenResponse.access_token}` }, 32 | }) 33 | .then(res => res.data); 34 | 35 | setTokenResponse(tokenResponse); 36 | setUser(userInfo); 37 | }, 38 | onError: errorResponse => console.log(errorResponse), 39 | }); 40 | 41 | return ( 42 | 43 | 50 | useGoogleLogin Props 51 | 52 | 55 | 56 | 57 | 63 | 64 | In Implicit Token flow, you can't get JWT but also you don't need to get 65 | it to get user profile info. Google gives you access_token to talk with 66 | their APIs 67 | 68 | 72 | 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /apps/playground/src/components/SignInFlows.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react'; 2 | import { Link, Stack, Text, VStack } from '@chakra-ui/react'; 3 | import { GoogleLogin, CredentialResponse } from '@react-oauth/google'; 4 | import jwtDecode from 'jwt-decode'; 5 | 6 | import CodeBlock from './CodeBlock'; 7 | 8 | const code = ` 9 | { 11 | console.log(credentialResponse); 12 | }} 13 | onError={() => { 14 | console.log('Login Failed'); 15 | }} 16 | /> 17 | `; 18 | 19 | export default function SignInFlows() { 20 | const [credentialResponse, setCredentialResponse] = 21 | useState(); 22 | 23 | const user = useMemo(() => { 24 | if (!credentialResponse?.credential) return; 25 | return jwtDecode(credentialResponse.credential); 26 | }, [credentialResponse]); 27 | 28 | return ( 29 | 30 | 37 | GoogleLogin Props 38 | 39 | 40 | { 42 | setCredentialResponse(credentialResponse); 43 | }} 44 | onError={() => { 45 | console.log('Login Failed'); 46 | }} 47 | /> 48 | { 52 | setCredentialResponse(credentialResponse); 53 | }} 54 | onError={() => { 55 | console.log('Login Failed'); 56 | }} 57 | /> 58 | { 63 | setCredentialResponse(credentialResponse); 64 | }} 65 | onError={() => { 66 | console.log('Login Failed'); 67 | }} 68 | /> 69 | 70 | 71 | 79 | Decode Credential (JWT) to get user info 80 | 84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /apps/playground/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { ChakraProvider } from '@chakra-ui/react'; 4 | 5 | import App from './App'; 6 | import reportWebVitals from './reportWebVitals'; 7 | import { GoogleOAuthProvider } from '@react-oauth/google'; 8 | 9 | const root = ReactDOM.createRoot( 10 | document.getElementById('root') as HTMLElement, 11 | ); 12 | 13 | root.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | , 21 | ); 22 | 23 | // If you want to start measuring performance in your app, pass a function 24 | // to log results (for example: reportWebVitals(console.log)) 25 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 26 | reportWebVitals(); 27 | -------------------------------------------------------------------------------- /apps/playground/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace NodeJS { 4 | export interface ProcessEnv { 5 | REACT_APP_CLIENT_ID: string; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/playground/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /apps/playground/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /apps/playground/src/types/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Flows { 2 | SignIn = 'SignIn', 3 | Authorization = 'Authorization', 4 | } 5 | -------------------------------------------------------------------------------- /apps/playground/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type Feature = { 2 | label: string; 3 | available: boolean; 4 | }; 5 | -------------------------------------------------------------------------------- /apps/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-oauth/monorepo", 3 | "private": true, 4 | "description": "Monorepo for OAuth2 providers in React 🚀", 5 | "workspaces": [ 6 | "packages/*", 7 | "packages/@react-oauth/*", 8 | "apps/*" 9 | ], 10 | "scripts": { 11 | "dev": "cross-env NODE_ENV=development turbo run dev", 12 | "dev:google": "npm run dev -- --filter=...@react-oauth/google", 13 | "build": "cross-env NODE_ENV=production turbo run build", 14 | "prettier:check": "prettier . --check --ignore-unknown --ignore-path .gitignore", 15 | "prettier:fix": "prettier . --write --ignore-unknown --ignore-path .gitignore", 16 | "prerelease": "npm run build -- --filter=./packages/@react-oauth/*", 17 | "changeset": "changeset", 18 | "release": "changeset publish", 19 | "commit": "cz", 20 | "prepare": "is-ci || husky install" 21 | }, 22 | "devDependencies": { 23 | "@changesets/changelog-github": "^0.4.4", 24 | "@changesets/cli": "^2.22.0", 25 | "@commitlint/cli": "^16.3.0", 26 | "@commitlint/config-conventional": "^16.2.4", 27 | "commitizen": "^4.2.4", 28 | "cross-env": "^7.0.3", 29 | "cz-conventional-changelog": "^3.3.0", 30 | "husky": "^8.0.1", 31 | "is-ci": "^3.0.1", 32 | "lint-staged": "^12.4.1", 33 | "prettier": "^2.6.2", 34 | "turbo": "^1.2.9" 35 | }, 36 | "config": { 37 | "commitizen": { 38 | "path": "cz-conventional-changelog" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @react-oauth/google 2 | 3 | ## 0.12.2 4 | 5 | ### Patch Changes 6 | 7 | - [#387](https://github.com/MomenSherif/react-oauth/pull/387) [`85c49bf`](https://github.com/MomenSherif/react-oauth/commit/85c49bf8e8d130db4c3a62f0b40dbc21dde3da2a) Thanks [@pierroberto](https://github.com/pierroberto)! - Add support for FedCM Button Flow 8 | 9 | ## 0.12.1 10 | 11 | ### Patch Changes 12 | 13 | - [`d237197`](https://github.com/MomenSherif/react-oauth/commit/d237197ac898a041d4cdf3e458651b2ccc3545cc) Thanks [@MomenSherif](https://github.com/MomenSherif)! - add `use client` directive to support nextjs out of the box 14 | 15 | ## 0.12.0 16 | 17 | ### Minor Changes 18 | 19 | - [#316](https://github.com/MomenSherif/react-oauth/pull/316) [`9c23c44`](https://github.com/MomenSherif/react-oauth/commit/9c23c442d9a383d47fab6061b5842d6da4b22e5a) Thanks [@MomenSherif](https://github.com/MomenSherif)! - - add support for use_fedcm_for_prompt for fedcm migration 20 | - export `useGoogleOAuth` returns { scriptLoadedSuccessfully: boolean; clientId: string } 21 | 22 | ## 0.11.1 23 | 24 | ### Patch Changes 25 | 26 | - [#278](https://github.com/MomenSherif/react-oauth/pull/278) [`cb2a8ce`](https://github.com/MomenSherif/react-oauth/commit/cb2a8ceb7cad2d44dbaf4e0320b4a45bbaec683e) Thanks [@MomenSherif](https://github.com/MomenSherif)! - allow width to be string or number for google login button 27 | 28 | ## 0.11.0 29 | 30 | ### Minor Changes 31 | 32 | - [#253](https://github.com/MomenSherif/react-oauth/pull/253) [`3826f02`](https://github.com/MomenSherif/react-oauth/commit/3826f02e244a20e3f67dc6a6848950a8f58c26f2) Thanks [@LivioGama](https://github.com/LivioGama)! - add state_cookie_domain to allow multiple subdomains connection 33 | 34 | ## 0.10.0 35 | 36 | ### Minor Changes 37 | 38 | - [#249](https://github.com/MomenSherif/react-oauth/pull/249) [`2217cc5`](https://github.com/MomenSherif/react-oauth/commit/2217cc508c7c94587f114abfd52548d5aa06dedb) Thanks [@MomenSherif](https://github.com/MomenSherif)! - add prompt_parent_id to change the prompt default's position 39 | 40 | ## 0.9.1 41 | 42 | ### Patch Changes 43 | 44 | - [#236](https://github.com/MomenSherif/react-oauth/pull/236) [`a9832bf`](https://github.com/MomenSherif/react-oauth/commit/a9832bfb05536e03b05e77cbcd573394db68ab4d) Thanks [@jcayabyab](https://github.com/jcayabyab)! - add include_granted_scopes to CodeClientConfig and TokenClientConfig 45 | 46 | ## 0.9.0 47 | 48 | ### Minor Changes 49 | 50 | - [#222](https://github.com/MomenSherif/react-oauth/pull/222) [`2364e3f`](https://github.com/MomenSherif/react-oauth/commit/2364e3f48a0672d76a1089b09083ab9bad408990) Thanks [@p1yu5h](https://github.com/p1yu5h)! - Added disabled prop to cancel the one tap login popup 51 | 52 | ## 0.8.1 53 | 54 | ### Patch Changes 55 | 56 | - [#230](https://github.com/MomenSherif/react-oauth/pull/230) [`c9238a7`](https://github.com/MomenSherif/react-oauth/commit/c9238a794106189464d451f20333f5d8ad866854) Thanks [@danfsd](https://github.com/danfsd)! - Add minor CSP support by accepting "nonce" and propagating it to GSI script & inline style 57 | 58 | ## 0.8.0 59 | 60 | ### Minor Changes 61 | 62 | - [`1cc9069`](https://github.com/MomenSherif/react-oauth/commit/1cc9069caf6c294806c11124519e289a5484ee2b) Thanks [@MomenSherif](https://github.com/MomenSherif)! - add container props to div button container 63 | 64 | ## 0.7.3 65 | 66 | ### Patch Changes 67 | 68 | - [#199](https://github.com/MomenSherif/react-oauth/pull/199) [`934e58b`](https://github.com/MomenSherif/react-oauth/commit/934e58b1189864f8f72461e687988ada04256a61) Thanks [@abereghici](https://github.com/abereghici)! - Improved definition types. Added missing error_callback function in TokenResponse type" 69 | 70 | ## 0.7.1 71 | 72 | ### Patch Changes 73 | 74 | - [#193](https://github.com/MomenSherif/react-oauth/pull/193) [`cc60a81`](https://github.com/MomenSherif/react-oauth/commit/cc60a8117857cc5ceaa0143e4f48512e8ee8af4f) Thanks [@jimcapel](https://github.com/jimcapel)! - Fixed a crash when window.google.accounts.id was undefined 75 | 76 | ## 0.7.0 77 | 78 | ### Minor Changes 79 | 80 | - [#177](https://github.com/MomenSherif/react-oauth/pull/177) [`715c4e8`](https://github.com/MomenSherif/react-oauth/commit/715c4e89f0e421bd16000d29ca79a8b597a77e35) Thanks [@MicahRamirez](https://github.com/MicahRamirez)! - Added state as part of the dependency array in useGoogleLogin to allow updates to the state in redirect mode 81 | 82 | ## 0.6.1 83 | 84 | ### Patch Changes 85 | 86 | - [`7e8b73e`](https://github.com/MomenSherif/react-oauth/commit/7e8b73e75ae9d0c812808ce60c7f46805c5c76ea) Thanks [@MomenSherif](https://github.com/MomenSherif)! - fix: useGoogleLogin login methods error before js loads 87 | 88 | ## 0.6.0 89 | 90 | ### Minor Changes 91 | 92 | - [#150](https://github.com/MomenSherif/react-oauth/pull/150) [`41acd39`](https://github.com/MomenSherif/react-oauth/commit/41acd39474458ca05db7c08aaea7f78e047a95ef) Thanks [@niksauer](https://github.com/niksauer)! - add click_listener for GoogleLogin button 93 | 94 | ## 0.5.1 95 | 96 | ### Patch Changes 97 | 98 | - [`2b35b38`](https://github.com/MomenSherif/react-oauth/commit/2b35b38065bdcb7d7bb6d2a062b221e620f9d091) Thanks [@MomenSherif](https://github.com/MomenSherif)! - Support non ouath error for useGoogleLogin 99 | 100 | ## 0.5.0 101 | 102 | ### Minor Changes 103 | 104 | - [`e956408`](https://github.com/MomenSherif/react-oauth/commit/e9564081c6241a7b2e85157cfc35e8db43b2560d) Thanks [@MomenSherif](https://github.com/MomenSherif)! - Ability to override default scope 105 | 106 | ## 0.4.0 107 | 108 | ### Minor Changes 109 | 110 | - [`8b80b8c`](https://github.com/MomenSherif/react-oauth/commit/8b80b8cdb342f3d127e058e8959a2ae7354ee690) Thanks [@MomenSherif](https://github.com/MomenSherif)! - Makes the client_id change on CredentialResponse in Brazil compatible for the current clientId and removes the verification to trigger onSuccess. 111 | 112 | ## 0.3.0 113 | 114 | ### Minor Changes 115 | 116 | - [#121](https://github.com/MomenSherif/react-oauth/pull/121) [`54e1856`](https://github.com/MomenSherif/react-oauth/commit/54e185654cb7bd1dde17249f800df8e8a97fd5ac) Thanks [@yuriburk](https://github.com/yuriburk)! - Makes the client_id change on CredentialResponse in Brazil compatible for the current clientId and removes the verification to trigger onSuccess. 117 | 118 | ## 0.2.8 119 | 120 | ### Patch Changes 121 | 122 | - [`7148dc8`](https://github.com/MomenSherif/react-oauth/commit/7148dc8779b999b52f68a8fdfdeaf136eaf54f3f) Thanks [@MomenSherif](https://github.com/MomenSherif)! - Update README.md 123 | 124 | ## 0.2.7 125 | 126 | ### Minor Changes 127 | 128 | - Export useGoogleLogin option types 129 | 130 | ### Minor Changes 131 | 132 | - [#87](https://github.com/MomenSherif/react-oauth/pull/87) [`787ed02`](https://github.com/MomenSherif/react-oauth/commit/787ed022133546619765ad25261598347fe98948) Thanks [@MomenSherif](https://github.com/MomenSherif)! - Export useGoogleLogin options types 133 | 134 | ## 0.2.6 135 | 136 | ### Patch Changes 137 | 138 | - [#28](https://github.com/MomenSherif/react-oauth/pull/28) [`34967fa`](https://github.com/MomenSherif/react-oauth/commit/34967faad363581f92ba38862d7722b99e06d653) Thanks [@MomenSherif](https://github.com/MomenSherif)! - docs(readme.md): add playground website 139 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/README.md: -------------------------------------------------------------------------------- 1 | # React OAuth2 | Google 2 | 3 | Google OAuth2 using the new [**Google Identity Services SDK**](https://developers.google.com/identity/gsi/web) for React [@react-oauth/google](https://www.npmjs.com/package/@react-oauth/google)🚀 4 | 5 | ## Install 6 | 7 | ```sh 8 | $ npm install @react-oauth/google@latest 9 | 10 | # or 11 | 12 | $ yarn add @react-oauth/google@latest 13 | ``` 14 | 15 | ## Demo & How to use to fetch user details 16 | 17 | https://react-oauth.vercel.app/ 18 | 19 | ## Seamless sign-in and sign-up flows 20 | 21 | ### Sign In With Google 22 | 23 | Add a personalized and customizable sign-up or sign-in button to your website. 24 | 25 | ![personalized button](https://developers.google.com/identity/gsi/web/images/personalized-button-single_480.png) 26 | 27 | ### One-tap sign-up 28 | 29 | Sign up new users with just one tap, without interrupting them with a sign-up screen. Users get a secure, token-based, passwordless account on your site, protected by their Google Account. 30 | 31 | ![One-tap sign-up](https://developers.google.com/identity/gsi/web/images/one-tap-sign-in_480.png) 32 | 33 | ### Automatic sign-in 34 | 35 | Sign users in automatically when they return to your site on any device or browser, even after their session expires. 36 | 37 | ![Automatic sign-in](https://developers.google.com/identity/gsi/web/images/auto-sign-in_480.png) 38 | 39 | ## User authorization for Google APIs (with custom button) 40 | 41 | OAuth 2.0 implicit and authorization code flows for web apps 42 | 43 | > The Google Identity Services JavaScript library helps you to quickly and safely obtain access tokens necessary to call Google APIs. Your web application, complete either the OAuth 2.0 implicit flow, or to initiate the authorization code flow which then finishes on your backend platform. 44 | 45 | ## How to use 46 | 47 | 1. Get your [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) 48 | 49 | > Key Point: Add both `http://localhost` and `http://localhost:` to the Authorized JavaScript origins box for local tests or development. 50 | 51 | 2. Configure your OAuth [**Consent Screen**](https://console.cloud.google.com/apis/credentials/consent) 52 | 53 | 3. Wrap your application with `GoogleOAuthProvider` 54 | 55 | ```jsx 56 | import { GoogleOAuthProvider } from '@react-oauth/google'; 57 | 58 | ...; 59 | ``` 60 | 61 | ### Sign In With Google 62 | 63 | ```jsx 64 | import { GoogleLogin } from '@react-oauth/google'; 65 | 66 | { 68 | console.log(credentialResponse); 69 | }} 70 | onError={() => { 71 | console.log('Login Failed'); 72 | }} 73 | />; 74 | ``` 75 | 76 | ### One-tap 77 | 78 | ```jsx 79 | import { useGoogleOneTapLogin } from '@react-oauth/google'; 80 | 81 | useGoogleOneTapLogin({ 82 | onSuccess: credentialResponse => { 83 | console.log(credentialResponse); 84 | }, 85 | onError: () => { 86 | console.log('Login Failed'); 87 | }, 88 | }); 89 | ``` 90 | 91 | or 92 | 93 | ```jsx 94 | import { GoogleLogin } from '@react-oauth/google'; 95 | 96 | { 98 | console.log(credentialResponse); 99 | }} 100 | onError={() => { 101 | console.log('Login Failed'); 102 | }} 103 | useOneTap 104 | />; 105 | ``` 106 | 107 | > If you are using one tap login, when logging user out consider [this issue](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out#sign-out) may happen, to prevent it call `googleLogout` when logging user out from your application. 108 | 109 | ```jsx 110 | import { googleLogout } from '@react-oauth/google'; 111 | 112 | googleLogout(); 113 | ``` 114 | 115 | ### Automatic sign-in 116 | 117 | > `auto_select` prop `true` 118 | 119 | ```jsx 120 | 121 | 125 | 126 | useGoogleOneTapLogin({ 127 | ... 128 | auto_select 129 | }); 130 | ``` 131 | 132 | ### Custom login button (implicit & authorization code flow) 133 | 134 | #### Implicit flow 135 | 136 | ```jsx 137 | import { useGoogleLogin } from '@react-oauth/google'; 138 | 139 | const login = useGoogleLogin({ 140 | onSuccess: tokenResponse => console.log(tokenResponse), 141 | }); 142 | 143 | login()}>Sign in with Google 🚀; 144 | ``` 145 | 146 | #### Authorization code flow 147 | 148 | > Requires backend to exchange code with access and refresh token. 149 | 150 | ```jsx 151 | import { useGoogleLogin } from '@react-oauth/google'; 152 | 153 | const login = useGoogleLogin({ 154 | onSuccess: codeResponse => console.log(codeResponse), 155 | flow: 'auth-code', 156 | }); 157 | 158 | login()}>Sign in with Google 🚀; 159 | ``` 160 | 161 | #### Checks if the user granted all the specified scope or scopes 162 | 163 | ```jsx 164 | import { hasGrantedAllScopesGoogle } from '@react-oauth/google'; 165 | 166 | const hasAccess = hasGrantedAllScopesGoogle( 167 | tokenResponse, 168 | 'google-scope-1', 169 | 'google-scope-2', 170 | ); 171 | ``` 172 | 173 | #### Checks if the user granted any of the specified scope or scopes 174 | 175 | ```jsx 176 | import { hasGrantedAnyScopeGoogle } from '@react-oauth/google'; 177 | 178 | const hasAccess = hasGrantedAnyScopeGoogle( 179 | tokenResponse, 180 | 'google-scope-1', 181 | 'google-scope-2', 182 | ); 183 | ``` 184 | 185 | #### [Content Security Policy (if needed)](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy) 186 | 187 | ## API 188 | 189 | ### GoogleOAuthProvider 190 | 191 | | Required | Prop | Type | Description | 192 | | :------: | ------------------- | ---------- | --------------------------------------------------------------------------- | 193 | | ✓ | clientId | `string` | [**Google API client ID**](https://console.cloud.google.com/apis/dashboard) | 194 | | | nonce | `string` | Nonce applied to GSI script tag. Propagates to GSI's inline style tag | 195 | | | onScriptLoadSuccess | `function` | Callback fires on load gsi script success | 196 | | | onScriptLoadError | `function` | Callback fires on load gsi script failure | 197 | 198 | ### GoogleLogin 199 | 200 | | Required | Prop | Type | Description | 201 | | :------: | ---------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 202 | | ✓ | onSuccess | `(response: CredentialResponse) => void` | Callback fires with credential response after successfully login | 203 | | | onError | `function` | Callback fires after login failure | 204 | | | type | `standard` \| `icon` | Button type [type](https://developers.google.com/identity/gsi/web/reference/js-reference#type) | 205 | | | theme | `outline` \| `filled_blue` \| `filled_black` | Button [theme](https://developers.google.com/identity/gsi/web/reference/js-reference#theme) | 206 | | | size | `large` \| `medium` \| `small` | Button [size](https://developers.google.com/identity/gsi/web/reference/js-reference#size) | 207 | | | text | `signin_with` \| `signup_with` \| `continue_with` \| `signin` | Button [text](https://developers.google.com/identity/gsi/web/reference/js-reference#text). For example, "Sign in with Google", "Sign up with Google" or "Sign in" | 208 | | | shape | `rectangular` \| `pill` \| `circle` \| `square` | Button [shape](https://developers.google.com/identity/gsi/web/reference/js-reference#shape) | 209 | | | logo_alignment | `left` \| `center` | Google [logo alignment](https://developers.google.com/identity/gsi/web/reference/js-reference#logo_alignment) | 210 | | | width | `string` | button [width](https://developers.google.com/identity/gsi/web/reference/js-reference#width), in pixels | 211 | | | locale | `string` | If set, then the button [language](https://developers.google.com/identity/gsi/web/reference/js-reference#locale) is rendered | 212 | | | useOneTap | `boolean` | Activate One-tap sign-up or not | 213 | | | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description | 214 | | | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt | 215 | | | auto_select | `boolean` | Enables automatic selection on Google One Tap | 216 | | | ux_mode | `popup` \| `redirect` | The Sign In With Google button UX flow | 217 | | | login_uri | `string` | The URL of your login endpoint | 218 | | | native_login_uri | `string` | The URL of your password credential handler endpoint | 219 | | | native_callback | `(response: { id: string; password: string }) => void` | The JavaScript password credential handler function name | 220 | | | prompt_parent_id | `string` | The DOM ID of the One Tap prompt container element | 221 | | | nonce | `string` | A random string for ID tokens | 222 | | | context | `signin` \| `signup` \| `use` | The title and words in the One Tap prompt | 223 | | | state_cookie_domain | `string` | If you need to call One Tap in the parent domain and its subdomains, pass the parent domain to this attribute so that a single shared cookie is used | 224 | | | allowed_parent_origin | `string` \| `string[]` | The origins that are allowed to embed the intermediate iframe. One Tap will run in the intermediate iframe mode if this attribute presents | 225 | | | intermediate_iframe_close_callback | `function` | Overrides the default intermediate iframe behavior when users manually close One Tap | 226 | | | itp_support | `boolean` | Enables upgraded One Tap UX on ITP browsers | 227 | | | 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 | 228 | | | 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. | 229 | | | use_fedcm_for_button | `boolean` | Enable FedCM Button flow. | 230 | 231 | ### useGoogleLogin (Both implicit & authorization code flow) 232 | 233 | | Required | Prop | Type | Description | 234 | | :------: | --------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 235 | | | flow | `implicit` \| `auth-code` | [Two flows](https://developers.google.com/identity/oauth2/web/guides/how-user-authz-works), implicit and authorization code are discussed. Both return an access token suitable for use with Google APIs | 236 | | | onSuccess | `(response: TokenResponse\|CodeResponse) => void` | Callback fires with response ([token](https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenResponse) \| [code](https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeResponse)) based on flow selected after successfully login | 237 | | | onError | `(errorResponse: {error: string; error_description?: string,error_uri?: string}) => void` | Callback fires after login failure | 238 | | | onNonOAuthError | `(nonOAuthError: NonOAuthError) => void` | Some non-OAuth errors, such as the popup window is failed to open or closed before an OAuth response is returned. `popup_failed_to_open` \| `popup_closed` \| `unknown` | 239 | | | scope | `string` | A space-delimited list of scopes that are approved by the user | 240 | | | enable_serial_consent | `boolean` | defaults to true. If set to false, [more granular Google Account permissions](https://developers.googleblog.com/2018/10/more-granular-google-account.html) will be disabled for clients created before 2019. No effect for newer clients, since more granular permissions is always enabled for them. | 241 | | | hint | `string` | If your application knows which user should authorize the request, it can use this property to provide a hint to Google. The email address for the target user. For more information, see the [login_hint](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs | 242 | | | 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 | 243 | 244 | ### useGoogleLogin (Extra implicit flow props) 245 | 246 | | Required | Prop | Type | Description | 247 | | :------: | ------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 248 | | | prompt | `''` \| `none` \| `consent` \| `select_account` | defaults to 'select_account'. A space-delimited, case-sensitive list of prompts to present the user | 249 | | | state | `string` | Not recommended. Specifies any string value that your application uses to maintain state between your authorization request and the authorization server's response | 250 | 251 | ### useGoogleLogin (Extra authorization code flow props) 252 | 253 | | Required | Prop | Type | Description | 254 | | :------: | -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 255 | | | ux_mode | `popup` \| `redirect` | The UX mode to use for the authorization flow. By default, it will open the consent flow in a popup. Valid values are popup and redirect | 256 | | | redirect_uri | `string` | Required for redirect UX. Determines where the API server redirects the user after the user completes the authorization flow The value must exactly match one of the authorized redirect URIs for the OAuth 2.0 client which you configured in the API Console and must conform to our [Redirect URI validation](https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation) rules. The property will be ignored by the popup UX | 257 | | | state | `string` | Recommended for redirect UX. Specifies any string value that your application uses to maintain state between your authorization request and the authorization server's response | 258 | | | select_account | `boolean` | defaults to 'false'. Boolean value to prompt the user to select an account | 259 | 260 | ### useGoogleOneTapLogin 261 | 262 | | Required | Prop | Type | Description | 263 | | :------: | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 264 | | ✓ | onSuccess | `(response: CredentialResponse) => void` | Callback fires with credential response after successfully login | 265 | | | onError | `function` | Callback fires after login failure | 266 | | | promptMomentNotification | `(notification: PromptMomentNotification) => void` | [PromptMomentNotification](https://developers.google.com/identity/gsi/web/reference/js-reference) methods and description | 267 | | | cancel_on_tap_outside | `boolean` | Controls whether to cancel the prompt if the user clicks outside of the prompt | 268 | | | 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 | 269 | | | disabled | `boolean` | Controls whether to cancel the popup in cases such as when the user is already logged in | 270 | | | 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. | 271 | | | use_fedcm_for_button | `boolean` | Enable FedCM Button flow. | 272 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-oauth/google", 3 | "version": "0.12.2", 4 | "description": "Google OAuth2 using Google Identity Services for React 🚀", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.esm.js", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "license": "MIT", 12 | "scripts": { 13 | "dev": "rollup -c -w", 14 | "build": "rollup -c", 15 | "prerelease": "npm run build" 16 | }, 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "author": { 21 | "name": "Mo'men Sherif", 22 | "email": "momensherif.2019@gmail.com", 23 | "url": "https://github.com/MomenSherif" 24 | }, 25 | "homepage": "https://github.com/MomenSherif/react-oauth", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/MomenSherif/react-oauth" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/MomenSherif/react-oauth/issues", 32 | "email": "momensherif.2019@gmail.com" 33 | }, 34 | "keywords": [ 35 | "react", 36 | "reactjs", 37 | "react-component", 38 | "react-oauth-google", 39 | "react-google-login", 40 | "react-social-login", 41 | "react-oauth", 42 | "react-login", 43 | "google-login", 44 | "google-oAuth2", 45 | "google-oAuth" 46 | ], 47 | "devDependencies": { 48 | "@types/react": "^18.0.8", 49 | "cross-env": "^7.0.3", 50 | "rollup": "^2.71.1", 51 | "typescript": "^4.6.4" 52 | }, 53 | "peerDependencies": { 54 | "react": ">=16.8.0", 55 | "react-dom": ">=16.8.0" 56 | }, 57 | "config": { 58 | "commitizen": { 59 | "path": "cz-conventional-changelog" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup'; 2 | import rollupConfigGenerator from 'rollup-config-generator'; 3 | import packageJson from './package.json'; 4 | 5 | export default defineConfig(rollupConfigGenerator(packageJson)); 6 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/GoogleLogin.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | 3 | import { useGoogleOAuth } from './GoogleOAuthProvider'; 4 | import { extractClientId } from './utils'; 5 | import { 6 | IdConfiguration, 7 | CredentialResponse, 8 | GoogleCredentialResponse, 9 | MomentListener, 10 | GsiButtonConfiguration, 11 | } from './types'; 12 | 13 | const containerHeightMap = { large: 40, medium: 32, small: 20 }; 14 | 15 | export type GoogleLoginProps = { 16 | onSuccess: (credentialResponse: CredentialResponse) => void; 17 | onError?: () => void; 18 | promptMomentNotification?: MomentListener; 19 | useOneTap?: boolean; 20 | containerProps?: React.ComponentPropsWithoutRef<'div'>; 21 | } & Omit & 22 | GsiButtonConfiguration; 23 | 24 | export default function GoogleLogin({ 25 | onSuccess, 26 | onError, 27 | useOneTap, 28 | promptMomentNotification, 29 | type = 'standard', 30 | theme = 'outline', 31 | size = 'large', 32 | text, 33 | shape, 34 | logo_alignment, 35 | width, 36 | locale, 37 | click_listener, 38 | containerProps, 39 | ...props 40 | }: GoogleLoginProps) { 41 | const btnContainerRef = useRef(null); 42 | const { clientId, scriptLoadedSuccessfully } = useGoogleOAuth(); 43 | 44 | const onSuccessRef = useRef(onSuccess); 45 | onSuccessRef.current = onSuccess; 46 | 47 | const onErrorRef = useRef(onError); 48 | onErrorRef.current = onError; 49 | 50 | const promptMomentNotificationRef = useRef(promptMomentNotification); 51 | promptMomentNotificationRef.current = promptMomentNotification; 52 | 53 | useEffect(() => { 54 | if (!scriptLoadedSuccessfully) return; 55 | 56 | window?.google?.accounts?.id?.initialize({ 57 | client_id: clientId, 58 | callback: (credentialResponse: GoogleCredentialResponse) => { 59 | if (!credentialResponse?.credential) { 60 | return onErrorRef.current?.(); 61 | } 62 | 63 | const { credential, select_by } = credentialResponse; 64 | onSuccessRef.current({ 65 | credential, 66 | clientId: extractClientId(credentialResponse), 67 | select_by, 68 | }); 69 | }, 70 | ...props, 71 | }); 72 | 73 | window?.google?.accounts?.id?.renderButton(btnContainerRef.current!, { 74 | type, 75 | theme, 76 | size, 77 | text, 78 | shape, 79 | logo_alignment, 80 | width, 81 | locale, 82 | click_listener, 83 | }); 84 | 85 | if (useOneTap) 86 | window?.google?.accounts?.id?.prompt(promptMomentNotificationRef.current); 87 | 88 | return () => { 89 | if (useOneTap) window?.google?.accounts?.id?.cancel(); 90 | }; 91 | // eslint-disable-next-line react-hooks/exhaustive-deps 92 | }, [ 93 | clientId, 94 | scriptLoadedSuccessfully, 95 | useOneTap, 96 | type, 97 | theme, 98 | size, 99 | text, 100 | shape, 101 | logo_alignment, 102 | width, 103 | locale, 104 | ]); 105 | 106 | return ( 107 |
112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/GoogleOAuthProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, createContext, useMemo, ReactNode } from 'react'; 2 | 3 | import useLoadGsiScript, { 4 | UseLoadGsiScriptOptions, 5 | } from './hooks/useLoadGsiScript'; 6 | 7 | interface GoogleOAuthContextProps { 8 | clientId: string; 9 | scriptLoadedSuccessfully: boolean; 10 | } 11 | 12 | const GoogleOAuthContext = createContext(null!); 13 | 14 | interface GoogleOAuthProviderProps extends UseLoadGsiScriptOptions { 15 | clientId: string; 16 | children: ReactNode; 17 | } 18 | 19 | export default function GoogleOAuthProvider({ 20 | clientId, 21 | nonce, 22 | onScriptLoadSuccess, 23 | onScriptLoadError, 24 | children, 25 | }: GoogleOAuthProviderProps) { 26 | const scriptLoadedSuccessfully = useLoadGsiScript({ 27 | nonce, 28 | onScriptLoadSuccess, 29 | onScriptLoadError, 30 | }); 31 | 32 | const contextValue = useMemo( 33 | () => ({ 34 | clientId, 35 | scriptLoadedSuccessfully, 36 | }), 37 | [clientId, scriptLoadedSuccessfully], 38 | ); 39 | 40 | return ( 41 | 42 | {children} 43 | 44 | ); 45 | } 46 | 47 | export function useGoogleOAuth() { 48 | const context = useContext(GoogleOAuthContext); 49 | if (!context) { 50 | throw new Error( 51 | 'Google OAuth components must be used within GoogleOAuthProvider', 52 | ); 53 | } 54 | return context; 55 | } 56 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/google-auth-window.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IdConfiguration, 3 | MomentListener, 4 | GsiButtonConfiguration, 5 | TokenClientConfig, 6 | OverridableTokenClientConfig, 7 | CodeClientConfig, 8 | TokenResponse, 9 | } from './types'; 10 | declare global { 11 | interface Window { 12 | google?: { 13 | accounts: { 14 | id: { 15 | initialize: (input: IdConfiguration) => void; 16 | prompt: (momentListener?: MomentListener) => void; 17 | renderButton: ( 18 | parent: HTMLElement, 19 | options: GsiButtonConfiguration, 20 | ) => void; 21 | disableAutoSelect: () => void; 22 | storeCredential: ( 23 | credential: { id: string; password: string }, 24 | callback?: () => void, 25 | ) => void; 26 | cancel: () => void; 27 | onGoogleLibraryLoad: Function; 28 | revoke: (accessToken: string, done: () => void) => void; 29 | }; 30 | oauth2: { 31 | initTokenClient: (config: TokenClientConfig) => { 32 | requestAccessToken: ( 33 | overridableClientConfig?: OverridableTokenClientConfig, 34 | ) => void; 35 | }; 36 | initCodeClient: (config: CodeClientConfig) => { 37 | requestCode: () => void; 38 | }; 39 | hasGrantedAnyScope: ( 40 | tokenResponse: TokenResponse, 41 | firstScope: string, 42 | ...restScopes: string[] 43 | ) => boolean; 44 | hasGrantedAllScopes: ( 45 | tokenResponse: TokenResponse, 46 | firstScope: string, 47 | ...restScopes: string[] 48 | ) => boolean; 49 | revoke: (accessToken: string, done?: () => void) => void; 50 | }; 51 | }; 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/googleLogout.ts: -------------------------------------------------------------------------------- 1 | export default function googleLogout() { 2 | window?.google?.accounts?.id?.disableAutoSelect(); 3 | } 4 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/hasGrantedAllScopesGoogle.ts: -------------------------------------------------------------------------------- 1 | import { TokenResponse } from './types'; 2 | 3 | /** 4 | * Checks if the user granted all the specified scope or scopes 5 | * @returns True if all the scopes are granted 6 | */ 7 | export default function hasGrantedAllScopesGoogle( 8 | tokenResponse: TokenResponse, 9 | firstScope: string, 10 | ...restScopes: string[] 11 | ): boolean { 12 | if (!window?.google) return false; 13 | 14 | return ( 15 | window?.google?.accounts?.oauth2?.hasGrantedAllScopes( 16 | tokenResponse, 17 | firstScope, 18 | ...restScopes, 19 | ) || false 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/hasGrantedAnyScopeGoogle.ts: -------------------------------------------------------------------------------- 1 | import { TokenResponse } from './types'; 2 | 3 | /** 4 | * Checks if the user granted any of the specified scope or scopes. 5 | * @returns True if any of the scopes are granted 6 | */ 7 | export default function hasGrantedAnyScopeGoogle( 8 | tokenResponse: TokenResponse, 9 | firstScope: string, 10 | ...restScopes: string[] 11 | ): boolean { 12 | if (!window?.google) return false; 13 | 14 | return ( 15 | window?.google?.accounts?.oauth2?.hasGrantedAnyScope( 16 | tokenResponse, 17 | firstScope, 18 | ...restScopes, 19 | ) || false 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/hooks/useGoogleLogin.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/export */ 2 | import { useCallback, useEffect, useRef } from 'react'; 3 | 4 | import { useGoogleOAuth } from '../GoogleOAuthProvider'; 5 | import { 6 | TokenClientConfig, 7 | TokenResponse, 8 | CodeClientConfig, 9 | CodeResponse, 10 | OverridableTokenClientConfig, 11 | NonOAuthError, 12 | } from '../types'; 13 | 14 | interface ImplicitFlowOptions 15 | extends Omit { 16 | onSuccess?: ( 17 | tokenResponse: Omit< 18 | TokenResponse, 19 | 'error' | 'error_description' | 'error_uri' 20 | >, 21 | ) => void; 22 | onError?: ( 23 | errorResponse: Pick< 24 | TokenResponse, 25 | 'error' | 'error_description' | 'error_uri' 26 | >, 27 | ) => void; 28 | onNonOAuthError?: (nonOAuthError: NonOAuthError) => void; 29 | scope?: TokenClientConfig['scope']; 30 | overrideScope?: boolean; 31 | } 32 | 33 | interface AuthCodeFlowOptions 34 | extends Omit { 35 | onSuccess?: ( 36 | codeResponse: Omit< 37 | CodeResponse, 38 | 'error' | 'error_description' | 'error_uri' 39 | >, 40 | ) => void; 41 | onError?: ( 42 | errorResponse: Pick< 43 | CodeResponse, 44 | 'error' | 'error_description' | 'error_uri' 45 | >, 46 | ) => void; 47 | onNonOAuthError?: (nonOAuthError: NonOAuthError) => void; 48 | scope?: CodeResponse['scope']; 49 | overrideScope?: boolean; 50 | } 51 | 52 | export type UseGoogleLoginOptionsImplicitFlow = { 53 | flow?: 'implicit'; 54 | } & ImplicitFlowOptions; 55 | 56 | export type UseGoogleLoginOptionsAuthCodeFlow = { 57 | flow?: 'auth-code'; 58 | } & AuthCodeFlowOptions; 59 | 60 | export type UseGoogleLoginOptions = 61 | | UseGoogleLoginOptionsImplicitFlow 62 | | UseGoogleLoginOptionsAuthCodeFlow; 63 | 64 | export default function useGoogleLogin( 65 | options: UseGoogleLoginOptionsImplicitFlow, 66 | ): (overrideConfig?: OverridableTokenClientConfig) => void; 67 | export default function useGoogleLogin( 68 | options: UseGoogleLoginOptionsAuthCodeFlow, 69 | ): () => void; 70 | 71 | export default function useGoogleLogin({ 72 | flow = 'implicit', 73 | scope = '', 74 | onSuccess, 75 | onError, 76 | onNonOAuthError, 77 | overrideScope, 78 | state, 79 | ...props 80 | }: UseGoogleLoginOptions): unknown { 81 | const { clientId, scriptLoadedSuccessfully } = useGoogleOAuth(); 82 | const clientRef = useRef(); 83 | 84 | const onSuccessRef = useRef(onSuccess); 85 | onSuccessRef.current = onSuccess; 86 | 87 | const onErrorRef = useRef(onError); 88 | onErrorRef.current = onError; 89 | 90 | const onNonOAuthErrorRef = useRef(onNonOAuthError); 91 | onNonOAuthErrorRef.current = onNonOAuthError; 92 | 93 | useEffect(() => { 94 | if (!scriptLoadedSuccessfully) return; 95 | 96 | const clientMethod = 97 | flow === 'implicit' ? 'initTokenClient' : 'initCodeClient'; 98 | 99 | const client = window?.google?.accounts?.oauth2[clientMethod]({ 100 | client_id: clientId, 101 | scope: overrideScope ? scope : `openid profile email ${scope}`, 102 | callback: (response: TokenResponse | CodeResponse) => { 103 | if (response.error) return onErrorRef.current?.(response); 104 | 105 | onSuccessRef.current?.(response as any); 106 | }, 107 | error_callback: (nonOAuthError: NonOAuthError) => { 108 | onNonOAuthErrorRef.current?.(nonOAuthError); 109 | }, 110 | state, 111 | ...props, 112 | }); 113 | 114 | clientRef.current = client; 115 | // eslint-disable-next-line react-hooks/exhaustive-deps 116 | }, [clientId, scriptLoadedSuccessfully, flow, scope, state]); 117 | 118 | const loginImplicitFlow = useCallback( 119 | (overrideConfig?: OverridableTokenClientConfig) => 120 | clientRef.current?.requestAccessToken(overrideConfig), 121 | [], 122 | ); 123 | 124 | const loginAuthCodeFlow = useCallback( 125 | () => clientRef.current?.requestCode(), 126 | [], 127 | ); 128 | 129 | return flow === 'implicit' ? loginImplicitFlow : loginAuthCodeFlow; 130 | } 131 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/hooks/useGoogleOneTapLogin.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | import { useGoogleOAuth } from '../GoogleOAuthProvider'; 4 | import type { 5 | CredentialResponse, 6 | GoogleCredentialResponse, 7 | IdConfiguration, 8 | MomentListener, 9 | } from '../types'; 10 | import { extractClientId } from '../utils'; 11 | 12 | interface UseGoogleOneTapLoginOptions { 13 | onSuccess: (credentialResponse: CredentialResponse) => void; 14 | onError?: () => void; 15 | promptMomentNotification?: MomentListener; 16 | cancel_on_tap_outside?: boolean; 17 | prompt_parent_id?: string; 18 | state_cookie_domain?: string; 19 | hosted_domain?: string; 20 | disabled?: boolean; 21 | use_fedcm_for_prompt?: IdConfiguration['use_fedcm_for_prompt']; 22 | use_fedcm_for_button?: IdConfiguration['use_fedcm_for_button']; 23 | auto_select?: boolean; 24 | } 25 | 26 | export default function useGoogleOneTapLogin({ 27 | onSuccess, 28 | onError, 29 | promptMomentNotification, 30 | cancel_on_tap_outside, 31 | prompt_parent_id, 32 | state_cookie_domain, 33 | hosted_domain, 34 | use_fedcm_for_prompt = false, 35 | use_fedcm_for_button = false, 36 | disabled, 37 | auto_select, 38 | }: UseGoogleOneTapLoginOptions): void { 39 | const { clientId, scriptLoadedSuccessfully } = useGoogleOAuth(); 40 | 41 | const onSuccessRef = useRef(onSuccess); 42 | onSuccessRef.current = onSuccess; 43 | 44 | const onErrorRef = useRef(onError); 45 | onErrorRef.current = onError; 46 | 47 | const promptMomentNotificationRef = useRef(promptMomentNotification); 48 | promptMomentNotificationRef.current = promptMomentNotification; 49 | 50 | useEffect(() => { 51 | if (!scriptLoadedSuccessfully) return; 52 | 53 | if (disabled) { 54 | window?.google?.accounts?.id?.cancel(); 55 | return; 56 | } 57 | 58 | window?.google?.accounts?.id?.initialize({ 59 | client_id: clientId, 60 | callback: (credentialResponse: GoogleCredentialResponse) => { 61 | if (!credentialResponse?.credential) { 62 | return onErrorRef.current?.(); 63 | } 64 | 65 | const { credential, select_by } = credentialResponse; 66 | onSuccessRef.current({ 67 | credential, 68 | clientId: extractClientId(credentialResponse), 69 | select_by, 70 | }); 71 | }, 72 | hosted_domain, 73 | cancel_on_tap_outside, 74 | prompt_parent_id, 75 | state_cookie_domain, 76 | use_fedcm_for_prompt, 77 | use_fedcm_for_button, 78 | auto_select, 79 | }); 80 | 81 | window?.google?.accounts?.id?.prompt(promptMomentNotificationRef.current); 82 | 83 | return () => { 84 | window?.google?.accounts?.id?.cancel(); 85 | }; 86 | }, [ 87 | clientId, 88 | scriptLoadedSuccessfully, 89 | cancel_on_tap_outside, 90 | prompt_parent_id, 91 | state_cookie_domain, 92 | hosted_domain, 93 | use_fedcm_for_prompt, 94 | use_fedcm_for_button, 95 | disabled, 96 | auto_select, 97 | ]); 98 | } 99 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/hooks/useLoadGsiScript.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from 'react'; 2 | 3 | export interface UseLoadGsiScriptOptions { 4 | /** 5 | * Nonce applied to GSI script tag. Propagates to GSI's inline style tag 6 | */ 7 | nonce?: string; 8 | /** 9 | * Callback fires on load [gsi](https://accounts.google.com/gsi/client) script success 10 | */ 11 | onScriptLoadSuccess?: () => void; 12 | /** 13 | * Callback fires on load [gsi](https://accounts.google.com/gsi/client) script failure 14 | */ 15 | onScriptLoadError?: () => void; 16 | } 17 | 18 | export default function useLoadGsiScript( 19 | options: UseLoadGsiScriptOptions = {}, 20 | ): boolean { 21 | const { nonce, onScriptLoadSuccess, onScriptLoadError } = options; 22 | 23 | const [scriptLoadedSuccessfully, setScriptLoadedSuccessfully] = 24 | useState(false); 25 | 26 | const onScriptLoadSuccessRef = useRef(onScriptLoadSuccess); 27 | onScriptLoadSuccessRef.current = onScriptLoadSuccess; 28 | 29 | const onScriptLoadErrorRef = useRef(onScriptLoadError); 30 | onScriptLoadErrorRef.current = onScriptLoadError; 31 | 32 | useEffect(() => { 33 | const scriptTag = document.createElement('script'); 34 | scriptTag.src = 'https://accounts.google.com/gsi/client'; 35 | scriptTag.async = true; 36 | scriptTag.defer = true; 37 | scriptTag.nonce = nonce; 38 | scriptTag.onload = () => { 39 | setScriptLoadedSuccessfully(true); 40 | onScriptLoadSuccessRef.current?.(); 41 | }; 42 | scriptTag.onerror = () => { 43 | setScriptLoadedSuccessfully(false); 44 | onScriptLoadErrorRef.current?.(); 45 | }; 46 | 47 | document.body.appendChild(scriptTag); 48 | 49 | return () => { 50 | document.body.removeChild(scriptTag); 51 | }; 52 | }, [nonce]); 53 | 54 | return scriptLoadedSuccessfully; 55 | } 56 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as GoogleOAuthProvider, 3 | useGoogleOAuth, 4 | } from './GoogleOAuthProvider'; 5 | export { default as GoogleLogin } from './GoogleLogin'; 6 | export type { GoogleLoginProps } from './GoogleLogin'; 7 | export { default as googleLogout } from './googleLogout'; 8 | export { default as useGoogleLogin } from './hooks/useGoogleLogin'; 9 | export type { 10 | UseGoogleLoginOptions, 11 | UseGoogleLoginOptionsAuthCodeFlow, 12 | UseGoogleLoginOptionsImplicitFlow, 13 | } from './hooks/useGoogleLogin'; 14 | export { default as useGoogleOneTapLogin } from './hooks/useGoogleOneTapLogin'; 15 | export { default as hasGrantedAllScopesGoogle } from './hasGrantedAllScopesGoogle'; 16 | export { default as hasGrantedAnyScopeGoogle } from './hasGrantedAnyScopeGoogle'; 17 | export * from './types'; 18 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type Context = 'signin' | 'signup' | 'use'; 2 | 3 | export type UxMode = 'popup' | 'redirect'; 4 | 5 | export type ErrorCode = 6 | | 'invalid_request' 7 | | 'access_denied' 8 | | 'unauthorized_client' 9 | | 'unsupported_response_type' 10 | | 'invalid_scope' 11 | | 'server_error' 12 | | 'temporarily_unavailable'; 13 | 14 | export interface IdConfiguration { 15 | /** Your application's client ID */ 16 | client_id?: string; 17 | /** Enables automatic selection on Google One Tap */ 18 | auto_select?: boolean; 19 | /** ID token callback handler */ 20 | callback?: (credentialResponse: CredentialResponse) => void; 21 | /** The Sign In With Google button UX flow */ 22 | ux_mode?: UxMode; 23 | /** The URL of your login endpoint */ 24 | login_uri?: string; 25 | /** The URL of your password credential handler endpoint */ 26 | native_login_uri?: string; 27 | /** The JavaScript password credential handler function name */ 28 | native_callback?: (response: { id: string; password: string }) => void; 29 | /** Controls whether to cancel the prompt if the user clicks outside of the prompt */ 30 | cancel_on_tap_outside?: boolean; 31 | /** The DOM ID of the One Tap prompt container element */ 32 | prompt_parent_id?: string; 33 | /** A random string for ID tokens */ 34 | nonce?: string; 35 | /** The title and words in the One Tap prompt */ 36 | context?: Context; 37 | /** If you need to call One Tap in the parent domain and its subdomains, pass the parent domain to this attribute so that a single shared cookie is used. */ 38 | state_cookie_domain?: string; 39 | /** The origins that are allowed to embed the intermediate iframe. One Tap will run in the intermediate iframe mode if this attribute presents */ 40 | allowed_parent_origin?: string | string[]; 41 | /** Overrides the default intermediate iframe behavior when users manually close One Tap */ 42 | intermediate_iframe_close_callback?: () => void; 43 | /** Enables upgraded One Tap UX on ITP browsers */ 44 | itp_support?: boolean; 45 | /** 46 | * If your application knows the Workspace domain the user belongs to, 47 | * use this to provide a hint to Google. For more information, 48 | * see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) 49 | * field in the OpenID Connect docs. 50 | */ 51 | hosted_domain?: string; 52 | /** 53 | * Allow the browser to control user sign-in prompts and mediate the sign-in flow between your website and Google. 54 | * @default false 55 | */ 56 | use_fedcm_for_prompt?: boolean; 57 | /** 58 | * Enable FedCM Button flow 59 | * @default false 60 | */ 61 | use_fedcm_for_button?: boolean; 62 | } 63 | 64 | export interface CredentialResponse { 65 | /** This field is the returned ID token */ 66 | credential?: string; 67 | /** This field sets how the credential is selected */ 68 | select_by?: 69 | | 'auto' 70 | | 'user' 71 | | 'user_1tap' 72 | | 'user_2tap' 73 | | 'btn' 74 | | 'btn_confirm' 75 | | 'btn_add_session' 76 | | 'btn_confirm_add_session'; 77 | clientId?: string; 78 | } 79 | 80 | export interface GoogleCredentialResponse extends CredentialResponse { 81 | client_id?: string; 82 | } 83 | 84 | export interface GsiButtonConfiguration { 85 | /** The button [type](https://developers.google.com/identity/gsi/web/reference/js-reference#type): icon, or standard button */ 86 | type?: 'standard' | 'icon'; 87 | /** The button [theme](https://developers.google.com/identity/gsi/web/reference/js-reference#theme). For example, filled_blue or filled_black */ 88 | theme?: 'outline' | 'filled_blue' | 'filled_black'; 89 | /** The button [size](https://developers.google.com/identity/gsi/web/reference/js-reference#size). For example, small or large */ 90 | size?: 'large' | 'medium' | 'small'; 91 | /** The button [text](https://developers.google.com/identity/gsi/web/reference/js-reference#text). For example, "Sign in with Google" or "Sign up with Google" */ 92 | text?: 'signin_with' | 'signup_with' | 'continue_with' | 'signin'; 93 | /** The button [shape](https://developers.google.com/identity/gsi/web/reference/js-reference#shape). For example, rectangular or circular */ 94 | shape?: 'rectangular' | 'pill' | 'circle' | 'square'; 95 | /** The Google [logo alignment](https://developers.google.com/identity/gsi/web/reference/js-reference#logo_alignment): left or center */ 96 | logo_alignment?: 'left' | 'center'; 97 | /** The button [width](https://developers.google.com/identity/gsi/web/reference/js-reference#width), in pixels */ 98 | width?: string | number; 99 | /** If set, then the button [language](https://developers.google.com/identity/gsi/web/reference/js-reference#locale) is rendered */ 100 | locale?: string; 101 | /** If set, this [function](https://developers.google.com/identity/gsi/web/reference/js-reference#click_listener) will be called when the Sign in with Google button is clicked. */ 102 | click_listener?: () => void; 103 | } 104 | 105 | export interface PromptMomentNotification { 106 | /** Is this notification for a display moment? */ 107 | isDisplayMoment: () => boolean; 108 | /** Is this notification for a display moment, and the UI is displayed? */ 109 | isDisplayed: () => boolean; 110 | /** Is this notification for a display moment, and the UI isn't displayed? */ 111 | isNotDisplayed: () => boolean; 112 | /** 113 | * The detailed reason why the UI isn't displayed 114 | * Avoid using `opt_out_or_no_session`. When FedCM is enabled, 115 | * this value is not supported. See Migrate to FedCM page for more information. 116 | * */ 117 | getNotDisplayedReason: () => 118 | | 'browser_not_supported' 119 | | 'invalid_client' 120 | | 'missing_client_id' 121 | | 'opt_out_or_no_session' 122 | | 'secure_http_required' 123 | | 'suppressed_by_user' 124 | | 'unregistered_origin' 125 | | 'unknown_reason'; 126 | /** Is this notification for a skipped moment? */ 127 | isSkippedMoment: () => boolean; 128 | /** The detailed reason for the skipped moment */ 129 | getSkippedReason: () => 130 | | 'auto_cancel' 131 | | 'user_cancel' 132 | | 'tap_outside' 133 | | 'issuing_failed'; 134 | /** Is this notification for a dismissed moment? */ 135 | isDismissedMoment: () => boolean; 136 | /** The detailed reason for the dismissa */ 137 | getDismissedReason: () => 138 | | 'credential_returned' 139 | | 'cancel_called' 140 | | 'flow_restarted'; 141 | /** Return a string for the moment type */ 142 | getMomentType: () => 'display' | 'skipped' | 'dismissed'; 143 | } 144 | 145 | export interface TokenResponse { 146 | /** The access token of a successful token response. */ 147 | access_token: string; 148 | 149 | /** The lifetime in seconds of the access token. */ 150 | expires_in: number; 151 | 152 | /** The hosted domain the signed-in user belongs to. */ 153 | hd?: string; 154 | 155 | /** The prompt value that was used from the possible list of values specified by TokenClientConfig or OverridableTokenClientConfig */ 156 | prompt: string; 157 | 158 | /** The type of the token issued. */ 159 | token_type: string; 160 | 161 | /** A space-delimited list of scopes that are approved by the user. */ 162 | scope: string; 163 | 164 | /** The string value that your application uses to maintain state between your authorization request and the response. */ 165 | state?: string; 166 | 167 | /** A single ASCII error code. */ 168 | error?: ErrorCode; 169 | 170 | /** Human-readable ASCII text providing additional information, used to assist the client developer in understanding the error that occurred. */ 171 | error_description?: string; 172 | 173 | /** A URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error. */ 174 | error_uri?: string; 175 | } 176 | 177 | export type NonOAuthError = { 178 | /** 179 | * Some non-OAuth errors, such as the popup window is failed to open; 180 | * or closed before an OAuth response is returned. 181 | * https://developers.google.com/identity/oauth2/web/reference/js-reference#TokenClientConfig 182 | * https://developers.google.com/identity/oauth2/web/reference/js-reference#CodeClientConfig 183 | */ 184 | type: 'popup_failed_to_open' | 'popup_closed' | 'unknown'; 185 | }; 186 | export interface TokenClientConfig { 187 | /** 188 | * The client ID for your application. You can find this value in the 189 | * [API Console](https://console.cloud.google.com/apis/dashboard) 190 | */ 191 | client_id: string; 192 | 193 | /** 194 | * A space-delimited list of scopes that identify the resources 195 | * that your application could access on the user's behalf. 196 | * These values inform the consent screen that Google displays to the user 197 | */ 198 | scope: string; 199 | 200 | /** 201 | * Optional, defaults to true. Enables applications to use incremental authorization to 202 | * request access to additional scopes in context. If you set this parameter's value to 203 | * false and the authorization request is granted, then the new access token will only 204 | * cover any scopes to which the scope requested in this TokenClientConfig. 205 | */ 206 | include_granted_scopes?: boolean; 207 | 208 | /** 209 | * Required for popup UX. The JavaScript function name that handles returned code response 210 | * The property will be ignored by the redirect UX 211 | */ 212 | callback?: (response: TokenResponse) => void; 213 | 214 | /** 215 | * Optional. The JavaScript function that handles some non-OAuth errors, 216 | * such as the popup window is failed to open; or closed before an OAuth response is returned. 217 | */ 218 | error_callback?: (nonOAuthError: NonOAuthError) => void; 219 | 220 | /** 221 | * Optional, defaults to 'select_account'. A space-delimited, case-sensitive list of prompts to present the user 222 | */ 223 | prompt?: '' | 'none' | 'consent' | 'select_account'; 224 | 225 | /** 226 | * Optional, defaults to true. If set to false, 227 | * [more granular Google Account permissions](https://developers.googleblog.com/2018/10/more-granular-google-account.html) 228 | * will be disabled for clients created before 2019. No effect for newer clients, 229 | * since more granular permissions is always enabled for them. 230 | */ 231 | enable_serial_consent?: boolean; 232 | 233 | /** 234 | * Optional. If your application knows which user should authorize the request, 235 | * it can use this property to provide a hint to Google. 236 | * The email address for the target user. For more information, 237 | * see the [login_hint](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs. 238 | */ 239 | hint?: string; 240 | 241 | /** 242 | * Optional. If your application knows the Workspace domain the user belongs to, 243 | * use this to provide a hint to Google. For more information, 244 | * see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) 245 | * field in the OpenID Connect docs. 246 | */ 247 | hosted_domain?: string; 248 | 249 | /** 250 | * Optional. Not recommended. Specifies any string value that 251 | * your application uses to maintain state between your authorization 252 | * request and the authorization server's response. 253 | */ 254 | state?: string; 255 | } 256 | 257 | export interface OverridableTokenClientConfig { 258 | /** 259 | * Optional. A space-delimited, case-sensitive list of prompts to present the user. 260 | */ 261 | prompt?: '' | 'none' | 'consent' | 'select_account'; 262 | 263 | /** 264 | * Optional. If set to false, 265 | * [more granular Google Account permissions](https://developers.googleblog.com/2018/10/more-granular-google-account.html) 266 | * will be disabled for clients created before 2019. 267 | * No effect for newer clients, since more granular permissions is always enabled for them. 268 | */ 269 | enable_serial_consent?: boolean; 270 | 271 | /** 272 | * Optional. If your application knows which user should authorize the request, 273 | * it can use this property to provide a hint to Google. 274 | * The email address for the target user. For more information, 275 | * see the [login_hint](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs. 276 | */ 277 | hint?: string; 278 | 279 | /** 280 | * Optional. Not recommended. Specifies any string value that your 281 | * application uses to maintain state between your authorization request 282 | * and the authorization server's response. 283 | */ 284 | state?: string; 285 | } 286 | 287 | export interface CodeResponse { 288 | /** The authorization code of a successful token response */ 289 | code: string; 290 | /** A space-delimited list of scopes that are approved by the user */ 291 | scope: string; 292 | /** The string value that your application uses to maintain state between your authorization request and the response */ 293 | state?: string; 294 | /** A single ASCII error code */ 295 | error?: ErrorCode; 296 | /** Human-readable ASCII text providing additional information, used to assist the client developer in understanding the error that occurred */ 297 | error_description?: string; 298 | /** A URI identifying a human-readable web page with information about the error, used to provide the client developer with additional information about the error */ 299 | error_uri?: string; 300 | } 301 | 302 | export interface CodeClientConfig { 303 | /** 304 | * Required. The client ID for your application. You can find this value in the 305 | * [API Console](https://console.developers.google.com/) 306 | */ 307 | client_id: string; 308 | 309 | /** 310 | * Required. A space-delimited list of scopes that identify 311 | * the resources that your application could access on the user's behalf. 312 | * These values inform the consent screen that Google displays to the user 313 | */ 314 | scope: string; 315 | 316 | /** 317 | * Optional, defaults to true. Enables applications to use incremental authorization to 318 | * request access to additional scopes in context. If you set this parameter's value to 319 | * false and the authorization request is granted, then the new access token will only 320 | * cover any scopes to which the scope requested in this CodeClientConfig. 321 | */ 322 | include_granted_scopes?: boolean; 323 | 324 | /** 325 | * Required for redirect UX. Determines where the API server redirects 326 | * the user after the user completes the authorization flow. 327 | * The value must exactly match one of the authorized redirect URIs for the OAuth 2.0 client, 328 | * which you configured in the API Console and must conform to our 329 | * [Redirect URI validation](https://developers.google.com/identity/protocols/oauth2/web-server#uri-validation) rules. The property will be ignored by the popup UX 330 | */ 331 | redirect_uri?: string; 332 | 333 | /** 334 | * Required for popup UX. The JavaScript function name that handles 335 | * returned code response. The property will be ignored by the redirect UX 336 | */ 337 | callback?: (codeResponse: CodeResponse) => void; 338 | 339 | /** 340 | * Optional. Recommended for redirect UX. Specifies any string value that 341 | * your application uses to maintain state between your authorization request and the authorization server's response 342 | */ 343 | state?: string; 344 | 345 | /** 346 | * Optional, defaults to true. If set to false, 347 | * [more granular Google Account permissions](https://developers.googleblog.com/2018/10/more-granular-google-account.html) 348 | * will be disabled for clients created before 2019. No effect for newer clients, since 349 | * more granular permissions is always enabled for them 350 | */ 351 | enable_serial_consent?: boolean; 352 | 353 | /** 354 | * Optional. If your application knows which user should authorize the request, 355 | * it can use this property to provide a hint to Google. 356 | * The email address for the target user. For more information, 357 | * see the [login_hint](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs 358 | */ 359 | hint?: string; 360 | 361 | /** 362 | * Optional. If your application knows the Workspace domain 363 | * the user belongs to, use this to provide a hint to Google. 364 | * For more information, see the [hd](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) field in the OpenID Connect docs 365 | */ 366 | hosted_domain?: string; 367 | 368 | /** 369 | * Optional. The UX mode to use for the authorization flow. 370 | * By default, it will open the consent flow in a popup. Valid values are popup and redirect 371 | */ 372 | ux_mode?: 'popup' | 'redirect'; 373 | 374 | /** 375 | * Optional, defaults to 'false'. Boolean value to prompt the user to select an account 376 | */ 377 | select_account?: boolean; 378 | } 379 | 380 | export type MomentListener = ( 381 | promptMomentNotification: PromptMomentNotification, 382 | ) => void; 383 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { GoogleCredentialResponse } from '../types'; 2 | 3 | export function extractClientId( 4 | credentialResponse: GoogleCredentialResponse, 5 | ): string | undefined { 6 | const clientId = 7 | credentialResponse?.clientId ?? credentialResponse?.client_id; 8 | return clientId; 9 | } 10 | -------------------------------------------------------------------------------- /packages/@react-oauth/google/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/library.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "declarationDir": "dist" 6 | }, 7 | "include": ["src"], 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/rollup-config-generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # rollup-config-generator 2 | 3 | ## 0.0.1 4 | 5 | ### Patch Changes 6 | 7 | - [`d237197`](https://github.com/MomenSherif/react-oauth/commit/d237197ac898a041d4cdf3e458651b2ccc3545cc) Thanks [@MomenSherif](https://github.com/MomenSherif)! - add `use client` directive to support nextjs out of the box 8 | -------------------------------------------------------------------------------- /packages/rollup-config-generator/index.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('rollup'); 2 | const peerDepsExternal = require('rollup-plugin-peer-deps-external'); 3 | const { nodeResolve } = require('@rollup/plugin-node-resolve'); 4 | const commonjs = require('@rollup/plugin-commonjs'); 5 | const typescript = require('rollup-plugin-typescript2'); 6 | const { default: dts } = require('rollup-plugin-dts'); 7 | const del = require('rollup-plugin-delete'); 8 | const cleaner = require('rollup-plugin-cleaner'); 9 | 10 | const __PROD__ = process.env.NODE_ENV !== 'development'; 11 | 12 | module.exports = packageJson => 13 | [ 14 | { 15 | input: 'src/index.ts', 16 | output: [ 17 | { 18 | file: packageJson.main, 19 | format: 'cjs', 20 | banner: "'use client'", 21 | }, 22 | { 23 | file: packageJson.module, 24 | format: 'esm', 25 | banner: "'use client'", 26 | }, 27 | ], 28 | external: Object.keys(packageJson.dependencies || {}).concat( 29 | Object.keys(packageJson.peerDependencies || {}), 30 | ), 31 | plugins: [ 32 | __PROD__ && 33 | cleaner({ 34 | targets: ['./dist'], 35 | }), 36 | peerDepsExternal(), 37 | nodeResolve({ 38 | extensions: ['.js', '.ts', '.tsx'], 39 | }), 40 | commonjs({ 41 | include: 'node_modules/**', 42 | }), 43 | typescript({ 44 | useTsconfigDeclarationDir: true, 45 | }), 46 | ].filter(Boolean), 47 | }, 48 | __PROD__ && { 49 | input: './dist/index.d.ts', 50 | output: [{ file: './dist/index.d.ts', format: 'esm' }], 51 | plugins: [ 52 | dts(), 53 | del({ 54 | hook: 'buildEnd', 55 | targets: ['./dist/**/*.d.ts', './dist/{types,hooks}'], 56 | ignore: ['./dist/index.d.ts'], 57 | }), 58 | ], 59 | }, 60 | ].filter(Boolean); 61 | -------------------------------------------------------------------------------- /packages/rollup-config-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-config-generator", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./index.js", 6 | "module": "./index.js", 7 | "devDependencies": { 8 | "@rollup/plugin-commonjs": "^22.0.0", 9 | "@rollup/plugin-node-resolve": "^13.3.0", 10 | "rollup": "^2.71.1", 11 | "rollup-plugin-babel": "^4.4.0", 12 | "rollup-plugin-cleaner": "^1.0.0", 13 | "rollup-plugin-delete": "^2.0.0", 14 | "rollup-plugin-dts": "^4.2.1", 15 | "rollup-plugin-peer-deps-external": "^2.2.4", 16 | "rollup-plugin-typescript2": "^0.31.2", 17 | "typescript": "^4.6.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/tsconfig/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "compilerOptions": { 5 | "target": "ES2019", 6 | "module": "ESNext", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "declaration": true, 9 | "allowJs": true, 10 | "noEmit": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "react" 20 | }, 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "library.json" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "baseBranch": "origin/master", 4 | "pipeline": { 5 | "dev": { 6 | "cache": false 7 | }, 8 | "build": { 9 | "dependsOn": ["^build"], 10 | "outputs": ["dist/**", "build/**"] 11 | } 12 | } 13 | } 14 | --------------------------------------------------------------------------------