2.0.0 • Published 9 months ago

ra-keycloak v2.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

ra-keycloak

An auth provider for react-admin which handles authentication via a Keycloak server.

This package provides:

  • A keycloakAuthProvider for react-admin
  • A helper httpClient which adds headers needed by Keycloak in all requests.
  • A <LoginPage> component

This package uses keycloak-js to handle the Keycloak authentication.

Installation

yarn add ra-keycloak
# or
npm install --save ra-keycloak

Usage

// in src/index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { App } from './App';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
const router = createBrowserRouter([{ path: '*', element: <App /> }]);

root.render(
    <React.StrictMode>
        <RouterProvider router={router} />
    </React.StrictMode>
);

// in src/App.tsx
import * as React from 'react';
import { Admin, Resource } from 'react-admin';
import simpleRestProvider from 'ra-data-simple-rest';
import Keycloak, {
    KeycloakConfig,
    KeycloakTokenParsed,
    KeycloakInitOptions,
} from 'keycloak-js';
import { keycloakAuthProvider, httpClient, LoginPage } from 'ra-keycloak';
import posts from './posts';
import users from './users';

const config: KeycloakConfig = {
    url: '$KEYCLOAK_URL',
    realm: '$KEYCLOAK_REALM',
    clientId: '$KEYCLOAK_CLIENT_ID',
};

// here you can set options for the keycloak client
const initOptions: KeycloakInitOptions = {
    // Optional: makes Keycloak check that a user session already exists when it initializes
    // and immediately consider the user as authenticated if one exists.
    onLoad: 'check-sso',
    // Optional: makes Keycloak check that a user session already exists when it initializes and redirect them to the Keycloak login page if not.
    // It's not necessary with react-admin as it already has a process for that (authProvider.checkAuth)
    // onLoad: 'login-required',
    // Required when using react-router HashRouter (or createHashRouter)
    // responseMode: 'query'
};

// here you can implement the permission mapping logic for react-admin
const getPermissions = (decoded: KeycloakTokenParsed) => {
    const roles = decoded?.realm_access?.roles;
    if (!roles) {
        return false;
    }
    if (roles.includes('admin')) return 'admin';
    if (roles.includes('user')) return 'user';
    return false;
};

const keycloakClient = new Keycloak(config);
const authProvider = keycloakAuthProvider(keycloakClient, {
    initOptions,
    onPermissions: getPermissions,
});
const dataProvider = simpleRestProvider(
    '$API_URL',
    httpClient(keycloakClient)
);

export const App = () => {
    return (
        <Admin
            authProvider={authProvider}
            dataProvider={dataProvider}
            // Optional when using login-required init option on keycloak
            loginPage={LoginPage}
        >
            {permissions => (
                <>
                    <Resource name="posts" {...posts} />
                    {permissions === 'admin' ? (
                        <Resource name="users" {...users} />
                    ) : null}
                </>
            )}
        </Admin>
    );
};

keycloakAuthProvider Parameters

A function that returns an authProvider. It requires a Keycloak client as its first parameter.

// in src/dataProvider.ts
import { keycloakAuthProvider } from 'ra-keycloak';
import { keycloakClient } from './keycloakClient';

export const authProvider = keycloakAuthProvider(
    keycloakClient,
    {
        initOptions: { onLoad: 'check-sso' },
    }
);

It also accept a second parameter with the following options:

| Option | Required | Type | Description | | authenticationTimeout | | Number | The time to wait in milliseconds for Keycloak to detect authenticated users. Defaults to 2 seconds. | | initOptions | | Object | The options to pass to the Keycloak init function (See https://www.keycloak.org/securing-apps/javascript-adapter#_methods) | | loginRedirectUri | | String | The URI to which to redirect users after login | | logoutRedirectUri | | String | The URI to which to redirect users after logout | | onPermissions | | Function | A function used to transform the permissions fetched from Keycloak into a permissions object in the form of what your react-admin app expects |

Tip: This function will take care of initializing the Keycloak client if not already done.

httpClient

An Http client you can pass to many React-Admin data providers that will add the Keycloak authentication token to the requests headers. It requires a Keycloak client as its first parameter.

// in src/dataProvider.ts
import { httpClient } from 'ra-keycloak';
import simpleRestProvider from 'ra-data-simple-rest';
import { keycloakClient } from './keycloakClient';

export const dataProvider = simpleRestProvider(
    '$API_URL',
    httpClient(keycloakClient)
);

<LoginPage>

A custom React-Admin login page that call the Keycloak login method and automatically set the redirect URI to the /auth-callback route.

// in src/Admin.tsx
import * as React from 'react';
import { Admin } from 'react-admin';
import { LoginPage } from 'ra-keycloak';

export const App = () => {
    return (
        <Admin
            authProvider={authProvider}
            dataProvider={dataProvider}
            // Make sure you use the LoginPage provided by ra-keycloak if you didn't set the onLoad keycloak init option to 'login-required'
            loginPage={LoginPage}
        >
            {/* ... */}
        </Admin>
    );
};

Using ra-keycloak with the HashRouter

If for some reason you can't use createBrowserRouter nor BrowserRouter, you'll have to set the responseMode Keycloak init option to "query" and the keycloakAuthProvider.loginRedirectUri to /#/auth-callback. If your application does not have routes accessible to anonymous users, you should also set the keycloakAuthProvider.logoutRedirectUri to /#/login:

const keycloakClient = new Keycloak({
    url: '$KEYCLOAK_URL',
    realm: '$KEYCLOAK_REALM',
    clientId: '$KEYCLOAK_CLIENT_ID',
});
const authProvider = keycloakAuthProvider(keycloakClient, {
    initOptions: {
        responseMode: 'query',
    },
    loginRedirectUri: `/#/auth-callback`,
    logoutRedirectUri: `/#/login`,
    onPermissions: getPermissions,
});

This is because Keycloak passes its parameters for authentication in the URL hash fragment by default and it's not compatible with createHashRouter nor <HashRouter>.

Demo

You can find a working demo, along with the source code, in this project's repository: https://github.com/marmelab/ra-keycloak

License

This auth provider is licensed under the MIT License and sponsored by marmelab.

2.0.0

9 months ago

1.0.1

2 years ago

1.0.0

3 years ago