Saltar al contenido principal

Autenticación REST

Todas las peticiones de red pasan por el getRequestInit que opcionalmente defines en tu RestEndpoint.

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',
    };
  }
}
▶Usage
import { MyResource } from './MyResource';
MyResource.get({ id: 1 });
Request
GET /my/1
content-type: application/json
cookie: session=abc;
Response200 OK
{
"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​

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;
};
▶Usage
import { MyResource } from './MyResource';
MyResource.get({ id: 1 });
Request
GET /my/1
access-token: mytoken
content-type: application/json
Response200 OK
{
"id": "1",
"title": "this post"
}

Encabezados de autenticación desde React Context​

aviso

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.

Podemos transformar cualquier Resource en uno que use hooks para crear endpoints mediante hookifyResource

resources/Post.ts
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>;
}
aviso

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>
);
}

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.