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:

api/AuthdEndpoint.ts
import { RestEndpoint } from '@data-client/rest';

export default class AuthdEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
getRequestInit(body: any): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'same-origin',
};
}
}
api/MyResource.ts
import { createResource, Entity } from '@data-client/rest';
import AuthdEndpoint from 'api/AuthdEndpoint';

class MyEntity extends Entity {
/* Define MyEntity */
}

export const MyResource = createResource({
path: '/my/:id',
schema: MyEntity,
Endpoint: AuthdEndpoint,
});

Access Tokens or JWT

api/AuthdEndpoint.ts
import { RestEndpoint } from '@data-client/rest';

export default class AuthdEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
declare static accessToken?: string;

getHeaders(headers: HeadersInit) {
return {
...headers,
'Access-Token': this.constructor.accessToken,
};
}
}

Upon login we set the token:

Auth.tsx
import AuthdEndpoint from 'api/AuthdEndpoint';

function Auth() {
const handleLogin = useCallback(
async e => {
const { accessToken } = await login(new FormData(e.target));
// success!
AuthdEndpoint.accessToken = accessToken;
},
[login],
);

return <AuthForm onSubmit={handleLogin} />;
}
api/MyResource.ts
import { createResource, Entity } from '@data-client/rest';
import AuthdEndpoint from 'api/AuthdEndpoint';

class MyEntity extends Entity {
/* Define MyEntity */
}

export const MyResource = createResource({
path: '/my/:id',
schema: MyEntity,
Endpoint: AuthdEndpoint,
});

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.