@better-auth/sso 1.4.7-beta.3 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/dist/client.d.mts +1 -1
- package/dist/{index-m7FISidt.d.mts → index-B9WMxRdD.d.mts} +325 -19
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +454 -16
- package/package.json +3 -3
- package/src/index.ts +27 -0
- package/src/oidc/discovery.test.ts +1157 -0
- package/src/oidc/discovery.ts +494 -0
- package/src/oidc/errors.ts +92 -0
- package/src/oidc/index.ts +31 -0
- package/src/oidc/types.ts +219 -0
- package/src/oidc.test.ts +3 -164
- package/src/routes/sso.ts +192 -23
- package/src/saml.test.ts +302 -57
- package/src/types.ts +32 -6
package/src/saml.test.ts
CHANGED
|
@@ -17,6 +17,7 @@ import express from "express";
|
|
|
17
17
|
import * as saml from "samlify";
|
|
18
18
|
import {
|
|
19
19
|
afterAll,
|
|
20
|
+
afterEach,
|
|
20
21
|
beforeAll,
|
|
21
22
|
beforeEach,
|
|
22
23
|
describe,
|
|
@@ -24,7 +25,12 @@ import {
|
|
|
24
25
|
it,
|
|
25
26
|
vi,
|
|
26
27
|
} from "vitest";
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
createInMemoryAuthnRequestStore,
|
|
30
|
+
DEFAULT_CLOCK_SKEW_MS,
|
|
31
|
+
sso,
|
|
32
|
+
validateSAMLTimestamp,
|
|
33
|
+
} from ".";
|
|
28
34
|
import { ssoClient } from "./client";
|
|
29
35
|
|
|
30
36
|
const spMetadata = `
|
|
@@ -1916,72 +1922,82 @@ describe("SSO Provider Config Parsing", () => {
|
|
|
1916
1922
|
});
|
|
1917
1923
|
|
|
1918
1924
|
it("returns parsed OIDC config and avoids [object Object] in response", async () => {
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
session: [] as any[],
|
|
1922
|
-
verification: [] as any[],
|
|
1923
|
-
account: [] as any[],
|
|
1924
|
-
ssoProvider: [] as any[],
|
|
1925
|
-
};
|
|
1925
|
+
const { OAuth2Server } = await import("oauth2-mock-server");
|
|
1926
|
+
const oidcServer = new OAuth2Server();
|
|
1926
1927
|
|
|
1927
|
-
|
|
1928
|
+
await oidcServer.issuer.keys.generate("RS256");
|
|
1929
|
+
await oidcServer.start(8082, "localhost");
|
|
1928
1930
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1931
|
+
try {
|
|
1932
|
+
const data = {
|
|
1933
|
+
user: [] as any[],
|
|
1934
|
+
session: [] as any[],
|
|
1935
|
+
verification: [] as any[],
|
|
1936
|
+
account: [] as any[],
|
|
1937
|
+
ssoProvider: [] as any[],
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
const memory = memoryAdapter(data);
|
|
1941
|
+
|
|
1942
|
+
const auth = betterAuth({
|
|
1943
|
+
database: memory,
|
|
1944
|
+
trustedOrigins: ["http://localhost:8082"],
|
|
1945
|
+
baseURL: "http://localhost:3000",
|
|
1946
|
+
emailAndPassword: { enabled: true },
|
|
1947
|
+
plugins: [sso()],
|
|
1948
|
+
});
|
|
1935
1949
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1950
|
+
const authClient = createAuthClient({
|
|
1951
|
+
baseURL: "http://localhost:3000",
|
|
1952
|
+
plugins: [bearer(), ssoClient()],
|
|
1953
|
+
fetchOptions: {
|
|
1954
|
+
customFetchImpl: async (url, init) =>
|
|
1955
|
+
auth.handler(new Request(url, init)),
|
|
1956
|
+
},
|
|
1957
|
+
});
|
|
1944
1958
|
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1959
|
+
const headers = new Headers();
|
|
1960
|
+
await authClient.signUp.email({
|
|
1961
|
+
email: "test@example.com",
|
|
1962
|
+
password: "password123",
|
|
1963
|
+
name: "Test User",
|
|
1964
|
+
});
|
|
1965
|
+
await authClient.signIn.email(
|
|
1966
|
+
{ email: "test@example.com", password: "password123" },
|
|
1967
|
+
{ onSuccess: setCookieToHeader(headers) },
|
|
1968
|
+
);
|
|
1955
1969
|
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
+
const provider = await auth.api.registerSSOProvider({
|
|
1971
|
+
body: {
|
|
1972
|
+
providerId: "oidc-config-provider",
|
|
1973
|
+
issuer: oidcServer.issuer.url!,
|
|
1974
|
+
domain: "example.com",
|
|
1975
|
+
oidcConfig: {
|
|
1976
|
+
clientId: "test-client",
|
|
1977
|
+
clientSecret: "test-secret",
|
|
1978
|
+
tokenEndpointAuthentication: "client_secret_basic",
|
|
1979
|
+
mapping: {
|
|
1980
|
+
id: "sub",
|
|
1981
|
+
email: "email",
|
|
1982
|
+
name: "name",
|
|
1983
|
+
},
|
|
1970
1984
|
},
|
|
1971
1985
|
},
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
});
|
|
1986
|
+
headers,
|
|
1987
|
+
});
|
|
1975
1988
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1989
|
+
expect(provider.oidcConfig).toBeDefined();
|
|
1990
|
+
expect(typeof provider.oidcConfig).toBe("object");
|
|
1991
|
+
expect(provider.oidcConfig?.clientId).toBe("test-client");
|
|
1992
|
+
expect(provider.oidcConfig?.clientSecret).toBe("test-secret");
|
|
1980
1993
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1994
|
+
const serialized = JSON.stringify(provider.oidcConfig);
|
|
1995
|
+
expect(serialized).not.toContain("[object Object]");
|
|
1983
1996
|
|
|
1984
|
-
|
|
1997
|
+
expect(provider.oidcConfig?.mapping?.id).toBe("sub");
|
|
1998
|
+
} finally {
|
|
1999
|
+
await oidcServer.stop().catch(() => {});
|
|
2000
|
+
}
|
|
1985
2001
|
});
|
|
1986
2002
|
});
|
|
1987
2003
|
|
|
@@ -2138,3 +2154,232 @@ describe("SAML SSO - Signature Validation Security", () => {
|
|
|
2138
2154
|
});
|
|
2139
2155
|
});
|
|
2140
2156
|
});
|
|
2157
|
+
|
|
2158
|
+
describe("SAML SSO - Timestamp Validation", () => {
|
|
2159
|
+
describe("Valid assertions within time window", () => {
|
|
2160
|
+
it("should accept assertion with current NotBefore and future NotOnOrAfter", () => {
|
|
2161
|
+
const now = new Date();
|
|
2162
|
+
const fiveMinutesFromNow = new Date(Date.now() + 5 * 60 * 1000);
|
|
2163
|
+
expect(() =>
|
|
2164
|
+
validateSAMLTimestamp({
|
|
2165
|
+
notBefore: now.toISOString(),
|
|
2166
|
+
notOnOrAfter: fiveMinutesFromNow.toISOString(),
|
|
2167
|
+
}),
|
|
2168
|
+
).not.toThrow();
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
it("should accept assertion within clock skew tolerance (expired 2 min ago with 5 min skew)", () => {
|
|
2172
|
+
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000).toISOString();
|
|
2173
|
+
expect(() =>
|
|
2174
|
+
validateSAMLTimestamp({ notOnOrAfter: twoMinutesAgo }),
|
|
2175
|
+
).not.toThrow();
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
it("should accept assertion with NotBefore slightly in future (within clock skew)", () => {
|
|
2179
|
+
const twoMinutesFromNow = new Date(
|
|
2180
|
+
Date.now() + 2 * 60 * 1000,
|
|
2181
|
+
).toISOString();
|
|
2182
|
+
expect(() =>
|
|
2183
|
+
validateSAMLTimestamp({ notBefore: twoMinutesFromNow }),
|
|
2184
|
+
).not.toThrow();
|
|
2185
|
+
});
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2188
|
+
describe("NotBefore validation (future-dated assertions)", () => {
|
|
2189
|
+
it("should reject assertion with NotBefore too far in future (beyond clock skew)", () => {
|
|
2190
|
+
const tenMinutesFromNow = new Date(
|
|
2191
|
+
Date.now() + 10 * 60 * 1000,
|
|
2192
|
+
).toISOString();
|
|
2193
|
+
expect(() =>
|
|
2194
|
+
validateSAMLTimestamp({ notBefore: tenMinutesFromNow }),
|
|
2195
|
+
).toThrow("SAML assertion is not yet valid");
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2198
|
+
it("should reject with custom strict clock skew (1 second)", () => {
|
|
2199
|
+
const threeSecondsFromNow = new Date(Date.now() + 3 * 1000).toISOString();
|
|
2200
|
+
expect(() =>
|
|
2201
|
+
validateSAMLTimestamp(
|
|
2202
|
+
{ notBefore: threeSecondsFromNow },
|
|
2203
|
+
{ clockSkew: 1000 },
|
|
2204
|
+
),
|
|
2205
|
+
).toThrow("SAML assertion is not yet valid");
|
|
2206
|
+
});
|
|
2207
|
+
});
|
|
2208
|
+
|
|
2209
|
+
describe("NotOnOrAfter validation (expired assertions)", () => {
|
|
2210
|
+
it("should reject expired assertion (NotOnOrAfter in past beyond clock skew)", () => {
|
|
2211
|
+
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
|
|
2212
|
+
expect(() =>
|
|
2213
|
+
validateSAMLTimestamp({ notOnOrAfter: tenMinutesAgo }),
|
|
2214
|
+
).toThrow("SAML assertion has expired");
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
it("should reject with custom strict clock skew (1 second)", () => {
|
|
2218
|
+
const threeSecondsAgo = new Date(Date.now() - 3 * 1000).toISOString();
|
|
2219
|
+
expect(() =>
|
|
2220
|
+
validateSAMLTimestamp(
|
|
2221
|
+
{ notOnOrAfter: threeSecondsAgo },
|
|
2222
|
+
{ clockSkew: 1000 },
|
|
2223
|
+
),
|
|
2224
|
+
).toThrow("SAML assertion has expired");
|
|
2225
|
+
});
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
describe("Boundary conditions (exactly at window edges)", () => {
|
|
2229
|
+
const FIXED_TIME = new Date("2024-01-15T12:00:00.000Z").getTime();
|
|
2230
|
+
|
|
2231
|
+
beforeEach(() => {
|
|
2232
|
+
vi.useFakeTimers();
|
|
2233
|
+
vi.setSystemTime(FIXED_TIME);
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
afterEach(() => {
|
|
2237
|
+
vi.useRealTimers();
|
|
2238
|
+
});
|
|
2239
|
+
|
|
2240
|
+
it("should accept assertion expiring exactly at clock skew boundary", () => {
|
|
2241
|
+
const exactlyAtBoundary = new Date(
|
|
2242
|
+
FIXED_TIME - DEFAULT_CLOCK_SKEW_MS,
|
|
2243
|
+
).toISOString();
|
|
2244
|
+
expect(() =>
|
|
2245
|
+
validateSAMLTimestamp({ notOnOrAfter: exactlyAtBoundary }),
|
|
2246
|
+
).not.toThrow();
|
|
2247
|
+
});
|
|
2248
|
+
|
|
2249
|
+
it("should reject assertion expiring 1ms beyond clock skew boundary", () => {
|
|
2250
|
+
const justPastBoundary = new Date(
|
|
2251
|
+
FIXED_TIME - DEFAULT_CLOCK_SKEW_MS - 1,
|
|
2252
|
+
).toISOString();
|
|
2253
|
+
expect(() =>
|
|
2254
|
+
validateSAMLTimestamp({ notOnOrAfter: justPastBoundary }),
|
|
2255
|
+
).toThrow("SAML assertion has expired");
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
it("should accept assertion with NotBefore exactly at clock skew boundary", () => {
|
|
2259
|
+
const exactlyAtBoundary = new Date(
|
|
2260
|
+
FIXED_TIME + DEFAULT_CLOCK_SKEW_MS,
|
|
2261
|
+
).toISOString();
|
|
2262
|
+
expect(() =>
|
|
2263
|
+
validateSAMLTimestamp({ notBefore: exactlyAtBoundary }),
|
|
2264
|
+
).not.toThrow();
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
it("should reject assertion with NotBefore 1ms beyond clock skew boundary", () => {
|
|
2268
|
+
const justPastBoundary = new Date(
|
|
2269
|
+
FIXED_TIME + DEFAULT_CLOCK_SKEW_MS + 1,
|
|
2270
|
+
).toISOString();
|
|
2271
|
+
expect(() =>
|
|
2272
|
+
validateSAMLTimestamp({ notBefore: justPastBoundary }),
|
|
2273
|
+
).toThrow("SAML assertion is not yet valid");
|
|
2274
|
+
});
|
|
2275
|
+
});
|
|
2276
|
+
|
|
2277
|
+
describe("Missing timestamps behavior", () => {
|
|
2278
|
+
it("should accept missing timestamps when requireTimestamps is false (default)", () => {
|
|
2279
|
+
expect(() =>
|
|
2280
|
+
validateSAMLTimestamp(undefined, { requireTimestamps: false }),
|
|
2281
|
+
).not.toThrow();
|
|
2282
|
+
});
|
|
2283
|
+
|
|
2284
|
+
it("should accept empty conditions when requireTimestamps is false", () => {
|
|
2285
|
+
expect(() =>
|
|
2286
|
+
validateSAMLTimestamp({}, { requireTimestamps: false }),
|
|
2287
|
+
).not.toThrow();
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
it("should reject missing timestamps when requireTimestamps is true", () => {
|
|
2291
|
+
expect(() =>
|
|
2292
|
+
validateSAMLTimestamp(undefined, { requireTimestamps: true }),
|
|
2293
|
+
).toThrow("SAML assertion missing required timestamp conditions");
|
|
2294
|
+
});
|
|
2295
|
+
|
|
2296
|
+
it("should reject empty conditions when requireTimestamps is true", () => {
|
|
2297
|
+
expect(() =>
|
|
2298
|
+
validateSAMLTimestamp({}, { requireTimestamps: true }),
|
|
2299
|
+
).toThrow("SAML assertion missing required timestamp conditions");
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
it("should accept assertions with only NotBefore (valid)", () => {
|
|
2303
|
+
const now = new Date().toISOString();
|
|
2304
|
+
expect(() => validateSAMLTimestamp({ notBefore: now })).not.toThrow();
|
|
2305
|
+
});
|
|
2306
|
+
|
|
2307
|
+
it("should accept assertions with only NotOnOrAfter (valid, in future)", () => {
|
|
2308
|
+
const future = new Date(Date.now() + 10 * 60 * 1000).toISOString();
|
|
2309
|
+
expect(() =>
|
|
2310
|
+
validateSAMLTimestamp({ notOnOrAfter: future }),
|
|
2311
|
+
).not.toThrow();
|
|
2312
|
+
});
|
|
2313
|
+
});
|
|
2314
|
+
|
|
2315
|
+
describe("Custom clock skew configuration", () => {
|
|
2316
|
+
it("should use custom clockSkew when provided", () => {
|
|
2317
|
+
const twoSecondsAgo = new Date(Date.now() - 2 * 1000).toISOString();
|
|
2318
|
+
|
|
2319
|
+
expect(() =>
|
|
2320
|
+
validateSAMLTimestamp(
|
|
2321
|
+
{ notOnOrAfter: twoSecondsAgo },
|
|
2322
|
+
{ clockSkew: 1000 },
|
|
2323
|
+
),
|
|
2324
|
+
).toThrow("SAML assertion has expired");
|
|
2325
|
+
|
|
2326
|
+
expect(() =>
|
|
2327
|
+
validateSAMLTimestamp(
|
|
2328
|
+
{ notOnOrAfter: twoSecondsAgo },
|
|
2329
|
+
{ clockSkew: 5 * 60 * 1000 },
|
|
2330
|
+
),
|
|
2331
|
+
).not.toThrow();
|
|
2332
|
+
});
|
|
2333
|
+
|
|
2334
|
+
it("should use default 5 minute clock skew when not specified", () => {
|
|
2335
|
+
const fourMinutesAgo = new Date(Date.now() - 4 * 60 * 1000).toISOString();
|
|
2336
|
+
expect(() =>
|
|
2337
|
+
validateSAMLTimestamp({ notOnOrAfter: fourMinutesAgo }),
|
|
2338
|
+
).not.toThrow();
|
|
2339
|
+
|
|
2340
|
+
const sixMinutesAgo = new Date(Date.now() - 6 * 60 * 1000).toISOString();
|
|
2341
|
+
expect(() =>
|
|
2342
|
+
validateSAMLTimestamp({ notOnOrAfter: sixMinutesAgo }),
|
|
2343
|
+
).toThrow("SAML assertion has expired");
|
|
2344
|
+
});
|
|
2345
|
+
});
|
|
2346
|
+
|
|
2347
|
+
describe("Malformed timestamp handling", () => {
|
|
2348
|
+
it("should reject malformed NotBefore timestamp", () => {
|
|
2349
|
+
expect(() =>
|
|
2350
|
+
validateSAMLTimestamp({ notBefore: "not-a-valid-date" }),
|
|
2351
|
+
).toThrow("SAML assertion has invalid NotBefore timestamp");
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
it("should reject malformed NotOnOrAfter timestamp", () => {
|
|
2355
|
+
expect(() =>
|
|
2356
|
+
validateSAMLTimestamp({ notOnOrAfter: "invalid-timestamp" }),
|
|
2357
|
+
).toThrow("SAML assertion has invalid NotOnOrAfter timestamp");
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
it("should treat empty string timestamps as missing (falsy values)", () => {
|
|
2361
|
+
expect(() => validateSAMLTimestamp({ notBefore: "" })).not.toThrow();
|
|
2362
|
+
expect(() => validateSAMLTimestamp({ notOnOrAfter: "" })).not.toThrow();
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
it("should reject garbage data in timestamps", () => {
|
|
2366
|
+
expect(() =>
|
|
2367
|
+
validateSAMLTimestamp({
|
|
2368
|
+
notBefore: "abc123xyz",
|
|
2369
|
+
notOnOrAfter: "!@#$%^&*()",
|
|
2370
|
+
}),
|
|
2371
|
+
).toThrow("SAML assertion has invalid NotBefore timestamp");
|
|
2372
|
+
});
|
|
2373
|
+
|
|
2374
|
+
it("should accept valid ISO 8601 timestamps", () => {
|
|
2375
|
+
const now = new Date();
|
|
2376
|
+
const future = new Date(Date.now() + 10 * 60 * 1000);
|
|
2377
|
+
expect(() =>
|
|
2378
|
+
validateSAMLTimestamp({
|
|
2379
|
+
notBefore: now.toISOString(),
|
|
2380
|
+
notOnOrAfter: future.toISOString(),
|
|
2381
|
+
}),
|
|
2382
|
+
).not.toThrow();
|
|
2383
|
+
});
|
|
2384
|
+
});
|
|
2385
|
+
});
|
package/src/types.ts
CHANGED
|
@@ -233,13 +233,7 @@ export interface SSOOptions {
|
|
|
233
233
|
*
|
|
234
234
|
* If you want to allow account linking for specific trusted providers, enable the `accountLinking` option in your auth config and specify those
|
|
235
235
|
* providers in the `trustedProviders` list.
|
|
236
|
-
*
|
|
237
236
|
* @default false
|
|
238
|
-
*
|
|
239
|
-
* @deprecated This option is discouraged for new projects. Relying on provider-level `email_verified` is a weaker
|
|
240
|
-
* trust signal compared to using `trustedProviders` in `accountLinking` or enabling `domainVerification` for SSO.
|
|
241
|
-
* Existing configurations will continue to work, but new integrations should use explicit trust mechanisms.
|
|
242
|
-
* This option may be removed in a future major version.
|
|
243
237
|
*/
|
|
244
238
|
trustEmailVerified?: boolean | undefined;
|
|
245
239
|
/**
|
|
@@ -307,5 +301,37 @@ export interface SSOOptions {
|
|
|
307
301
|
* verification table fallback) is used automatically.
|
|
308
302
|
*/
|
|
309
303
|
authnRequestStore?: AuthnRequestStore;
|
|
304
|
+
/**
|
|
305
|
+
* Clock skew tolerance for SAML assertion timestamp validation in milliseconds.
|
|
306
|
+
* Allows for minor time differences between IdP and SP servers.
|
|
307
|
+
*
|
|
308
|
+
* Defaults to 300000 (5 minutes) to accommodate:
|
|
309
|
+
* - Network latency and processing time
|
|
310
|
+
* - Clock synchronization differences (NTP drift)
|
|
311
|
+
* - Distributed systems across timezones
|
|
312
|
+
*
|
|
313
|
+
* For stricter security, reduce to 1-2 minutes (60000-120000).
|
|
314
|
+
* For highly distributed systems, increase up to 10 minutes (600000).
|
|
315
|
+
*
|
|
316
|
+
* @default 300000 (5 minutes)
|
|
317
|
+
*/
|
|
318
|
+
clockSkew?: number;
|
|
319
|
+
/**
|
|
320
|
+
* Require timestamp conditions (NotBefore/NotOnOrAfter) in SAML assertions.
|
|
321
|
+
* When enabled, assertions without timestamp conditions will be rejected.
|
|
322
|
+
*
|
|
323
|
+
* When disabled (default), assertions without timestamps are accepted
|
|
324
|
+
* but a warning is logged.
|
|
325
|
+
*
|
|
326
|
+
* **SAML Spec Notes:**
|
|
327
|
+
* - SAML 2.0 Core: Timestamps are OPTIONAL
|
|
328
|
+
* - SAML2Int (enterprise profile): Timestamps are REQUIRED
|
|
329
|
+
*
|
|
330
|
+
* **Recommendation:** Enable for enterprise/production deployments
|
|
331
|
+
* where your IdP follows SAML2Int (Okta, Azure AD, OneLogin, etc.)
|
|
332
|
+
*
|
|
333
|
+
* @default false
|
|
334
|
+
*/
|
|
335
|
+
requireTimestamps?: boolean;
|
|
310
336
|
};
|
|
311
337
|
}
|