encore
encore copied to clipboard
Generated Client `mustBeSet` check fails for `Set-Cookie`
Hey all! Loving Encore TS! I ran into an issue with the generated client this evening.
I have an AuthResponse defined in the following way:
export interface AuthResponse {
cookies: Header<'Set-Cookie'>
user: User
}
When generated, it outputs the following:
public async login(params: LoginParams): Promise<AuthResponse> {
// Now make the actual call to the API
const resp = await this.baseClient.callAPI("POST", `/auth/login`, JSON.stringify(params))
//Populate the return object from the JSON body and received headers
const rtn = await resp.json() as AuthResponse
rtn.cookies = mustBeSet("Header `set-cookie`", resp.headers.get("set-cookie"))
return rtn
}
The mustBeSet call for set-cookie is failing because Set-Cookie is a forbidden response header. Encore itself is setting that header correctly, but the generated client should not expect to be able to access the Set-Cookie header.
any solution for setting cookie headers from encore backend ?
Also looking for solutions to setting cookies from encore.dev and reading same cookies in other api calls.
@eandre
Also waiting for fix, but here's our temporary solution:
import { middleware } from "encore.dev/api";
/**
* Data type for storing Set-Cookie values.
* Can be either a single string or an array of strings.
*/
export type SetCookie = string | string[];
/**
* Middleware for correctly setting Set-Cookie headers in the response.
*
* @param setCookieResponseFieldName - The name of the field in the payload from which Set-Cookie values will be extracted (default "setCookie")
* @returns Middleware function that modifies the response by adding Set-Cookie headers
*
* @example
* ```ts
* // Example of middleware usage
* export default new Service("somService", {
* middlewares: [
* setCookieMiddleware(),
* ],
* });
*
* // Use the SetCookie type to declare the setCookie field in the response
* export type SomeResponse = {
* setCookie?: SetCookie;
* }
*
* // In the controller code, simply add cookie data to the response
* const refreshTokenCookie = serializeCookie('refreshToken', refreshToken, { maxAge: '2d' });
* const deviceIdCookie: serializeCookie('deviceId', deviceId, { maxAge: '2d' });
*
* const response = { setCookie: [refreshTokenCookie, deviceIdCookie]; };
*
* ```
*
* @remarks
* The current implementation is necessary due to a known bug in the typing system (https://github.com/encoredev/encore/issues/1092).
* After fixing the bug, it will be possible to use typing without middleware.
*/
export const setCookieMiddleware = (setCookieResponseFieldName: string = "setCookie") => {
return middleware({ target: {} }, async (req, next) => {
const resp = await next(req);
if (setCookieResponseFieldName in resp.payload) {
const setCookieValues = resp.payload[setCookieResponseFieldName] as SetCookie;
resp.header.set('Set-Cookie', setCookieValues);
delete resp.payload[setCookieResponseFieldName];
}
return resp;
})
}
And serializeCookie looks like:
import c from 'cookie';
import type { StringValue } from 'ms';
import ms from 'ms';
export type CookieOptions = Omit<c.SerializeOptions, 'maxAge'> & {
maxAge?: string
}
const defaultOption: CookieOptions = {
sameSite: 'lax',
path: '/',
secure: false,
httpOnly: true,
maxAge: '1d',
}
export const serializeCookie = (name: string, value: string, options?: CookieOptions) => {
const maxAge = options?.maxAge ?? defaultOption.maxAge!;
return c.serialize(name, value, {
...defaultOption,
...options,
maxAge: Math.ceil(ms(maxAge as StringValue) / 1000),
});
}
Thanks for this @roman-komarov , seems I'll have to rely on the middleware the return and extract the cookies
Hey guys, this is a pretty neat and simple approach to setting and reading cookies. Works for me, tested and approved https://github.com/encoredev/encore/issues/1895
As of encore v1.48.0 we no longer run mustBeSet for the set-cookie header in a browser context. But we've also added support for adding cookies in your api schemas, see https://encore.dev/docs/ts/primitives/cookies