Autenticación REST
Todas las peticiones de red pasan por el getRequestInit que opcionalmente defines en tu RestEndpoint.
Autenticación con cookies (credentials)
Este es un ejemplo de autenticación simple con cookies que envía las credenciales de fetch:
import { RestEndpoint, type RestGenerics } from '@data-client/rest'; export default class AuthdEndpoint< O extends RestGenerics = any, > extends RestEndpoint<O> { async getRequestInit(body: any): Promise<RequestInit> { return { ...(await super.getRequestInit(body)), credentials: 'same-origin', }; } }
import { MyResource } from './MyResource'; MyResource.get({ id: 1 });
GET /my/1
content-type: application/json
cookie: session=abc;
{
"id": "1",
"title": "this post"
}
Consulta la integración con Django para ver un ejemplo que también incluye protección CSRF.
Tokens de acceso o JWT
- static member
- function singleton
- async function
import { RestEndpoint, type RestGenerics } from '@data-client/rest'; import { login } from './login'; export default class AuthdEndpoint< O extends RestGenerics = any, > extends RestEndpoint<O> { declare static accessToken?: string; getHeaders(headers: HeadersInit) { // TypeScript doesn't infer properly const EP = this.constructor as typeof AuthdEndpoint; if (!EP.accessToken) return headers; return { ...headers, 'Access-Token': EP.accessToken, }; } } export const handleLogin = async e => { const { accessToken } = await login(new FormData(e.target)); AuthdEndpoint.accessToken = accessToken; };
import { MyResource } from './MyResource'; MyResource.get({ id: 1 });
GET /my/1
access-token: mytoken
content-type: application/json
{
"id": "1",
"title": "this post"
}
import { RestEndpoint, type RestGenerics } from '@data-client/rest'; import { getAuthToken, setAuthToken, login } from './login'; export default class AuthdEndpoint< O extends RestGenerics = any, > extends RestEndpoint<O> { async getHeaders(headers: HeadersInit) { return { ...headers, 'Access-Token': await getAuthToken(), }; } } export const handleLogin = async e => { const { accessToken } = await login(new FormData(e.target)); setAuthToken(accessToken); };
import { MyResource } from './MyResource'; MyResource.get({ id: 1 });
GET /my/1
access-token: mytoken
content-type: application/json
{
"id": "1",
"title": "this post"
}
import { RestEndpoint, type RestGenerics } from '@data-client/rest'; import { getAuthToken, setAuthToken, login } from './login'; export default class AuthdEndpoint< O extends RestGenerics = any, > extends RestEndpoint<O> { getHeaders(headers: HeadersInit) { return { ...headers, 'Access-Token': getAuthToken(), }; } } export const handleLogin = async e => { const { accessToken } = await login(new FormData(e.target)); setAuthToken(accessToken); };
import { MyResource } from './MyResource'; MyResource.get({ id: 1 });
GET /my/1
access-token: mytoken
content-type: application/json
{
"id": "1",
"title": "this post"
}
Encabezados de autenticación desde React Context
No se recomienda usar React Context para estado que no se muestra (como los tokens de autenticación). Esto provocará re-renders innecesarios y complejidad en la aplicación.
- Resource
- RestEndpoint
Podemos transformar cualquier Resource en uno que use hooks para crear endpoints mediante hookifyResource
import { Entity, resource, hookifyResource } from '@data-client/rest';
import { useAuthContext } from '../AuthContext';
class Post extends Entity {
id = '';
title = '';
}
export const PostResource = hookifyResource(
resource({ path: '/posts/:id', schema: Post }),
function useInit(): RequestInit {
const accessToken = useAuthContext();
return {
headers: {
'Access-Token': accessToken,
},
};
},
);
Luego podemos obtener los endpoints como hooks en nuestros componentes de React
import { useSuspense } from '@data-client/react';
import { PostResource } from 'resources/Post';
function PostDetail({ id }) {
const post = useSuspense(PostResource.useGet(), { id });
return <div>{post.title}</div>;
}
Usar esto significa que todas las llamadas a endpoints solo pueden ocurrir durante el renderizado de una función.
import { useController } from '@data-client/react';
import { PostResource } from './resources/Post';
function CreatePost() {
const controller = useController();
const createPost = PostResource.useCreate();
return (
<form
onSubmit={e =>
controller.fetch(createPost, new FormData(e.currentTarget))
}
>
{/* ... */}
</form>
);
}
Primero daremos una forma sencilla de usar el contexto para modificar los encabezados del fetch.
import { RestEndpoint, type RestGenerics } from '@data-client/rest';
export default class AuthdEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
declare accessToken?: string;
getHeaders(headers: HeadersInit): HeadersInit {
return {
...headers,
'Access-Token': this.accessToken,
};
}
}
A continuación usaremos extend para generar un nuevo endpoint con este contexto inyectado.
import { useMemo } from 'react';
import type { IRestEndpoint } from '@data-client/rest';
import { useAuthContext } from './AuthContext';
function useEndpoint(endpoint: IRestEndpoint) {
const accessToken = useAuthContext();
return useMemo(
() => endpoint.extend({ accessToken }),
[endpoint, accessToken],
);
}
Usar esto significa que todas las llamadas a endpoints solo pueden ocurrir durante el renderizado de una función.
import { useController } from '@data-client/react';
import { PostResource } from './api/Post';
import { useEndpoint } from './useEndpoint';
function CreatePost() {
const controller = useController();
const createPost = useEndpoint(PostResource.create);
return (
<form
onSubmit={e =>
controller.fetch(createPost, {}, new FormData(e.target))
}
>
{/* ... */}
</form>
);
}
Organización del código
Si muchos de tus Resources comparten un mecanismo de autenticación similar, puedes
intentar extender de una clase base que defina esas personalizaciones comunes.
Manejo del cierre de sesión por 401
Si la autorización de un usuario caduca, el servidor normalmente responderá para indicarlo. La forma estándar de hacerlo es con un 401. LogoutManager se puede usar para activar fácilmente cualquier limpieza de desautorización.