@recursyve/iap-session-core
v1.0.0
Published
Platform-agnostic IAP session token lifecycle
Readme
iap-session-core
Platform-agnostic TypeScript library for managing Google OIDC ID token lifecycle when calling Identity-Aware Proxy (IAP)–protected APIs. It is the shared foundation used by ngx-iap-session (Angular) and iap_session (Flutter).
Short Description
iap-session-core handles IAP token caching, expiry checks, refresh orchestration, and Authorization / Proxy-Authorization header construction. Platform-specific OAuth sign-in is delegated to an injected IapTokenProvider; optional cross-session persistence is available via the IapTokenStorage port.
Features
- Environment-gated activation via an
enabledflag (fully inert when disabled) - In-memory token cache with JWT expiry detection (5-minute refresh buffer)
- Optional
IapTokenStorageport for cross-session persistence (default: in-memory only) - Origin-scoped URL matching so tokens attach only to IAP API requests
- Configurable header strategy:
AuthorizationorProxy-Authorization - IAP 401 detection helper (
isIapUnauthorizedResponse) for HTTP interceptors - OAuth-client-agnostic — no Google SDK or framework dependencies
- Fully unit-testable with a mock
IapTokenProvider
Installation
npm install iap-session-coreQuick Start / Basic Usage
import {
IapSessionManager,
InMemoryTokenStorage,
type IapSessionCoreConfig,
type IapTokenProvider,
} from "iap-session-core";
const config: IapSessionCoreConfig = {
enabled: true,
apiBaseUrl: "https://project-a.staging.api.example.com",
useProxyAuthorization: false,
};
const tokenProvider: IapTokenProvider = {
getTokenSilent: async () => null,
getTokenInteractive: async () => "<google-id-token>",
};
const manager = new IapSessionManager(config, tokenProvider);
if (manager.shouldAttachToUrl("https://project-a.staging.api.example.com/users")) {
const header = await manager.getAuthHeader();
// { Authorization: "Bearer <id_token>" }
}
// After capturing a token from an OAuth redirect:
manager.setCachedToken("<id_token>");
// On IAP 401 from your HTTP layer:
await manager.handleUnauthorized();API Reference
Types
IapSessionCoreConfig
| Field | Type | Required when enabled | Description |
| ----------------------- | --------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| enabled | boolean | — | Master switch. When false, the manager is inert. |
| apiBaseUrl | string | yes | IAP-protected API base URL. |
| useProxyAuthorization | boolean | yes | true → Proxy-Authorization; false → Authorization. No default — platform wrappers must set this explicitly (ngx-iap-session passes false; Flutter defaults false). Ignored when enabled is false. |
| apiOrigin | string | no | Origin filter for token attachment. Defaults to the origin of apiBaseUrl. |
NormalizedIapSessionCoreConfig
Return type of validateCoreConfig. Discriminated union: { enabled: false } when disabled, or { enabled: true; apiBaseUrl; useProxyAuthorization; apiOrigin? } when enabled.
IapTokenProvider
Platform adapter for obtaining Google OIDC ID tokens:
interface IapTokenProvider {
getTokenSilent(): Promise<string | null>;
getTokenInteractive(): Promise<string>;
}IapTokenStorage
Optional persistence port for cross-session token storage:
interface IapTokenStorage {
read(): string | null;
write(token: string): void;
clear(): void;
}validateCoreConfig(config: IapSessionCoreConfig): NormalizedIapSessionCoreConfig
Validates and normalizes configuration. Throws if enabled is true and apiBaseUrl or useProxyAuthorization is missing. When enabled is false, returns { enabled: false } without validating other fields.
resolveApiOrigin(config: NormalizedIapSessionCoreConfig): string | null
Returns the origin used for URL matching. Uses apiOrigin when set, otherwise parses apiBaseUrl. Returns null when disabled.
IapSessionManager
class IapSessionManager {
constructor(
config: IapSessionCoreConfig,
tokenProvider: IapTokenProvider,
storage?: IapTokenStorage // defaults to InMemoryTokenStorage
);
readonly isActive: boolean;
shouldAttachToUrl(requestUrl: string): boolean;
invalidateToken(): void;
setCachedToken(token: string): void;
getToken(): Promise<string | null>;
getAuthHeader(): Promise<Record<string, string> | null>;
handleUnauthorized(): Promise<string | null>;
}| Member | Returns | Description |
| ------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| isActive | boolean | false when enabled is false. |
| shouldAttachToUrl(requestUrl) | boolean | true when the request URL origin matches the configured API origin. |
| invalidateToken() | void | Clears the in-memory cache and storage. |
| setCachedToken(token) | void | Stores a token in memory and via IapTokenStorage.write(). Used after OAuth redirect capture. |
| getToken() | Promise<string \| null> | Returns a valid cached token, silently refreshes via getTokenSilent(), or null when disabled / unavailable. Expired tokens are purged before refresh. |
| getAuthHeader() | Promise<Record<string, string> \| null> | Builds { Authorization: "Bearer …" } or { "Proxy-Authorization": "Bearer …" }, or null. |
| handleUnauthorized() | Promise<string \| null> | Invalidates the cache, calls getTokenInteractive(), caches the result, and returns the new token. |
On construction, the manager hydrates a valid token from storage (if provided). Concurrent calls to getToken() and handleUnauthorized() are deduplicated.
InMemoryTokenStorage
Default IapTokenStorage implementation with no cross-session persistence. All methods are no-ops except read() which always returns null.
isIapUnauthorizedResponse(options): boolean
Detects IAP-generated 401 responses for use in HTTP interceptors.
function isIapUnauthorizedResponse(options: {
status: number;
hasIapGeneratedHeader: boolean;
body: string;
}): boolean;Returns true when status is 401 and either the x-goog-iap-generated-response header is present or the body contains "Invalid IAP credentials".
iapUnauthorizedBodyMarker
const iapUnauthorizedBodyMarker = "Invalid IAP credentials";isTokenExpired(token, bufferMs?, nowMs?): boolean
Returns true when the JWT exp claim is within bufferMs of expiry (default: 5 minutes). Treats unparseable tokens as expired.
getJwtExpiryMs(token): number | null
Decodes the JWT payload and returns the exp claim in milliseconds, or null if unavailable.
urlMatchesOrigin(requestUrl, apiOrigin): boolean
Returns true when requestUrl parses to the same origin as apiOrigin.
Prerequisites / Requirements
- Node.js >= 20
- TypeScript 5.9+ (consumers)
- No runtime peer dependencies — pure TypeScript / ESM (
"type": "module")
