yarn add @broadleaf/auth-react
The Broadleaf Auth React SDK is designed to make interacting with the Broadleaf Authorization Server easier for a single-page React application.
Start by configuring the AuthProvider
as a wrapper around your App
:
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider } from '@broadleaf/auth-react';
import App from './App';
ReactDOM.render(
<AuthProvider
baseURL="/auth"
clientId="my-app"
>
<App/>
</AuthProvider>,
document.getElementById('root')
);
Now, you can use the useAuth
hook to access the provider state and methods:
import React from 'react';
import { useAuth } from '@broadleaf/auth-react';
const App = () => {
const {
error,
isAuthenticated,
isLoading,
loginWithRedirect,
logoutWithRedirect,
user
} = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
if (!isAuthenticated) {
return <button onClick={() => loginWithRedirect()}>Login</button>
}
return (
<div>
<h1>Welcome {user.fullName}</h1>
<button onClick={() => logoutWithRedirect()}>Logout</button>
</div>
);
};
export default App;
If you want to secure calls to your APIs, you can do so using getAccessToken
:
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useAuth } from '@broadleaf/auth-react';
const OrderList = () => {
const { getAccessToken } = useAuth();
const [orders, setOrders] = useState();
useEffect(() => {
(async () => {
try {
const accessToken = await getAccessToken();
const response = await fetch('/api/orders', {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
setOrders(await response.json());
} catch (e) {
console.error('Unable to fetch orders', e);
}
})();
}, [getAccessToken, isAuthenticated])
if (!orders) {
return <div>Loading Orders...</div>;
}
return (
<div>
{orders.map(order => <Link to={`/orders/${order.orderNumber}`}>{order.orderNumber}</Link>)}
</div>
)
}
export default OrderList;
By default and as a fallback for Refresh Token Rotation, getAccessToken
uses a hidden iframe to execute the OAuth2 Authorization Code Grant with PKCE to retrieve tokens in the background.
In order to support this flow, we need our application to serve a special callback URL to communicate the query parameters returned by the authorization flow.
We provide an example file as part of the installed library, which can be found at node_modules/@broadeaf/auth-web/example/silent-callback.html
:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
parent.postMessage(
{ type: 'authorization_code', search: window.location.search },
window.location.origin
);
</script>
</head>
<body></body>
</html>
Each environment may have a different way of providing this file on the silent callback route.
Be sure that your various environments are capable of serving this static resource at default silentRedirectUri
at ${window.location.origin}/silent-callback.html
.
If you want to serve this file at a custom URL, you can configure the silentRedirectUri
when rendering the AuthProvider
:
import React from 'react';
import ReactDOM from 'react-dom';
import { AuthProvider } from '@broadleaf/auth-react';
import App from './App';
ReactDOM.render(
<AuthProvider
baseURL="/auth"
clientId="my-app"
silentRedirectUri={`${window.location.origin}/custom-silent-callback.html`}
>
<App/>
</AuthProvider>,
document.getElementById('root')
);
If you are using a router library such as react-router-dom
, you will need to pass a custom
onRedirectCallback
function to handle the post-login redirect:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, useHistory } from 'react-router-dom';
import { AuthProvider } from '@broadleaf/auth-react';
import App from './App';
const AppWithAuth = () => {
const history = useHistory();
return (
<AuthProvider
baseURL="/auth"
clientId="my-app"
onRedirectCallback={appState => {
history.replace(appState?.returnTo || window.location.pathname)
}}
>
<App/>
</AuthProvider>
);
};
ReactDOM.render(
<BrowserRouter>
<AppWithAuth/>
</BrowserRouter>,
document.getElementById('root')
);
If you want to secure a private route so that authentication is required to enter:
import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import { useAuth } from '@broadleaf/auth-react';
const PrivateRoute = ({children, ...routeProps}) => {
const { isAuthenticated } = useAuth();
return (
<Route {...routeProps}>
{({ location }) => isAuthenticated ? children : <RedirectToLogin returnTo={location.pathname}/>}
</Route>
)
};
const RedirectToLogin = ({ returnTo }) => {
const { loginWithRedirect } = useAuth();
useEffect(
() => {
loginWithRedirect({ appState: { returnTo } });
},
[]
);
return null;
};
export default PrivateRoute;
If you want to display or otherwise utilize user profile information within your application, use the useAuth
hook:
import { useAuth } from '@broadleaf/auth-react';
const Welcome = () => {
const { isAuthenticated, user } = useAuth();
if (!isAuthenticated) {
return <div>{'Welcome Guest!'}</div>;
}
return <div>{`Welcome, ${user.full_name}!`}</div>
};
export default Header;
If you want to implement a session inactivity timer or keep-alive behavior, it is best to use the useAuth
hook to access the current state, or check the user’s session:
function useKeepSessionAlive(isWindowFocused = false, showInactivityNotice) {
const {
checkSession,
isAuthenticated,
logoutWithRedirect,
session
} = useAuth();
useInterval(
checkSession,
// if the window is focused, refresh the session timer every 5 minutes
isWindowFocused && isAuthenticated
? 1000 * 60 * 5
: null
);
useInterval(
() => {
const inactivityDate = asDate(session.inactivityAt);
const timeLeftInSession = Math.max(
inactivityDate.getTime() - Date.now(),
0
);
if (timeLeftInSession <= 0) {
logoutWithRedirect();
} else if (timeLeftInSession <= 1000 * 60 * 3) {
showInactivityNotice();
}
},
isAuthenticated && session?.inactivityAt ? 1000 : null
);
}
There may be times when the frontend app using the SDK is hosted on a different domain than the Auth Server rather than hosting both behind a proxy gateway. In this case, the BLSID cookie will be considered a third-party cookie and blocked by browsers by default.
To handle auth in this case, refresh-token-rotation should be enabled. Along with that, because automatically logging in users after they register or reset their password relies on the session cookie as well, there is a parameter that can be added to the redirect URIs used to indicate to the AuthProvider
that it should automatically call loginWithRedirect
when detected to give a more seamless experience to users.
To enable these features, you would set up your AuthProvider
like the following:
+
<AuthProvider
key={clientId}
accountId={accountId}
baseURL={https://www.my-auth-server.com}
redirectUri={https://www.my-frontend.com}
clientId={clientId}
credentials
scope="USER CUSTOMER"
useRefreshTokens
usePkce
useIFrameAsFallbackForRefreshToken={false}
useSessionStorageForTokenCache // Session storage should be cleared when the browse closes
useLocalStorageForTokenCache // local storage won't be
>
Tip
|
The Authorization Server (managed in the Admin) should allow cross-origin SSO and the Authorized Client should allow the refresh_token grant type (under Advanced).
|