Pular para o conteúdo principal

Autenticação no Rest

Todas as requisições de rede passam pelo getRequestInit que pode ser definido no seu RestEndpoint.

Aqui está um exemplo de autenticação simples por cookie, enviando credenciais no 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"
}

Veja Integração com Django para um exemplo que também inclui proteção CSRF.

Access tokens ou 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"
}

Headers de autenticação a partir de React Context​

aviso

Não é recomendado usar React Context para estado que não é exibido (como tokens de autenticação). Isso resulta em re-renderizações desnecessárias e em mais complexidade na aplicação.

Podemos transformar qualquer Resource em um que usa hooks para criar endpoints usando 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,
},
};
},
);

Então podemos obter os endpoints como hooks em nossos componentes 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 isso significa que todas as chamadas de endpoint só podem ocorrer durante a renderização de uma função.

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

Organização do código​

Se boa parte dos seus Resources compartilha um mecanismo de autenticação semelhante, você pode tentar estender uma classe base que defina essas personalizações comuns.

Tratamento de logout em 401​

Caso a autorização de um usuário expire, o servidor normalmente responde indicando isso. A forma padrão de fazer isso é com um 401. O LogoutManager pode ser usado para disparar facilmente qualquer limpeza de desautorização.