@prowise/react-auth
TypeScript icon, indicating that this package has built-in type declarations

2.2.1 • Public • Published

Prowise React Auth

This package wraps the open-source oidc-client-ts library and provides a context, helpers and base set of config to enable authorizations for front-end React applications.

Installation

Install with npm install @prowise/react-auth

You will need react@^18.2.0 as a dependency in your own project and you will want to install deep-diff@^1.0.2 if you're not yet using it.

Implementation

All the public interfaces are re-exported from index.ts, all other files and functions are considered private.

Bootstrapping Config

During bootstrapping, create an auth manager object. The returned object is the UserManager from oidc-client-ts. When using server side rendering (SSR) you can do this in componentDidMount or equivalent.

The settings object allows you to override config passed to the UserManager and allows you to set a home_path and error_path which will be arguments passed to the redirector component in certain scenarios.

import { createAuthManager } from "@prowise/react-auth";

// ...
createAuthManager(config.authority, config.clientId, settings);
// ...

See also the example implementation for this.

Setup Context Provider

Wrap your application/routes with the context provider setup / managed by the package. The following props are optional:

  • signinSilent will immediately try to assess login status on the background.
  • removeStaleState will clean up stale state requests after the user has successfully authorized.
  • removeExpired will remove an expired user from the store
  • managedSigninCallback will manage the logic for handling the auth callback for you, preventing the rendering of children of the Provider while the user is in the callback. NOTE: you do not need to use the AuthCallback component if this is true (default value)
  • loader the loader component to render when the user is on the authCallback page. This is only required if managedSigninCallback is kept on its default (true)
import React, { FC } from "react";

import { AuthProvider } from "@prowise/react-auth";

import MyAppRoutes from "routes";

const Loader: FC = () => <div>Processing auth callback...</div>

const MyApp: FC = () => (
    <AuthProvider loader={Loader} signinSilent removeStaleState removeExpired>
        <MyAppRoutes />
    </AuthProvider>
);

export default MyApp;

For the most simple example, see the example implementation. Note that it is common practice to add the protected routes in a <Route> tree as followed:

<BrowserRouter>
    <Routes>
        <Route path="/auth/error" element={<>There was an authentication error</>} />
        <Route element={<ProtectedRoutes />}>
            <Route
                path="/"
                element={
                    <>Protected content</>
                }
            />
        </Route>
    </Routes>
</BrowserRouter>

Where <ProtectedRoutes /> typically looks like:

let isLoggingIn = false;

/**
 * Exposes mostly the same interface as the normal Route. Ensures a user is
 * logged in before rendering the given component.
 *
 * @returns Component.
 */
export const ProtectedRoutes: FC = () => {
    const auth = useAuth();

    return useMemo(() => {
        if (!auth.isAuthenticated && !auth.isChecking) {
            // Not logged in.
            if (!isLoggingIn) {
                isLoggingIn = true;
                void auth.login();
            }
        }
        if (!auth.isAuthenticated) {
            return <>Redirecting to login</>;
        }

        // Logged in.
        isLoggingIn = false;
        return <Outlet />;
    }, [auth]);
};

Setup Callback Route

Note: This setup is only needed if you want to implement your own custom callback handling / routing Per default the AuthProvider does this for you, but you can set AuthProvider prop managedSigninCallback=false to manage it yourself. If you do choose to implement this, please make sure to set the implementation as early as possible in your project to prevent side effects during silent signin.

Implement the callback component on the callback URL, by default on the path /auth/callback but this can be overridden with redirect_uri in settings.

The redirector will get passed the path the user was on when the interactive login was triggered. The loader is rendered while the process is still being completed. Both parameters are required in this case.

The callback should be an unprotected route.

import React, { FC } from "react";

import { Redirect } from "react-router-dom";
import { AuthCallback } from "@prowise/react-auth";

const MyCallback: FC = () => (
    <AuthCallback
        redirector={({ to }) => (<Redirect to={to} />)}
        loader={() => <>Checking session status...</>}
    />
);

export default MyCallback;

Route implementation:

const MyApp = () => (
    <AuthProvider signinSilent={true} managedSigninCallback={false} >
        <Router>
            <Switch>
                <Route path="/" exact element={<Home />} />
                <MyProtectedRoute path="/protected" element={<User />} />
                <Route path="/about" element={<About />} />
                <Route path="/auth/callback" element={<MyCallback />} />
                <Route path="/auth/error" element={<Error />} />
            </Switch>
        </Router>
        <MyStatus />
    </AuthProvider>
);

Do NOT do this when implementing the Route (because the logic inside those wrappers will be run when the user is on the auth callback route):

const MyApp = () => (
    <AuthProvider signinSilent={true} managedSigninCallback={false} >
        <WrapperWithSideEffects>
            <WrapperWithSomeApiCalls>
                <AnotherWrapper>
                    <Router>
                        <Switch>
                            <Route path="/" exact element={<Home />} />
                            <MyProtectedRoute path="/protected" element={<User />} />
                            <Route path="/about" element={<About />} />
                            <Route path="/auth/callback" element={<MyCallback />} />
                            <Route path="/auth/error" element={<Error />} />
                        </Switch>
                    </Router>
                </AnotherWrapper>
            </WrapperWithSomeApiCalls>
        </WrapperWithSideEffects>
        <MyStatus />
    </AuthProvider>
);

See also the example implementation for this.

Setup Silent Signin Callback route to improve performance

Per default, the auth library will handle the silent signin callback in the AuthCallback component. This will render the /auth/callback route in an iframe in your app. The downside of this is that it will request the full app resources in the iframe that handles the silent auth callback, resulting in possible performance issues.

To prevent this, you can implement a custom silent signin callback page, with minimal javascript to handle the callback.

Create the following files in your public directory (ideally under a subfolder auth so your URL is alike the other auth URLs).

silent.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Prowise Silent Signin</title>
</head>

<body>
<script type="module" src="./silent.js"></script>
</body>

</html>

silent.js

/**
 * Logic is cloned from oidc-client-ts source code
 * https://github.com/authts/oidc-client-ts/blob/main/src/navigators/AbstractChildWindow.ts
 *
 * messageSource might change as this is a hardcoded variable in oidc-client-ts source code
 */

const messageSource = "oidc-client"; // As set by oidc-client-ts
const url = window.location.href;
const targetOrigin = window.location.origin;

try {
    // oidc-client-ts expects and handles the postMessage
    window.parent.postMessage({
        source: messageSource,
        url,
        keepOpen: false,
    }, targetOrigin);
} catch (e) {
    console.error("Silent signin failed", e);
}

Make sure to add the file path to your AuthManager settings.

createAuthManager(ssoAuthority, ssoClientId, {
    silent_redirect_uri: `${window.location.origin.toString()}/auth/silent.html`,
});

This new URL then needs to be added to our SSO database (OAUTH_CLIENT_URL) as accepted URL.

Use hook to get status

Once the above two are implemented you can use the useAuth hook to get the login state of a user, given the client used is authorized.

import React from "react";

import {useAuth} from "@prowise/react-auth";

const MyStatus = () => {
    const auth = useAuth();
    return (
        <>
            <pre>{JSON.stringify(auth, null, 2)}</pre>
            {auth.isAuthenticated ?
                <button onClick={auth.logout}>Logout</button> :
                <button onClick={auth.login}>Login</button>
            }
        </>
    );
};

export default MyStatus;

See also the example implementation for this.

GetAccessToken to always make sure you use an up to date token in your request

Prowise tokens expire in an hour. To prevent unneeded calls when you have an expired token, you can now use the getAccessToken which is available on the returned manager when using createAuthManager.

Simply wait with doing the API call until you get a response from this Promise. You can then use the resulting token in your request header.

import { createAuthManager } from "@prowise/react-auth";

// ...
const auth = createAuthManager(config.authority, config.clientId, settings);
// ...

// Then you can use this wherever your logic resides to attach a token to each request

const token = await auth.getAccessToken();

// Process the token & do your api call here

Login_hint Query Parameter

The login_hint query parameter can be passed on to every URL of your application and will be automatically picked up and removed from the URL. It will then be passed on every sign-in request to the SSO, and stored until you close the browser tab/window.

Examples

Further examples can be found in the examples folder - if you want to run it you will have to have run the following (in the root directory):

nvm use
npm i

Run the examples with:

npm run example

Notes

Expiration notification

To speed up the expiration notification mechanism, enable the property accessTokenExpiringNotificationTimeInSeconds within src/utilities/CreateAuthManager.ts.

Firefox redirect loop

If your browser is stuck in a redirect loop (most likely Firefox), it may be configured to deny third party cookies. To fix:

  • Go to Settings > Privacy & Security
  • Choose Custom
  • Uncheck Cookies

Readme

Keywords

none

Package Sidebar

Install

npm i @prowise/react-auth

Weekly Downloads

482

Version

2.2.1

License

UNLICENSED

Unpacked Size

375 kB

Total Files

58

Last publish

Collaborators

  • prowise_engineering