Skip to content

Commit

Permalink
docs(admin): add OpenApiAdmin section in auth-support
Browse files Browse the repository at this point in the history
  • Loading branch information
igornast committed Jan 14, 2025
1 parent 130be1f commit 50e0933
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 63 deletions.
134 changes: 130 additions & 4 deletions admin/authentication-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<JwtPayload>(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 () => (
<OpenApiAdmin
entrypoint={API_ENTRYPOINT_PATH}
docEntrypoint={API_DOCS_PATH}
dataProvider={jsonDataProvider}
authProvider={authProvider}
/>
);
```
59 changes: 0 additions & 59 deletions admin/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => (
<OpenApiAdmin
entrypoint={API_URL}
docEntrypoint={DOC_URL}
dataProvider={jsonDataProvider}
/>
);

```

> [!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.
Expand Down

0 comments on commit 50e0933

Please sign in to comment.