From 50e0933ec3dd0efa20d4da44d7cfabc4727be033 Mon Sep 17 00:00:00 2001 From: igornast Date: Tue, 14 Jan 2025 11:20:03 +0100 Subject: [PATCH] docs(admin): add OpenApiAdmin section in auth-support --- admin/authentication-support.md | 134 +++++++++++++++++++++++++++++++- admin/openapi.md | 59 -------------- 2 files changed, 130 insertions(+), 63 deletions(-) diff --git a/admin/authentication-support.md b/admin/authentication-support.md index a7bf1fd8098..78ba943fec8 100644 --- a/admin/authentication-support.md +++ b/admin/authentication-support.md @@ -106,9 +106,12 @@ import { parseHydraDocumentation } from "@api-platform/api-doc-parser"; import authProvider from "utils/authProvider"; import { ENTRYPOINT } from "config/entrypoint"; -// ***** -// Here put the code parts shown above -// ***** +// 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); @@ -144,4 +147,127 @@ export default Admin; ### Additional Notes -For the implementation of the admin conponent, 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). +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 data `ApiPlatformAdminDataProvider` data provider, 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 () => ( + +); +``` diff --git a/admin/openapi.md b/admin/openapi.md index b7f3047c16f..cfba6580cfd 100644 --- a/admin/openapi.md +++ b/admin/openapi.md @@ -39,65 +39,6 @@ export default () => ( ); ``` -### Custom Data Provider - -For more advanced use cases, you can create a custom dataProvider to add features such as authentication, -logging, or token-based authorization. - -Here's an example of how to integrate a simpleRestProvider with a customized httpClient: - -```javascript -import { fetchUtils } from 'react-admin'; -import { openApiDataProvider } from '@api-platform/admin'; -import simpleRestProvider from 'ra-data-simple-rest'; -import { getAccessToken } from './accessToken'; - -const API_URL = process.env.REACT_APP_API_URL || 'http://localhost/api'; -const DOC_URL = `${API_URL}/docs`; - -// Custom HTTP client for authenticated requests -const httpClient = async (url, options = {}) => { - options.headers = new Headers({ - ...options.headers, - Accept: 'application/json', - }); - - const token = getAccessToken(); - if (token) { - options.user = { token: `Bearer ${token}`, authenticated: true }; - } - - try { - const { status, headers, body, json } = await fetchUtils.fetchJson(url, options); - return { status, headers, body, json }; - } catch (error) { - throw error; - } -}; - -// Wrapping the custom HTTP client into a data provider -const jsonDataProvider = openApiDataProvider({ - dataProvider: simpleRestProvider(API_URL, httpClient), - entrypoint: API_URL, - docEntrypoint: DOC_URL, -}); - -export default () => ( - -); - -``` - -> [!NOTE] -> The `getAccessToken` function is a placeholder for your custom logic to retrieve a JWT token. -> -> Implement this function according to your application's requirements, such as reading the token from local storage, -> cookies, or a secure context. - ## Mercure Support Mercure support can be enabled manually by giving the `mercure` prop to the `OpenApiAdmin` component.