get cookies in api client
Unanswered
Evanion posted this in #help-forum
EvanionOP
I'm working on a site that has a separate backend that returns a token, and Next stores that in the users cookies, I'm using mappersmith to create a mapped client of the API. And I have a mappersmith middleware that uses my
but I'm getting errors that I can't use the
I'm only using mappersmith to make better DX for data fetching in server actions. So it's never run client side.
getToken server action, and includes it in the header of the outgoing request.but I'm getting errors that I can't use the
cookies() object outside of Server actions or Route handlers. The mappersmith client is always called from a server action, and I don't want to have to manually read and set the header each time I make a request.I'm only using mappersmith to make better DX for data fetching in server actions. So it's never run client side.
2 Replies
EvanionOP
Here is some example code:
client.ts:
auth.middleware.ts
client.ts:
import forge, { configs } from 'mappersmith';
import { Fetch } from 'mappersmith/gateway/fetch';
import { authMiddleware } from './middleware/auth.middleware';
configs.gateway = Fetch;
export const api = forge({
clientId: 'backend',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
host: process.env.API_URL!,
middleware: [authMiddleware],
resources: {
Auth: {
signUp: { method: 'POST', path: '/authentication/sign-up' },
signIn: { method: 'POST', path: '/authentication/sign-in' },
signOut: { method: 'PUT', path: '/authentication/sign-out' },
refreshTokens: {
method: 'POST',
path: '/authentication/refresh-tokens',
headers: { 'Content-Type': 'application/json' },
},
googleAuth: { method: 'POST', path: '/authentication/google' },
},
Users: {
me: { method: 'GET', path: '/users/me' },
checkUsername: {
method: 'GET',
path: '/users/username/{username}/check',
},
getUserByUsername: { method: 'GET', path: '/users/username/{username}' },
},
Pages: {
all: { method: 'GET', path: '/pages' },
byId: { method: 'GET', path: '/pages/{id}' },
bySlug: { method: 'GET', path: '/pages/slug/{slug}' },
create: { method: 'POST', path: '/pages' },
update: { method: 'PATCH', path: '/pages/{id}' },
remove: { method: 'DELETE', path: '/pages/{id}' },
},
Courses: {
all: { method: 'GET', path: '/courses' },
byId: { method: 'GET', path: '/courses/{id}' },
bySlug: { method: 'GET', path: '/courses/slug/{slug}' },
create: { method: 'POST', path: '/courses' },
update: { method: 'PATCH', path: '/courses/{id}' },
remove: { method: 'DELETE', path: '/courses/{id}' },
},
},
});auth.middleware.ts
import { type Middleware } from 'mappersmith';
import { getToken } from '../../actions';
export const authMiddleware: Middleware = () => ({
async prepareRequest(next) {
const token = await getToken();
return next().then((request) => {
if (!token) return request;
return request.enhance({
headers: {
authorization: `Bearer ${token}`,
},
});
});
},
});get-tokens.ts
get-current-user.ts
/* eslint-disable @typescript-eslint/no-non-null-assertion */
'use server';
import { cookies } from 'next/headers';
import { Tokens } from '../types';
import { TokenPayload } from '../types/token-payloads';
const SECOND = 1000;
const MINUTE = 60 * SECOND;
/**
* Returns the access token to use for API requests against the backend
* If the token is expired, it will be refreshed
* If it's unable to refresh the token, it will return undefined
* @returns accessToken
*/
export async function getToken(): Promise<string | undefined> {
const cookieStore = cookies();
const token = cookieStore.get('token');
const refreshToken = cookieStore.get('refreshToken');
if (token) {
// check if token is expiring
const payload: TokenPayload = JSON.parse(atob(token.value.split('.')[1]));
const exp = payload.exp;
const now = Date.now();
// if token have more than 1 minute left, return it
if (now - exp > MINUTE) return token.value;
}
// if no refresh token, we can't refresh the token
if (!refreshToken) return;
try {
// using fetch to avoid a circular loop with the auth middleware
const tokens = await fetch(
`${process.env.API_URL}/authentication/refresh-tokens`,
{
method: 'POST',
body: JSON.stringify({ refreshToken: refreshToken.value }),
}
).then((res) => res.json() as Promise<Tokens>);
// if unable to refresh token, return undefined
if (!tokens) return;
// set the new tokens
cookieStore.set('token', tokens.accessToken, {
httpOnly: true,
maxAge: parseInt(process.env.JWT_ACCESS_TOKEN_TTL!, 10),
});
cookieStore.set('refreshToken', tokens.refreshToken, {
httpOnly: true,
maxAge: parseInt(process.env.JWT_REFRESH_TOKEN_TTL!, 10),
});
cookieStore.set('user', JSON.stringify(tokens.user), {
httpOnly: false,
});
return tokens.accessToken;
} catch (e) {
console.error('Failed to refresh tokens', e);
return undefined;
}
}get-current-user.ts
'use server';
import { plainToInstance } from 'class-transformer';
import { User } from '@codesmith/entities';
import { api } from '../api';
export async function getCurrentUser(): Promise<User | undefined> {
try {
const user = (await api.Users.me()).data<User>();
return plainToInstance(User, user);
} catch (e) {
return undefined;
}
}