Authentication SupportTable of Contents
Table of Contents
API Platform Admin delegates the authentication support to React Admin.
Refer to the Auth Provider Setup documentation for more information.
Tip: Once you have set up the authentication, you can also configure React Admin to perform client-side Authorization checks. Refer to the Authorization documentation for more information.
# HydraAdmin
Enabling authentication support for <HydraAdmin> component consists of a
few parts, which need to be integrated together.
In the following steps, we will see how to:
- Make authenticated requests to the API (i.e. include the
Authorizationheader) - Redirect users to the login page if they are not authenticated
- Clear expired tokens when encountering unauthorized
401response
# Make Authenticated Requests
First, we need to implement a getHeaders function, that will add the Bearer token from
localStorage (if there is one) to the Authorization header.
const getHeaders = () =>
localStorage.getItem("token")
? { Authorization: `Bearer ${localStorage.getItem("token")}` }
: {};Then, extend the Hydra fetch function to use the getHeaders function to add the Authorization
header to the requests.
import { fetchHydra as baseFetchHydra } from "@api-platform/admin";
const fetchHydra = (url, options = {}) =>
baseFetchHydra(url, {
...options,
headers: getHeaders,
});# Redirect To Login Page
Then, we’ll create a <RedirectToLogin> component, that will redirect users to the /login route
if no token is available in the localStorage, and call the dataProvider’s introspect function
otherwise.
import { Navigate } from "react-router-dom";
import { useIntrospection } from "@api-platform/admin";
const RedirectToLogin = () => {
const introspect = useIntrospection();
if (localStorage.getItem("token")) {
introspect();
return <></>;
}
return <Navigate to="/login" />;
};# Clear Expired Tokens
Now, we will extend the parseHydraDocumentaion function (imported from the
@api-platform/api-doc-parser library).
We will customize it to clear expired tokens when encountering unauthorized 401 response.
import { parseHydraDocumentation } from "@api-platform/api-doc-parser";
import { ENTRYPOINT } from "config/entrypoint";
const apiDocumentationParser = (setRedirectToLogin) => async () => {
try {
setRedirectToLogin(false);
return await parseHydraDocumentation(ENTRYPOINT, { headers: getHeaders });
} catch (result) {
const { api, response, status } = result;
if (status !== 401 || !response) {
throw result;
}
localStorage.removeItem("token");
setRedirectToLogin(true);
return { api, response, status };
}
};# Extend The Data Provider
Now, we can initialize the Hydra data provider with the custom fetchHydra (with custom headers)
and apiDocumentationParser functions created earlier.
import { hydraDataProvider as baseHydraDataProvider } from "@api-platform/admin";
import { ENTRYPOINT } from "config/entrypoint";
const dataProvider = (setRedirectToLogin) =>
baseHydraDataProvider({
entrypoint: ENTRYPOINT,
httpClient: fetchHydra,
apiDocumentationParser: apiDocumentationParser(setRedirectToLogin),
});# Update The Admin Component
Lastly, we can stitch everything together in the Admin component.
// src/Admin.tsx
import Head from "next/head";
import { useState } from "react";
import { Navigate, Route } from "react-router-dom";
import { CustomRoutes } from "react-admin";
import {
fetchHydra as baseFetchHydra,
HydraAdmin,
hydraDataProvider as baseHydraDataProvider,
useIntrospection,
} from "@api-platform/admin";
import { parseHydraDocumentation } from "@api-platform/api-doc-parser";
import authProvider from "utils/authProvider";
import { ENTRYPOINT } from "config/entrypoint";
// Functions and components created in the previous steps:
const getHeaders = () => {...};
const fetchHydra = (url, options = {}) => {...};
const RedirectToLogin = () => {...};
const apiDocumentationParser = (setRedirectToLogin) => async () => {...};
const dataProvider = (setRedirectToLogin) => {...};
export const Admin = () => {
const [redirectToLogin, setRedirectToLogin] = useState(false);
return (
<>
<Head>
<title>API Platform Admin</title>
</Head>
<HydraAdmin
dataProvider={dataProvider(setRedirectToLogin)}
authProvider={authProvider}
entrypoint={window.origin}
>
{redirectToLogin ? (
<CustomRoutes>
<Route path="/" element={<RedirectToLogin />} />
<Route path="/:any" element={<RedirectToLogin />} />
</CustomRoutes>
) : (
<>
<Resource name=".." list="..">
<Resource name=".." list="..">
</>
)}
</HydraAdmin>
</>
);
};# Example Implementation
For the implementation of the admin component, you can find a working example in the API Platform’s demo application.
# OpenApiAdmin
This section explains how to set up and customize the
<OpenApiAdmin> component to enable authentication.
In the following steps, we will see how to:
- Make authenticated requests to the API (i.e. include the
Authorizationheader) - Implement an authProvider to redirect users to the login page if they are not authenticated, and
clear expired tokens when encountering unauthorized
401response
# Making Authenticated Requests
First, we need to create a custom httpClient to add authentication tokens (via the the
Authorization HTTP header) to requests.
We will then configure openApiDataProvider to use
ra-data-simple-rest,
a simple REST dataProvider for React Admin, and make it use the httpClient we created earlier.
// src/dataProvider.ts
const getAccessToken = () => localStorage.getItem("token");
const httpClient = async (url: string, options: fetchUtils.Options = {}) => {
options.headers = new Headers({
...options.headers,
Accept: "application/json",
}) as Headers;
const token = getAccessToken();
options.user = { token: `Bearer ${token}`, authenticated: !!token };
return await fetchUtils.fetchJson(url, options);
};
const dataProvider = openApiDataProvider({
dataProvider: simpleRestProvider(API_ENTRYPOINT_PATH, httpClient),
entrypoint: API_ENTRYPOINT_PATH,
docEntrypoint: API_DOCS_PATH,
});Note: The simpleRestProvider provider expect the API to include a Content-Range header in
the response. You can find more about the header syntax in the
Mozilla’s MDN documentation: Content-Range.
Note: The getAccessToken function retrieves the JWT token stored in the browser’s
localStorage. Replace it with your own logic in case you don’t store the token that way.
# Creating The AuthProvider
Now let’s create and export an authProvider object that handles authentication and authorization
logic.
// src/authProvider.ts
interface JwtPayload {
sub: string;
username: string;
}
const getAccessToken = () => localStorage.getItem("token");
const authProvider = {
login: async ({ username, password }: { username: string; password: string }) => {
const request = new Request(API_AUTH_PATH, {
method: "POST",
body: JSON.stringify({ email: username, password }),
headers: new Headers({ "Content-Type": "application/json" }),
});
const response = await fetch(request);
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
const auth = await response.json();
localStorage.setItem("token", auth.token);
},
logout: () => {
localStorage.removeItem("token");
return Promise.resolve();
},
checkAuth: () => (getAccessToken() ? Promise.resolve() : Promise.reject()),
checkError: (error: { status: number }) => {
const status = error.status;
if (status === 401 || status === 403) {
localStorage.removeItem("token");
return Promise.reject();
}
return Promise.resolve();
},
getIdentity: () => {
const token = getAccessToken();
if (!token) return Promise.reject();
const decoded = jwtDecode<JwtPayload>(token);
return Promise.resolve({
id: decoded.sub,
fullName: decoded.username,
avatar: "",
});
},
getPermissions: () => Promise.resolve(""),
};
export default authProvider;# Updating The Admin Component
Finally, we can update the Admin component to use the authProvider and dataProvider we created
earlier.
// src/Admin.tsx
import { OpenApiAdmin } from "@api-platform/admin";
import authProvider from "./authProvider";
import dataProvider from "./dataProvider";
import { API_DOCS_PATH, API_ENTRYPOINT_PATH } from "./config/api";
export default () => (
<OpenApiAdmin
entrypoint={API_ENTRYPOINT_PATH}
docEntrypoint={API_DOCS_PATH}
dataProvider={dataProvider}
authProvider={authProvider}
/>
);You can also help us improve the documentation of this page.
Using an AI coding agent? See the documentation index for LLMs at /docs/llms.txt.
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more