# Authentication Support API Platform Admin delegates the authentication support to React Admin. Refer to [the chapter dedicated to authentication in the React Admin documentation](https://marmelab.com/react-admin/Authentication.html) for more information. ## HydraAdmin The authentication layer for [HydraAdmin component](https://api-platform.com/docs/admin/components/#hydra) consists of a few parts, which need to be integrated together. ### Authentication Add the Bearer token from `localStorage` to request headers. ```typescript const getHeaders = () => localStorage.getItem("token") ? { Authorization: `Bearer ${localStorage.getItem("token")}` } : {}; ``` Extend the Hydra fetch function with custom headers for authentication. ```typescript const fetchHydra = (url, options = {}) => baseFetchHydra(url, { ...options, headers: getHeaders, }); ``` ### Login Redirection Redirect users to a `/login` path, if no token is available in the `localStorage`. ```typescript const RedirectToLogin = () => { const introspect = useIntrospection(); if (localStorage.getItem("token")) { introspect(); return <>; } return ; }; ``` ### API Documentation Parsing Extend the `parseHydraDocumentaion` function from the [API Doc Parser library](https://github.com/api-platform/api-doc-parser) to handle the documentation parsing. Customize it to clear expired tokens when encountering unauthorized `401` response. ```typescript 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 }; } }; ``` ### Data Provider Initialize the hydra data provider with custom headers and the documentation parser. ```typescript const dataProvider = (setRedirectToLogin) => baseHydraDataProvider({ entrypoint: ENTRYPOINT, httpClient: fetchHydra, apiDocumentationParser: apiDocumentationParser(setRedirectToLogin), }); ``` ### Export Admin Component Export the Hydra admin component, and track the users' authentication status. ```typescript // components/admin/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"; // Auth, Parser, Provider calls const getHeaders = () => {...}; const fetchHydra = (url, options = {}) => {...}; const RedirectToLogin = () => {...}; const apiDocumentationParser = (setRedirectToLogin) => async () => {...}; const dataProvider = (setRedirectToLogin) => {...}; const Admin = () => { const [redirectToLogin, setRedirectToLogin] = useState(false); return ( <> API Platform Admin {redirectToLogin ? ( } /> } /> ) : ( <> )} ); }; export default Admin; ``` ### Additional Notes For the implementation of the admin component, you can find a working example in the [API Platform's demo application](https://github.com/api-platform/demo/blob/4.0/pwa/components/admin/Admin.tsx). ## OpenApiAdmin This section explains how to set up and customize the [OpenApiAdmin component](https://api-platform.com/docs/admin/components/#openapi) authentication layer. It covers: * Creating a custom HTTP Client * Data and rest data provider configuration * Implementation of an auth provider ### Data Provider & HTTP Client Create a custom HTTP client to add authentication tokens to request headers. Configure the `openApiDataProvider`, and inject the custom HTTP client into the [Simple REST Data Provider for React-Admin](https://github.com/Serind/ra-data-simple-rest). **File:** `src/components/jsonDataProvider.tsx` ```typescript 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 jsonDataProvider = 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range). > > The `getAccessToken` function retrieves the JWT token stored in the browser. ### Authentication and Authorization Create and export an `authProvider` object that handles authentication and authorization logic. **File:** `src/components/authProvider.tsx` ```typescript interface JwtPayload { exp?: number; iat?: number; roles: string[]; username: string; } 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(token); return Promise.resolve({ id: "", fullName: decoded.username, avatar: "", }); }, getPermissions: () => Promise.resolve(""), }; export default authProvider; ``` ### Export OpenApiAdmin Component **File:** `src/App.tsx` ```typescript import {OpenApiAdmin} from '@api-platform/admin'; import authProvider from "./components/authProvider"; import jsonDataProvider from "./components/jsonDataProvider"; import {API_DOCS_PATH, API_ENTRYPOINT_PATH} from "./config/api"; export default () => ( ); ```