Autenticação no Rest
Todas as requisições de rede passam pelo getRequestInit que pode ser definido no seu RestEndpoint.
Autenticação por cookie (credentials)
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', }; } }
import { MyResource } from './MyResource'; MyResource.get({ id: 1 });
GET /my/1
content-type: application/json
cookie: session=abc;
{
"id": "1",
"title": "this post"
}
Veja Integração com Django para um exemplo que também inclui proteção CSRF.
Access tokens ou 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"
}
Headers de autenticação a partir de React Context
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.
- Resource
- RestEndpoint
Podemos transformar qualquer Resource em um que usa hooks para criar endpoints usando 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,
},
};
},
);
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>;
}
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>
);
}
Primeiro, vamos oferecer uma forma simples de usar o contexto para alterar os headers do 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,
};
}
}
Em seguida, vamos usar extend para gerar um novo endpoint com esse contexto injetado.
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 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 './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>
);
}
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.