Skip to main content

Rest Authentication

All network requests are run through the getRequestInit optionally defined in your RestEndpoint.

Here's an example using simple cookie auth by sending fetch credentials:

import { RestEndpoint } 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 { createResource, Entity } from '@data-client/rest';
import AuthdEndpoint from './AuthdEndpoint';

class MyEntity extends Entity {
  id = '';
  title = '';
  pk() {
    return this.id;
  }
}

export const MyResource = createResource({
  path: '/my/:id',
  schema: MyEntity,
  Endpoint: AuthdEndpoint,
});
Request
import { MyResource } from './MyResource';
MyResource.get({ id: 1 });
Request
GET /my/1
Content-Type: application/json
Cookie: session=abc;
Response200
{
"id": "1",
"title": "this post"
}

Access Tokens or JWT

export const login = async (data: FormData) =>
  (
    await fetch('/login', { method: 'POST', body: data })
  ).json() as Promise<{
    accessToken: string;
  }>;
import { RestEndpoint } 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 { handleLogin } from './AuthdEndpoint';

export default function Auth() {
  return <AuthForm onSubmit={handleLogin} />;
}
import { createResource, Entity } from '@data-client/rest';
import AuthdEndpoint from './AuthdEndpoint';

class MyEntity extends Entity {
  id = '';
  title = '';
  pk() {
    return this.id;
  }
}

export const MyResource = createResource({
  path: '/my/:id',
  schema: MyEntity,
  Endpoint: AuthdEndpoint,
});
Request
import { MyResource } from './MyResource';
MyResource.get({ id: 1 });
Request
GET /my/1
Content-Type: application/json
Access-Token: mytoken
Response200
{
"id": "1",
"title": "this post"
}

Auth Headers from React Context

warning

Using React Context for state that is not displayed (like auth tokens) is not recommended. This will result in unnecessary re-renders and application complexity.

We can transform any Resource into one that uses hooks to create endpoints by using hookifyResource

api/Post.ts
import { createResource, hookifyResource } from '@data-client/rest';

// Post defined here

export const PostResource = hookifyResource(
createResource({ path: '/posts/:id', schema: Post }),
function useInit(): RequestInit {
const accessToken = useAuthContext();
return {
headers: {
'Access-Token': accessToken,
},
};
},
);

Then we can get the endpoints as hooks in our React Components

import { useSuspense } from '@data-client/react';
import { PostResource } from 'api/Post';

function PostDetail({ id }) {
const post = useSuspense(PostResource.useGet(), { id });
return <div>{post.title}</div>;
}
warning

Using this means all endpoint calls must only occur during a function render.

function CreatePost() {
const controller = useController();
const createPost = PostResource.useCreate();

return (
<form
onSubmit={e => controller.fetch(createPost, new FormData(e.target))}
>
{/* ... */}
</form>
);
}

Code organization

If much of your Resources share a similar auth mechanism, you might try extending from a base class that defines such common customizations.

401 Logout Handling

In case a users authorization expires, the server will typically responsd to indicate as such. The standard way of doing this is with a 401. LogoutManager can be used to easily trigger any de-authorization cleanup.