This small library is designed to simplify working with OAuth2 in your React applications. Authorization with a third-party service is called in a new popup window of the browser. It allows you to use multiple providers and separate the logic of interaction with third-party services (methods).
To install in your project, use the following command:
npm i react-use-oauth2-popup // or yarn add react-use-oauth2-popupFirst, you need to create configurations for OAuth2 using OAuthParams and pass it as a params argument to the context provider. Create a configuration outside the component.
import { OAuthParams } from 'react-use-oauth2-popup';
const params = new OAuthParams(redirectUri, providers)redirectUri: string- Required
- This is the URL where the browser will redirect after the user authorizes access
- The string must contain
:providerand:method - Specifies the path to the page where the
useOAuthPopuphook is called
providers: Record<string, {url: OAuthReqParams | OAuthUrlFn, popup?: PopupViewParams}>- Required
- This is an enumeration of all the providers that will be used
- Option
popup- Optional
- Allows you to customize the settings of the popup window
- An
RedirectUriParamsas a value:width?: number | undefined- Optional
- Defines the width of the popup
- Defaults to
450px
height?: number | undefined- Optional
- Defines the height of the popup
- Defaults to
600px
position?: 'center' | {leftOffset?: number, topOffset?: number} | undefined- Optional
- Defines the position of the popup
- Defaults to
center
- Option
url- An
OAuthReqParamsas a value- Creates a link based on the specified parameters
- Note that
stateandredirect_uriwill be added automatically - Must be:
base_path: string- Required
- Authorization server URL
client_id: string- Required
- Public identifier for the app
scope?: string | string[] | undefined- Optional
- The request may have one or more scope values indicating additional access requested by the application
response_type?: 'code' | 'token' | undefined- Optional
- Defines response type after authentication
- Default to
code
other_params?: Record<string, string | string[] | number[] | boolean> | undefined- Optional
- Lists any parameters that will be included in the URL
name=value
- An
OAuthLinkFnas a value- Manual version of the link compilation
- Must be:
(redirect_uri: string, state: string) => string
- Example:
- An
const params = new OAuthParams(`external/:method/:provider`, {
providerName: {
url: (redirect_uri, state) => `https://base-path.com?redirect_uri=${redirect_uri}&state=${state}&....`,
popup: { ... }
}
})import { OAuthParams, OAuthProvider } from 'react-use-oauth2-popup';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const params = new OAuthParams('/external/:provider/:method', {
google: {
url: {
base_path: 'https://accounts.google.com/o/oauth2/v2/auth',
client_id: process.env.google_client_id,
response_type: 'code',
scope: ['https://www.googleapis.com/auth/userinfo.email']
}
},
discord: {
url: {
base_path: 'https://discord.com/oauth2/authorize',
client_id: process.env.discord_client_id,
response_type: 'code',
scope: 'identify'
},
popup: {
height: 600,
width: 500
}
}
});
const router = createBrowserRouter([
{
path: '/login',
element: <LoginPage />
},
{
path: '/external/:provider/:method',
element: <PopupPage />
}
]);
const App = () => {
return (
<OAuthProvider params={params}>
<RouterProvider router={router} />
</OAuthProvider>
);
};You can also use pre-defined TypeScript OAuth templates (GitHub, Discord, VK, Twitch, Google, LinkedIn, Microsoft). Usage example:
const params = new OAuthParams(`external/:provider/:method`, (templates) => ({
google: templates.google({
client_id: process.env.google_client_id,
response_type: 'code',
include_granted_scopes: true,
scope: [...scopes]
}),
discord: templates.discord({
client_id: process.env.discord_client_id,
response_type: 'code',
prompt: 'consent',
scope: [...scopes]
})
}))After you have specified the configuration, you need to call the useOAuth hook on the desired page
const {
openPopup,
closePopup,
activeProvider
} = useOAuth(method?, events?)method: string- Required
- This allows you to use the
credentialshandlers in theuseOAuthPopuphook
events?: PopupEvents | undefined- Optional
- An
PopupEventsas a value:onSuccess?: ({ provider: string, method: string, credentials: Record<string, string>, data: TData}) => Promise<void> | void- Optional
- This function will fire when the
credentialsis received and processed using theuseOAuthPopuphook. - Can return a promise which will resolve the data (
activeProviderwill not change value tonull, until the promise is resolved) - The
datavalue contains the result of execution from the corresponding handler fromuseOAuthPopup - The
credentialsvalue contains all the parameters returned by the provider
onError?: ({ provider: string, method: string, code: string, details?: TError | ErrorResponse }) => void- Optional
- This function will fire when an error occurs during the process
- The
detailsis not empty only if the error was returned by the handler from theuseOAuthPopuphook or if the provider returned an error. codewill be- State Mismatch - The state value returned after receiving the credentials does not match
- Callback Error - The function passed to process the method in the
useOAuthPopuphook failed with an error - Invalid Parameters - An invalid
provideris specified or an error has been made inredirect_uri - Failure Response - The
providerreturned an error
onOpen?: (provider: Privider) => void- Optional
- This function will fire when a popup opens
onClose?: () => void- Optional
- This function will fire when a popup closes
openPopup: (provider: string) => () => void- Function for invoking the popup
closePopup: () => void- Function to close the popup
- If the popup is already closed, but the
onSuccessevent has not yet been executed, theonSuccessevent will not be interrupted
activeProvider: string | null- Name of the current popup provider
- It will keep its value until the
onSuccessis executed
import { useOAuth, ErrorCodes } from 'react-use-oauth2-popup';
import { redirect } from 'react-router-dom';
const LoginPage = () => {
const { openPopup } = useOAuth('login', {
onSuccess: ({ data }) => {
redirect('me/profile');
},
onError: (error) => {
switch(error.code){
case ErrorCodes.StateMismatch:
//notification: try again
}
}
});
return (
<button onClick={openPopup('discord')}>
Login with Discord
</button>
);
};The hook should be called on the page specified in the "redirectUri". After receiving the credentials from the provider, this page will be opened and the appropriate handler method will be called.
After processing the data, the popup will be closed automatically, even if an error occurs.
const {
status,
isIdle,
isRunning,
isError,
isSuccess
} = useOAuthPopup(handlers, options?)handlers: Record<string, (data: { method: string, provider: string, credentials: Record<string, string> }) => Promise<unknown> | unknown>handlers: (data: { method: string, provider: string, credentials: Record<string, string> }) => Promise<unknown> | unknown- Required
- Recommendation: use it to send
credentialsto the backend of the application - If
multiplehandlers:- You can specify a
defaulthandler that will be called if there is no handler for a specific method - The key for each handler is the
methodthat you specified in theuseOAuthhook
- You can specify a
options: PopupConfig | undifined- Optional
- An
PopupConfigas a value:directAccessHandler: (() => void) | undefined- Optional
- This function will fire when the page is opened manually
- Default to
() => window.location.assign(window.location.origin)
delayClose: number | undefined- Optional
- Defines the delay in milliseconds before closing the
popupfor any outcome - Default to
0
status: string- Will be:
idleThe initial state. Does not change if the page is opened manuallyrunningIf it processes the received datasuccessIf the data has been processed successfullyerrorIf the data has been processed unsuccessfully
isIdle: boolean- A derived boolean from the
statusvariable above, provided for convenience
- A derived boolean from the
isRunning: boolean- A derived boolean from the
statusvariable above, provided for convenience
- A derived boolean from the
isError: boolean- A derived boolean from the
statusvariable above, provided for convenience
- A derived boolean from the
isSuccess: boolean- A derived boolean from the
statusvariable above, provided for convenience
- A derived boolean from the
- Will be:
import { useOAuthPopup } from 'react-use-oauth2-popup'
const PopupPage = () => {
const { isSuccess, isError } = useOAuthPopup({
login: async ({ credentials }) => {
const res = await fetch('example.com/login', {
method: 'post',
body: JSON.stringify(credentials)
})
return res.json()
},
default: () => {
console.log('No method specified')
}
})
if(isSuccess){
return <SuccessIcon />
}
if(isError){
return <ErrorIcon />
}
return <LoaderIcon />
}To strictly specify the lists of available methods and providers,
use the extension of the definition of CustomTypeOptions
Create an oauth.d.ts, for example:
// import the original type declarations
import 'react-use-oauth2-popup'
declare module 'react-use-oauth2-popup' {
// Extend CustomTypeOptions
interface CustomTypeOptions {
provider: 'google' | 'discord' | ...
method: 'login' | 'join' | 'connect' | ...
}
}To enhance the typing of the returned values for details in both
success and error cases, it is recommended to use generics
import { useOAuth } from 'react-use-oauth2-popup';
import { DiscordSuccess, GoogleSuccess, DiscordError, GoogleError } from 'types'
type Success = {
discord: DiscordSuccess;
google: GoogleSuccess;
};
type Error = {
discord: DiscordError,
google: GoogleError
}
useOAuth<Success, Error>()