@@ -4,33 +4,38 @@ API Platform Admin delegates the authentication support to React Admin.
44Refer to [ the chapter dedicated to authentication in the React Admin documentation] ( https://marmelab.com/react-admin/Authentication.html )
55for more information.
66
7- In short, you have to tweak the data provider and the API documentation parser like this:
7+ ## HydraAdmin
8+
9+ The authentication layer for [ HydraAdmin component] ( https://api-platform.com/docs/admin/components/#hydra )
10+ consists of a few parts, which need to be integrated together.
11+
12+ ### Authentication
13+
14+ Add the Bearer token from ` localStorage ` to request headers.
815
916``` typescript
10- // components/admin/Admin.tsx
17+ const getHeaders = () =>
18+ localStorage .getItem (" token" )
19+ ? { Authorization: ` Bearer ${localStorage .getItem (" token" )} ` }
20+ : {};
21+ ```
1122
12- import Head from " next/head" ;
13- import { useState } from " react" ;
14- import { Navigate , Route } from " react-router-dom" ;
15- import { CustomRoutes } from " react-admin" ;
16- import {
17- fetchHydra as baseFetchHydra ,
18- HydraAdmin ,
19- hydraDataProvider as baseHydraDataProvider ,
20- useIntrospection ,
21- } from " @api-platform/admin" ;
22- import { parseHydraDocumentation } from " @api-platform/api-doc-parser" ;
23- import authProvider from " utils/authProvider" ;
24- import { ENTRYPOINT } from " config/entrypoint" ;
23+ Extend the Hydra fetch function with custom headers for authentication.
2524
26- const getHeaders = () => localStorage .getItem (" token" ) ? {
27- Authorization: ` Bearer ${localStorage .getItem (" token" )} ` ,
28- } : {};
25+ ``` typescript
2926const fetchHydra = (url , options = {}) =>
3027 baseFetchHydra (url , {
3128 ... options ,
3229 headers: getHeaders ,
3330 });
31+
32+ ```
33+
34+ ### Login Redirection
35+
36+ Redirect users to a ` /login ` path, if no token is available in the ` localStorage ` .
37+
38+ ``` typescript
3439const RedirectToLogin = () => {
3540 const introspect = useIntrospection ();
3641
@@ -40,34 +45,73 @@ const RedirectToLogin = () => {
4045 }
4146 return <Navigate to =" /login" />;
4247};
48+ ```
49+
50+ ### API Documentation Parsing
51+
52+ Extend the ` parseHydraDocumentaion ` function from the [ API Doc Parser library] ( https://github.com/api-platform/api-doc-parser )
53+ to handle the documentation parsing. Customize it to clear
54+ expired tokens when encountering unauthorized ` 401 ` response.
55+
56+ ``` typescript
4357const apiDocumentationParser = (setRedirectToLogin ) => async () => {
4458 try {
4559 setRedirectToLogin (false );
46-
4760 return await parseHydraDocumentation (ENTRYPOINT , { headers: getHeaders });
4861 } catch (result ) {
4962 const { api, response, status } = result ;
5063 if (status !== 401 || ! response ) {
5164 throw result ;
5265 }
5366
54- // Prevent infinite loop if the token is expired
5567 localStorage .removeItem (" token" );
56-
5768 setRedirectToLogin (true );
5869
59- return {
60- api ,
61- response ,
62- status ,
63- };
70+ return { api , response , status };
6471 }
6572};
66- const dataProvider = (setRedirectToLogin ) => baseHydraDataProvider ({
67- entrypoint: ENTRYPOINT ,
68- httpClient: fetchHydra ,
69- apiDocumentationParser: apiDocumentationParser (setRedirectToLogin ),
70- });
73+ ```
74+
75+ ### Data Provider
76+
77+ Initialize the hydra data provider with custom headers and the documentation parser.
78+
79+ ``` typescript
80+ const dataProvider = (setRedirectToLogin ) =>
81+ baseHydraDataProvider ({
82+ entrypoint: ENTRYPOINT ,
83+ httpClient: fetchHydra ,
84+ apiDocumentationParser: apiDocumentationParser (setRedirectToLogin ),
85+ });
86+ ```
87+
88+ ### Export Admin Component
89+
90+ Export the Hydra admin component, and track the users' authentication status.
91+
92+ ``` typescript
93+ // components/admin/Admin.tsx
94+
95+ import Head from " next/head" ;
96+ import { useState } from " react" ;
97+ import { Navigate , Route } from " react-router-dom" ;
98+ import { CustomRoutes } from " react-admin" ;
99+ import {
100+ fetchHydra as baseFetchHydra ,
101+ HydraAdmin ,
102+ hydraDataProvider as baseHydraDataProvider ,
103+ useIntrospection ,
104+ } from " @api-platform/admin" ;
105+ import { parseHydraDocumentation } from " @api-platform/api-doc-parser" ;
106+ import authProvider from " utils/authProvider" ;
107+ import { ENTRYPOINT } from " config/entrypoint" ;
108+
109+ // Auth, Parser, Provider calls
110+ const getHeaders = () => {... };
111+ const fetchHydra = (url , options = {}) => {... };
112+ const RedirectToLogin = () => {... };
113+ const apiDocumentationParser = (setRedirectToLogin ) => async () => {... };
114+ const dataProvider = (setRedirectToLogin ) => {... };
71115
72116const Admin = () => {
73117 const [redirectToLogin, setRedirectToLogin] = useState (false );
@@ -78,7 +122,11 @@ const Admin = () => {
78122 <title >API Platform Admin < / title >
79123 < / Head >
80124
81- < HydraAdmin dataProvider = {dataProvider(setRedirectToLogin )} authProvider = {authProvider } entrypoint = {window.origin }>
125+ < HydraAdmin
126+ dataProvider = {dataProvider(setRedirectToLogin )}
127+ authProvider = {authProvider }
128+ entrypoint = {window.origin }
129+ >
82130 {redirectToLogin ? (
83131 <CustomRoutes >
84132 <Route path = " /" element = {<RedirectToLogin />} / >
@@ -93,8 +141,133 @@ const Admin = () => {
93141 < / HydraAdmin >
94142 < / >
95143 );
96- }
144+ };
97145export default Admin ;
98146```
99147
100- For the implementation of the auth provider, you can find a working example in the [ API Platform's demo application] ( https://github.com/api-platform/demo/blob/main/pwa/utils/authProvider.tsx ) .
148+ ### Additional Notes
149+
150+ 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 ) .
151+
152+ ## OpenApiAdmin
153+
154+ This section explains how to set up and customize the [ OpenApiAdmin component] ( https://api-platform.com/docs/admin/components/#openapi ) authentication layer.
155+ It covers:
156+ * Creating a custom HTTP Client
157+ * Data and rest data provider configuration
158+ * Implementation of an auth provider
159+
160+ ### Data Provider & HTTP Client
161+
162+ Create a custom HTTP client to add authentication tokens to request headers.
163+ Configure the ` openApiDataProvider ` , and
164+ inject the custom HTTP client into the [ Simple REST Data Provider for React-Admin] ( https://github.com/Serind/ra-data-simple-rest ) .
165+
166+ ** File:** ` src/components/jsonDataProvider.tsx `
167+ ``` typescript
168+ const httpClient = async (url : string , options : fetchUtils .Options = {}) => {
169+ options .headers = new Headers ({
170+ ... options .headers ,
171+ Accept: ' application/json' ,
172+ }) as Headers ;
173+
174+ const token = getAccessToken ();
175+ options .user = { token: ` Bearer ${token } ` , authenticated: !! token };
176+
177+ return await fetchUtils .fetchJson (url , options );
178+ };
179+
180+ const jsonDataProvider = openApiDataProvider ({
181+ dataProvider: simpleRestProvider (API_ENTRYPOINT_PATH , httpClient ),
182+ entrypoint: API_ENTRYPOINT_PATH ,
183+ docEntrypoint: API_DOCS_PATH ,
184+ });
185+ ```
186+
187+ > [ !NOTE]
188+ > The ` simpleRestProvider ` provider expect the API to include a ` Content-Range ` header in the response.
189+ > 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 ) .
190+ >
191+ > The ` getAccessToken ` function retrieves the JWT token stored in the browser.
192+
193+ ### Authentication and Authorization
194+
195+ Create and export an ` authProvider ` object that handles authentication and authorization logic.
196+
197+ ** File:** ` src/components/authProvider.tsx `
198+ ``` typescript
199+ interface JwtPayload {
200+ exp? : number ;
201+ iat? : number ;
202+ roles: string [];
203+ username: string ;
204+ }
205+
206+ const authProvider = {
207+ login : async ({username , password }: { username: string ; password: string }) => {
208+ const request = new Request (API_AUTH_PATH , {
209+ method: " POST" ,
210+ body: JSON .stringify ({ email: username , password }),
211+ headers: new Headers ({ " Content-Type" : " application/json" }),
212+ });
213+
214+ const response = await fetch (request );
215+
216+ if (response .status < 200 || response .status >= 300 ) {
217+ throw new Error (response .statusText );
218+ }
219+
220+ const auth = await response .json ();
221+ localStorage .setItem (" token" , auth .token );
222+ },
223+ logout : () => {
224+ localStorage .removeItem (" token" );
225+ return Promise .resolve ();
226+ },
227+ checkAuth : () => getAccessToken () ? Promise .resolve () : Promise .reject (),
228+ checkError : (error : { status: number }) => {
229+ const status = error .status ;
230+ if (status === 401 || status === 403 ) {
231+ localStorage .removeItem (" token" );
232+ return Promise .reject ();
233+ }
234+
235+ return Promise .resolve ();
236+ },
237+ getIdentity : () => {
238+ const token = getAccessToken ();
239+
240+ if (! token ) return Promise .reject ();
241+
242+ const decoded = jwtDecode <JwtPayload >(token );
243+
244+ return Promise .resolve ({
245+ id: " " ,
246+ fullName: decoded .username ,
247+ avatar: " " ,
248+ });
249+ },
250+ getPermissions : () => Promise .resolve (" " ),
251+ };
252+
253+ export default authProvider ;
254+ ```
255+
256+ ### Export OpenApiAdmin Component
257+
258+ ** File:** ` src/App.tsx `
259+ ``` typescript
260+ import {OpenApiAdmin } from ' @api-platform/admin' ;
261+ import authProvider from " ./components/authProvider" ;
262+ import jsonDataProvider from " ./components/jsonDataProvider" ;
263+ import {API_DOCS_PATH , API_ENTRYPOINT_PATH } from " ./config/api" ;
264+
265+ export default () => (
266+ < OpenApiAdmin
267+ entrypoint = {API_ENTRYPOINT_PATH }
268+ docEntrypoint = {API_DOCS_PATH }
269+ dataProvider = {jsonDataProvider }
270+ authProvider = {authProvider }
271+ / >
272+ );
273+ ```
0 commit comments