跳到主要内容

Rest 身份验证

所有网络请求都会经过你在 RestEndpoint 中可选定义的 getRequestInit。

下面是一个通过发送 fetch credentials 实现简单 cookie 认证的示例:

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 });
请求
GET /my/1
content-type: application/json
cookie: session=abc;
响应200 OK
{
"id": "1",
"title": "this post"
}

同时包含 CSRF 防护的示例请参阅 Django 集成。

Access Token 或 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 });
请求
GET /my/1
access-token: mytoken
content-type: application/json
响应200 OK
{
"id": "1",
"title": "this post"
}

从 React Context 获取认证头​

注意

不建议将 React Context 用于不需要显示的状态(例如认证 token)。这会导致不必要的重新渲染,并增加应用的复杂度。

借助 hookifyResource,我们可以把任意 Resource 转换为使用 hook 来创建 endpoint 的版本

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

然后就可以在 React 组件中以 hook 的形式获取这些 endpoint

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>;
}
注意

使用这种方式意味着所有 endpoint 调用都只能发生在函数渲染期间。

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

代码组织​

如果你的大部分 Resources 都使用类似的认证机制,可以尝试从一个定义了这些通用定制的基类进行扩展。

401 登出处理​

当用户的授权过期时,服务器通常会在响应中予以表明,标准做法是返回 401。LogoutManager 可以轻松触发所有取消授权的清理工作。