Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("index exports", () => {
"mapLoginMethodParamsForUrl",
"sanitizeUrl",
"exchangeAuthCode",
"getCookieOptions",
"isAuthenticated",
"isTokenExpired",
"refreshToken",
Expand Down
2 changes: 2 additions & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export {
sessionManagerActivityProxy,
isClient,
isServer,
getCookieOptions,
} from "./utils";
export type { CookieOptions } from "./utils";

export {
getClaim,
Expand Down
74 changes: 74 additions & 0 deletions lib/utils/getCookieOptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, it, expect } from "vitest";

import {
getCookieOptions,
TWENTY_NINE_DAYS,
GLOBAL_COOKIE_OPTIONS,
} from "./getCookieOptions";
import { storageSettings } from "../sessionManager/index";

describe("getCookieOptions", () => {
it("returns the default configuration when no options provided", () => {
const result = getCookieOptions();

expect(result).toMatchObject({
maxAge: TWENTY_NINE_DAYS,
maxCookieLength: storageSettings.maxLength,
sameSite: "lax",
httpOnly: true,
path: "/",
});
});

it("allows consumers to override default options", () => {
const result = getCookieOptions({
secure: true,
sameSite: "none",
path: "/custom",
maxAge: 60,
domain: "example.com",
});

expect(result.secure).toBe(true);
expect(result.sameSite).toBe("none");
expect(result.path).toBe("/custom");
expect(result.maxAge).toBe(60);
expect(result.domain).toBe("example.com");
});

it("preserves GLOBAL_COOKIE_OPTIONS when not overridden", () => {
const result = getCookieOptions({ domain: "test.com" });

expect(result.httpOnly).toBe(GLOBAL_COOKIE_OPTIONS.httpOnly);
expect(result.sameSite).toBe(GLOBAL_COOKIE_OPTIONS.sameSite);
expect(result.path).toBe(GLOBAL_COOKIE_OPTIONS.path);
expect(result.maxAge).toBe(GLOBAL_COOKIE_OPTIONS.maxAge);
expect(result.maxCookieLength).toBe(GLOBAL_COOKIE_OPTIONS.maxCookieLength);
expect(result.domain).toBe("test.com");
});

it("user options take precedence over defaults", () => {
const result = getCookieOptions({
httpOnly: false,
maxAge: 1000,
});

expect(result.httpOnly).toBe(false);
expect(result.maxAge).toBe(1000);
// Other defaults remain
expect(result.sameSite).toBe("lax");
expect(result.path).toBe("/");
});
});

describe("GLOBAL_COOKIE_OPTIONS", () => {
it("contains secure defaults", () => {
expect(GLOBAL_COOKIE_OPTIONS.httpOnly).toBe(true);
expect(GLOBAL_COOKIE_OPTIONS.sameSite).toBe("lax");
expect(GLOBAL_COOKIE_OPTIONS.path).toBe("/");
expect(GLOBAL_COOKIE_OPTIONS.maxAge).toBe(TWENTY_NINE_DAYS);
expect(GLOBAL_COOKIE_OPTIONS.maxCookieLength).toBe(
storageSettings.maxLength,
);
});
});
59 changes: 59 additions & 0 deletions lib/utils/getCookieOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { storageSettings } from "../sessionManager/index.js";

export interface CookieOptions {
maxAge?: number;
domain?: string;
maxCookieLength?: number;
sameSite?: string;
httpOnly?: boolean;
secure?: boolean;
path?: string;
}

export const TWENTY_NINE_DAYS = 2505600;

/**
* Default cookie options used across Kinde SDKs.
*
* **Security Note:** The `secure` flag is intentionally omitted to support:
* - Framework-agnostic usage across different environments
* - Local development over HTTP (localhost)
*
* Warning: For production deployments using HTTPS, consumers must explicitly
* set `secure: true` via `getCookieOptions({ secure: true })` to ensure
* cookies are only transmitted over secure connections.
*/
export const GLOBAL_COOKIE_OPTIONS: CookieOptions = {
maxAge: TWENTY_NINE_DAYS,
maxCookieLength: storageSettings.maxLength,
sameSite: "lax",
httpOnly: true,
path: "/",
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Returns cookie options by merging provided options with secure defaults.
*
* @param options - Custom cookie options to override defaults
* @returns Merged cookie options with GLOBAL_COOKIE_OPTIONS as base
*
* @example
* ```typescript
* // Development (HTTP)
* const devOptions = getCookieOptions();
*
* // Production (HTTPS) - must set secure: true
* const prodOptions = getCookieOptions({ secure: true, domain: ".example.com" });
* ```
*
* **Security Warning:** Always set `secure: true` in production environments
* using HTTPS to prevent cookie transmission over insecure connections.
*/
export const getCookieOptions = (
options: CookieOptions = {},
): CookieOptions => {
return {
...GLOBAL_COOKIE_OPTIONS,
...options,
};
};
2 changes: 2 additions & 0 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export {
frameworkSettings,
generateKindeSDKHeader,
} from "./exchangeAuthCode";
export { getCookieOptions } from "./getCookieOptions";
export type { CookieOptions } from "./getCookieOptions";
export { checkAuth } from "./checkAuth";
export { isCustomDomain } from "./isCustomDomain";
export { setRefreshTimer, clearRefreshTimer } from "./refreshTimer";
Expand Down