react-use-oauth2-popup v0.4.0
React-Use-OAuth2-Popup
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).
Getting Started
To install in your project, use the following command:
npm i react-use-oauth2-popup // or yarn add react-use-oauth2-popup
OAuthProvider
First, 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)
Options:
redirectUri: string
- Required - This is the URL where the browser will redirect after the user authorizes access - The string must contain:provider
and:method
- Specifies the path to the page where theuseOAuthPopup
hook is calledproviders: Record<string, {url: OAuthReqParams | OAuthUrlFn, popup?: PopupViewParams}>
- Required - This is an enumeration of all the providers that will be used - Optionpopup
- Optional - Allows you to customize the settings of the popup window - AnRedirectUriParams
as a value: -width?: number | undefined
- Optional - Defines the width of the popup - Defaults to450px
-height?: number | undefined
- Optional - Defines the height of the popup - Defaults to600px
-position?: 'center' | {leftOffset?: number, topOffset?: number} | undefined
- Optional - Defines the position of the popup - Defaults tocenter
- Optionurl
- AnOAuthReqParams
as a value - Creates a link based on the specified parameters - Note thatstate
andredirect_uri
will 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 tocode
-other_params?: Record<string, string | string[] | number[] | boolean> | undefined
- Optional - Lists any parameters that will be included in the URLname=value
- AnOAuthLinkFn
as a value - Manual version of the link compilation - Must be: -(redirect_uri: string, state: string) => string
- Example:
const params = new OAuthParams(`external/:method/:provider`, {
providerName: {
url: (redirect_uri, state) => `https://base-path.com?redirect_uri=${redirect_uri}&state=${state}&....`,
popup: { ... }
}
})
Example:
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>
);
};
OAuthParams Templates
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]
})
}))
useOAuth
After you have specified the configuration, you need to call the useOAuth
hook on the desired page
const {
openPopup,
closePopup,
activeProvider
} = useOAuth(method?, events?)
Options:
method: string
Required This allows you to use thecredentials
handlers in theuseOAuthPopup
hookevents?: PopupEvents | undefined
Optional AnPopupEvents
as a value:onSuccess?: ({ provider: string, method: string, credentials: Record<string, string>, data: TData}) => Promise<void> | void
Optional This function will fire when thecredentials
is received and processed using theuseOAuthPopup
hook. Can return a promise which will resolve the data (activeProvider
will not change value tonull
, until the promise is resolved) Thedata
value contains the result of execution from the corresponding handler fromuseOAuthPopup
Thecredentials
value contains all the parameters returned by the provideronError?: ({ provider: string, method: string, code: string, details?: TError | ErrorResponse }) => void
Optional This function will fire when an error occurs during the process Thedetails
is not empty only if the error was returned by the handler from theuseOAuthPopup
hook or if the provider returned an error.code
will be State Mismatch - The state value returned after receiving the credentials does not match * Callback Error - The function passed to process the method in theuseOAuthPopup
hook failed with an error * Invalid Parameters - An invalidprovider
is specified or an error has been made inredirect_uri
* Failure Response - Theprovider
returned an erroronOpen?: (provider: Privider) => void
Optional This function will fire when a popup opensonClose?: () => void
Optional This function will fire when a popup closes
Returns:
openPopup: (provider: string) => () => void
* Function for invoking the popupclosePopup: () => void
Function to close the popup If the popup is already closed, but theonSuccess
event has not yet been executed, theonSuccess
event will not be interruptedactiveProvider: string | null
Name of the current popup provider It will keep its value until theonSuccess
is executed
Example:
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>
);
};
useOAuthPopup
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?)
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 sendcredentials
to the backend of the application Ifmultiple
handlers: You can specify adefault
handler that will be called if there is no handler for a specific method * The key for each handler is themethod
that you specified in theuseOAuth
hookoptions: PopupConfig | undifined
Optional AnPopupConfig
as 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 thepopup
for any outcome Default to0
Returns:
status: string
Will be:idle
The initial state. Does not change if the page is opened manuallyrunning
If it processes the received datasuccess
If the data has been processed successfullyerror
If the data has been processed unsuccessfullyisIdle: boolean
A derived boolean from thestatus
variable above, provided for convenienceisRunning: boolean
A derived boolean from thestatus
variable above, provided for convenienceisError: boolean
A derived boolean from thestatus
variable above, provided for convenienceisSuccess: boolean
* A derived boolean from thestatus
variable above, provided for convenience
Example:
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 />
}
TypeScript
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>()