Skip to content

Commit caed773

Browse files
committed
added createSessionCookie
1 parent 6e151bf commit caed773

12 files changed

+643
-2
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
],
1515
"scripts": {
1616
"test": "vitest run",
17+
"test-with-emulator": "firebase emulators:exec --project example-project12345 'vitest run'",
1718
"build": "run-p build:*",
1819
"build:main": "tsc -p tsconfig.main.json",
1920
"build:module": "tsc -p tsconfig.module.json",
@@ -40,6 +41,7 @@
4041
"npm-run-all": "^4.1.5",
4142
"prettier": "^3.2.5",
4243
"typescript": "^5.3.3",
44+
"undici": "^6.6.2",
4345
"vitest": "^1.3.0",
4446
"wrangler": "^3.28.3"
4547
},

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api-requests.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/** Http method type definition. */
2+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
3+
/** API callback function type definition. */
4+
export type ApiCallbackFunction = (data: object) => void;
5+
6+
/**
7+
* Class that defines all the settings for the backend API endpoint.
8+
*
9+
* @param endpoint - The Firebase Auth backend endpoint.
10+
* @param httpMethod - The http method for that endpoint.
11+
* @constructor
12+
*/
13+
export class ApiSettings {
14+
private requestValidator: ApiCallbackFunction;
15+
private responseValidator: ApiCallbackFunction;
16+
17+
constructor(
18+
private version: 'v1' | 'v2',
19+
private endpoint: string,
20+
private httpMethod: HttpMethod = 'POST'
21+
) {
22+
this.setRequestValidator(null).setResponseValidator(null);
23+
}
24+
25+
/** @returns The backend API resource version. */
26+
public getVersion(): 'v1' | 'v2' {
27+
return this.version;
28+
}
29+
30+
/** @returns The backend API endpoint. */
31+
public getEndpoint(): string {
32+
return this.endpoint;
33+
}
34+
35+
/** @returns The request HTTP method. */
36+
public getHttpMethod(): HttpMethod {
37+
return this.httpMethod;
38+
}
39+
40+
/**
41+
* @param requestValidator - The request validator.
42+
* @returns The current API settings instance.
43+
*/
44+
public setRequestValidator(requestValidator: ApiCallbackFunction | null): ApiSettings {
45+
const nullFunction: ApiCallbackFunction = () => undefined;
46+
this.requestValidator = requestValidator || nullFunction;
47+
return this;
48+
}
49+
50+
/** @returns The request validator. */
51+
public getRequestValidator(): ApiCallbackFunction {
52+
return this.requestValidator;
53+
}
54+
55+
/**
56+
* @param responseValidator - The response validator.
57+
* @returns The current API settings instance.
58+
*/
59+
public setResponseValidator(responseValidator: ApiCallbackFunction | null): ApiSettings {
60+
const nullFunction: ApiCallbackFunction = () => undefined;
61+
this.responseValidator = responseValidator || nullFunction;
62+
return this;
63+
}
64+
65+
/** @returns The response validator. */
66+
public getResponseValidator(): ApiCallbackFunction {
67+
return this.responseValidator;
68+
}
69+
}

src/auth-api-requests.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ApiSettings } from './api-requests';
2+
import { BaseClient } from './client';
3+
import type { EmulatorEnv } from './emulator';
4+
import { AuthClientErrorCode, FirebaseAuthError } from './errors';
5+
import { isNonEmptyString, isNumber } from './validator';
6+
7+
/** Minimum allowed session cookie duration in seconds (5 minutes). */
8+
const MIN_SESSION_COOKIE_DURATION_SECS = 5 * 60;
9+
10+
/** Maximum allowed session cookie duration in seconds (2 weeks). */
11+
const MAX_SESSION_COOKIE_DURATION_SECS = 14 * 24 * 60 * 60;
12+
13+
/**
14+
* Instantiates the createSessionCookie endpoint settings.
15+
*
16+
* @internal
17+
*/
18+
export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = new ApiSettings('v1', ':createSessionCookie', 'POST')
19+
// Set request validator.
20+
.setRequestValidator((request: any) => {
21+
// Validate the ID token is a non-empty string.
22+
if (!isNonEmptyString(request.idToken)) {
23+
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN);
24+
}
25+
// Validate the custom session cookie duration.
26+
if (
27+
!isNumber(request.validDuration) ||
28+
request.validDuration < MIN_SESSION_COOKIE_DURATION_SECS ||
29+
request.validDuration > MAX_SESSION_COOKIE_DURATION_SECS
30+
) {
31+
throw new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION);
32+
}
33+
})
34+
// Set response validator.
35+
.setResponseValidator((response: any) => {
36+
// Response should always contain the session cookie.
37+
if (!isNonEmptyString(response.sessionCookie)) {
38+
throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR);
39+
}
40+
});
41+
42+
export class AuthApiClient extends BaseClient {
43+
/**
44+
* Creates a new Firebase session cookie with the specified duration that can be used for
45+
* session management (set as a server side session cookie with custom cookie policy).
46+
* The session cookie JWT will have the same payload claims as the provided ID token.
47+
*
48+
* @param idToken - The Firebase ID token to exchange for a session cookie.
49+
* @param expiresIn - The session cookie duration in milliseconds.
50+
* @param - An optional parameter specifying the environment in which the function is running.
51+
* If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
52+
* If not specified, the function will assume it is running in a production environment.
53+
*
54+
* @returns A promise that resolves on success with the created session cookie.
55+
*/
56+
public async createSessionCookie(idToken: string, expiresIn: number, env?: EmulatorEnv): Promise<string> {
57+
const request = {
58+
idToken,
59+
// Convert to seconds.
60+
validDuration: expiresIn / 1000,
61+
};
62+
return await this.fetch(FIREBASE_AUTH_CREATE_SESSION_COOKIE, request, env);
63+
}
64+
}

src/auth.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
import { AuthApiClient } from './auth-api-requests';
12
import type { EmulatorEnv } from './emulator';
23
import { useEmulator } from './emulator';
4+
import { AuthClientErrorCode, FirebaseAuthError } from './errors';
35
import type { KeyStorer } from './key-store';
46
import type { FirebaseIdToken, FirebaseTokenVerifier } from './token-verifier';
57
import { createIdTokenVerifier, createSessionCookieVerifier } from './token-verifier';
8+
import { isNonNullObject, isNumber } from './validator';
69

710
export class BaseAuth {
811
/** @internal */
912
protected readonly idTokenVerifier: FirebaseTokenVerifier;
1013
protected readonly sessionCookieVerifier: FirebaseTokenVerifier;
14+
private readonly authApiClient: AuthApiClient;
1115

1216
constructor(projectId: string, keyStore: KeyStorer) {
1317
this.idTokenVerifier = createIdTokenVerifier(projectId, keyStore);
1418
this.sessionCookieVerifier = createSessionCookieVerifier(projectId, keyStore);
19+
this.authApiClient = new AuthApiClient(projectId);
1520
}
1621

1722
/**
@@ -31,6 +36,38 @@ export class BaseAuth {
3136
return this.idTokenVerifier.verifyJWT(idToken, isEmulator);
3237
}
3338

39+
/**
40+
* Creates a new Firebase session cookie with the specified options. The created
41+
* JWT string can be set as a server-side session cookie with a custom cookie
42+
* policy, and be used for session management. The session cookie JWT will have
43+
* the same payload claims as the provided ID token.
44+
*
45+
* See {@link https://firebase.google.com/docs/auth/admin/manage-cookies | Manage Session Cookies}
46+
* for code samples and detailed documentation.
47+
*
48+
* @param idToken - The Firebase ID token to exchange for a session
49+
* cookie.
50+
* @param sessionCookieOptions - The session
51+
* cookie options which includes custom session duration.
52+
* @param env - An optional parameter specifying the environment in which the function is running.
53+
* If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
54+
* If not specified, the function will assume it is running in a production environment.
55+
*
56+
* @returns A promise that resolves on success with the
57+
* created session cookie.
58+
*/
59+
public createSessionCookie(
60+
idToken: string,
61+
sessionCookieOptions: SessionCookieOptions,
62+
env?: EmulatorEnv
63+
): Promise<string> {
64+
// Return rejected promise if expiresIn is not available.
65+
if (!isNonNullObject(sessionCookieOptions) || !isNumber(sessionCookieOptions.expiresIn)) {
66+
return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION));
67+
}
68+
return this.authApiClient.createSessionCookie(idToken, sessionCookieOptions.expiresIn, env);
69+
}
70+
3471
/**
3572
* Verifies a Firebase session cookie. Returns a Promise with the cookie claims.
3673
* Rejects the promise if the cookie could not be verified.
@@ -47,6 +84,9 @@ export class BaseAuth {
4784
* for code samples and detailed documentation
4885
*
4986
* @param sessionCookie - The session cookie to verify.
87+
* @param env - An optional parameter specifying the environment in which the function is running.
88+
* If the function is running in an emulator environment, this should be set to `EmulatorEnv`.
89+
* If not specified, the function will assume it is running in a production environment.
5090
*
5191
* @returns A promise fulfilled with the
5292
* session cookie's decoded claims if the session cookie is valid; otherwise,
@@ -57,3 +97,15 @@ export class BaseAuth {
5797
return this.sessionCookieVerifier.verifyJWT(sessionCookie, isEmulator);
5898
}
5999
}
100+
101+
/**
102+
* Interface representing the session cookie options needed for the
103+
* {@link BaseAuth.createSessionCookie} method.
104+
*/
105+
export interface SessionCookieOptions {
106+
/**
107+
* The session cookie custom expiration in milliseconds. The minimum allowed is
108+
* 5 minutes and the maxium allowed is 2 weeks.
109+
*/
110+
expiresIn: number;
111+
}

0 commit comments

Comments
 (0)