oidc-spa 8.1.12 → 8.1.14
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/core/createOidc.js +86 -53
- package/core/createOidc.js.map +1 -1
- package/esm/core/createOidc.js +86 -53
- package/esm/core/createOidc.js.map +1 -1
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.d.ts +6 -3
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js +33 -5
- package/esm/tanstack-start/react/accessTokenValidation_rfc9068.js.map +1 -1
- package/esm/tanstack-start/react/apiBuilder.d.ts +6 -3
- package/esm/tanstack-start/react/apiBuilder.js.map +1 -1
- package/esm/tanstack-start/react/createOidcSpaApi.js +2 -1
- package/esm/tanstack-start/react/createOidcSpaApi.js.map +1 -1
- package/esm/tanstack-start/react/types.d.ts +7 -1
- package/package.json +1 -1
- package/src/core/createOidc.ts +137 -58
- package/src/tanstack-start/react/accessTokenValidation_rfc9068.ts +52 -10
- package/src/tanstack-start/react/apiBuilder.ts +4 -9
- package/src/tanstack-start/react/createOidcSpaApi.tsx +3 -1
- package/src/tanstack-start/react/types.tsx +8 -1
package/src/core/createOidc.ts
CHANGED
|
@@ -1062,6 +1062,27 @@ export async function createOidc_nonMemoized<
|
|
|
1062
1062
|
log
|
|
1063
1063
|
});
|
|
1064
1064
|
|
|
1065
|
+
detect_useless_idleSessionLifetimeInSeconds: {
|
|
1066
|
+
if (idleSessionLifetimeInSeconds === undefined) {
|
|
1067
|
+
break detect_useless_idleSessionLifetimeInSeconds;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (currentTokens.refreshTokenExpirationTime === undefined) {
|
|
1071
|
+
break detect_useless_idleSessionLifetimeInSeconds;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
console.warn(
|
|
1075
|
+
[
|
|
1076
|
+
"oidc-spa: You've specified idleSessionLifetimeInSeconds,",
|
|
1077
|
+
"but your auth server issues a refresh_token with a known expiration time.",
|
|
1078
|
+
"idleSessionLifetimeInSeconds should only be used as a fallback",
|
|
1079
|
+
"for auth servers that don't specify when an inactive session expires.",
|
|
1080
|
+
"The auth server, not your code, is the source of truth.",
|
|
1081
|
+
"See: https://docs.oidc-spa.dev/v/v8/auto-logout"
|
|
1082
|
+
].join(" ")
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1065
1086
|
{
|
|
1066
1087
|
if (getPersistedAuthState({ configId }) !== undefined) {
|
|
1067
1088
|
persistAuthState({ configId, state: undefined });
|
|
@@ -1558,6 +1579,18 @@ export async function createOidc_nonMemoized<
|
|
|
1558
1579
|
return;
|
|
1559
1580
|
}
|
|
1560
1581
|
|
|
1582
|
+
const msBeforeExpiration_idleSessionLifetimeInSeconds =
|
|
1583
|
+
idleSessionLifetimeInSeconds === undefined ? undefined : idleSessionLifetimeInSeconds * 1000;
|
|
1584
|
+
|
|
1585
|
+
const msBeforeExpiration_refreshToken =
|
|
1586
|
+
currentTokens.refreshTokenExpirationTime === undefined
|
|
1587
|
+
? undefined
|
|
1588
|
+
: currentTokens.refreshTokenExpirationTime - currentTokens.getServerDateNow();
|
|
1589
|
+
const msBeforeExpiration_accessToken =
|
|
1590
|
+
currentTokens.accessTokenExpirationTime - currentTokens.getServerDateNow();
|
|
1591
|
+
|
|
1592
|
+
let isRefreshTokenNeverExpiring = false;
|
|
1593
|
+
|
|
1561
1594
|
if (
|
|
1562
1595
|
currentTokens.refreshTokenExpirationTime !== undefined &&
|
|
1563
1596
|
currentTokens.refreshTokenExpirationTime >= INFINITY_TIME
|
|
@@ -1573,74 +1606,117 @@ export async function createOidc_nonMemoized<
|
|
|
1573
1606
|
if (warningLines.length > 0) {
|
|
1574
1607
|
warningLines.push(
|
|
1575
1608
|
...[
|
|
1576
|
-
"Misconfiguration: offline_access is for native apps, not
|
|
1609
|
+
"Misconfiguration: offline_access is for native apps, not for web apps like yours. ",
|
|
1577
1610
|
"You lose SSO and users must log in after every reload."
|
|
1578
1611
|
]
|
|
1579
1612
|
);
|
|
1613
|
+
console.warn(`oidc-spa: ${warningLines.join(" ")}`);
|
|
1614
|
+
return;
|
|
1580
1615
|
}
|
|
1581
1616
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
"The backend session will not expire.",
|
|
1585
|
-
...warningLines
|
|
1586
|
-
].join(" ");
|
|
1617
|
+
isRefreshTokenNeverExpiring = true;
|
|
1618
|
+
}
|
|
1587
1619
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1620
|
+
const RENEW_MS_BEFORE_EXPIRES = 30_000;
|
|
1621
|
+
const MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION = RENEW_MS_BEFORE_EXPIRES + 15_000;
|
|
1622
|
+
|
|
1623
|
+
detect_session_reached_max_life: {
|
|
1624
|
+
if (msBeforeExpiration_refreshToken === undefined) {
|
|
1625
|
+
break detect_session_reached_max_life;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
if (msBeforeExpiration_refreshToken > MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION) {
|
|
1629
|
+
break detect_session_reached_max_life;
|
|
1592
1630
|
}
|
|
1631
|
+
|
|
1632
|
+
console.warn(
|
|
1633
|
+
[
|
|
1634
|
+
"oidc-spa: The session is nearing its maximum lifetime, and the user will soon need to log in again,",
|
|
1635
|
+
`or you've configured a refresh_token with a TTL of ${toHumanReadableDuration(
|
|
1636
|
+
msBeforeExpiration_refreshToken
|
|
1637
|
+
)}.`,
|
|
1638
|
+
`If it's the latter, the TTL is too short, it must be at least ${toHumanReadableDuration(
|
|
1639
|
+
MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION
|
|
1640
|
+
)} for reliable operation.`,
|
|
1641
|
+
"Shorter lifetimes can cause unpredictable session expirations and are usually a misconfiguration.",
|
|
1642
|
+
"\nIn either case, oidc-spa will not ping the auth server to keep the session alive."
|
|
1643
|
+
].join(" ")
|
|
1644
|
+
);
|
|
1645
|
+
|
|
1593
1646
|
return;
|
|
1594
1647
|
}
|
|
1595
1648
|
|
|
1596
|
-
|
|
1597
|
-
(
|
|
1598
|
-
|
|
1649
|
+
let msBeforeExpiration = (() => {
|
|
1650
|
+
if (msBeforeExpiration_refreshToken !== undefined && !isRefreshTokenNeverExpiring) {
|
|
1651
|
+
log?.(
|
|
1652
|
+
[
|
|
1653
|
+
toHumanReadableDuration(msBeforeExpiration_refreshToken),
|
|
1654
|
+
`before expiration of the refresh_token.`,
|
|
1655
|
+
`Scheduling renewal of the tokens ${toHumanReadableDuration(
|
|
1656
|
+
RENEW_MS_BEFORE_EXPIRES
|
|
1657
|
+
)} before expiration as a way to keep the session alive on the OIDC server.`
|
|
1658
|
+
].join(" ")
|
|
1659
|
+
);
|
|
1599
1660
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1661
|
+
return msBeforeExpiration_refreshToken;
|
|
1662
|
+
}
|
|
1602
1663
|
|
|
1603
|
-
|
|
1664
|
+
if (msBeforeExpiration_idleSessionLifetimeInSeconds !== undefined) {
|
|
1665
|
+
if (
|
|
1666
|
+
msBeforeExpiration_idleSessionLifetimeInSeconds < MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION
|
|
1667
|
+
) {
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
[
|
|
1670
|
+
`oidc-spa: The configured idleSessionLifetimeInSeconds (${toHumanReadableDuration(
|
|
1671
|
+
msBeforeExpiration_idleSessionLifetimeInSeconds
|
|
1672
|
+
)}) is too short.`,
|
|
1673
|
+
`For reliability, it must be at least ${toHumanReadableDuration(
|
|
1674
|
+
MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION
|
|
1675
|
+
)}.`,
|
|
1676
|
+
"Very short session idle lifetimes are usually a misconfiguration, even for ultra sensitive apps."
|
|
1677
|
+
].join(" ")
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
log?.(
|
|
1682
|
+
[
|
|
1683
|
+
`You've set idleSessionLifetimeInSeconds to ${toHumanReadableDuration(
|
|
1684
|
+
msBeforeExpiration_idleSessionLifetimeInSeconds
|
|
1685
|
+
)}.`,
|
|
1686
|
+
`This means the user session will expire after ${toHumanReadableDuration(
|
|
1687
|
+
msBeforeExpiration_idleSessionLifetimeInSeconds
|
|
1688
|
+
)} of inactivity (assuming you're right).`,
|
|
1689
|
+
`Scheduling token renewal ${toHumanReadableDuration(
|
|
1690
|
+
RENEW_MS_BEFORE_EXPIRES
|
|
1691
|
+
)} before expiration to keep the session active on the OIDC server.`
|
|
1692
|
+
].join(" ")
|
|
1693
|
+
);
|
|
1694
|
+
|
|
1695
|
+
return msBeforeExpiration_idleSessionLifetimeInSeconds;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
const msBeforeExpiration =
|
|
1699
|
+
msBeforeExpiration_accessToken > MIN_ACCEPTABLE_MS_BEFORE_EXPIRATION
|
|
1700
|
+
? msBeforeExpiration_accessToken
|
|
1701
|
+
: 3_600_000;
|
|
1604
1702
|
|
|
1605
|
-
if (msBeforeExpiration <= RENEW_MS_BEFORE_EXPIRES) {
|
|
1606
1703
|
log?.(
|
|
1607
1704
|
[
|
|
1608
|
-
"
|
|
1609
|
-
(
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
case "access":
|
|
1620
|
-
return [
|
|
1621
|
-
currentTokens.hasRefreshToken
|
|
1622
|
-
? ", we can't read the expiration time of the refresh token"
|
|
1623
|
-
: ", we don't have a refresh token",
|
|
1624
|
-
` and the access token is already about to expire`,
|
|
1625
|
-
"we would spam the auth server by constantly renewing the access token in the background",
|
|
1626
|
-
"avoiding to do so."
|
|
1627
|
-
].join(" ");
|
|
1628
|
-
}
|
|
1629
|
-
})()
|
|
1630
|
-
].join(" ")
|
|
1705
|
+
"The auth server's idle session timeout is unknown.",
|
|
1706
|
+
isRefreshTokenNeverExpiring && "(The refresh token never expires)",
|
|
1707
|
+
`Assuming a default idle session TTL of ${toHumanReadableDuration(
|
|
1708
|
+
msBeforeExpiration
|
|
1709
|
+
)}.`,
|
|
1710
|
+
`Scheduling token renewal ${toHumanReadableDuration(
|
|
1711
|
+
RENEW_MS_BEFORE_EXPIRES
|
|
1712
|
+
)} before expiration to keep the session active on the OIDC server.`
|
|
1713
|
+
]
|
|
1714
|
+
.filter(line => typeof line === "string")
|
|
1715
|
+
.join(" ")
|
|
1631
1716
|
);
|
|
1632
|
-
return;
|
|
1633
|
-
}
|
|
1634
1717
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
toHumanReadableDuration(msBeforeExpiration),
|
|
1638
|
-
`before expiration of the ${typeOfTheTokenWeGotTheTtlFrom} token.`,
|
|
1639
|
-
`Scheduling renewal ${toHumanReadableDuration(
|
|
1640
|
-
RENEW_MS_BEFORE_EXPIRES
|
|
1641
|
-
)} before expiration to keep the session alive on the OIDC server.`
|
|
1642
|
-
].join(" ")
|
|
1643
|
-
);
|
|
1718
|
+
return msBeforeExpiration;
|
|
1719
|
+
})();
|
|
1644
1720
|
|
|
1645
1721
|
const timer = setTimeout(
|
|
1646
1722
|
async () => {
|
|
@@ -1670,7 +1746,7 @@ export async function createOidc_nonMemoized<
|
|
|
1670
1746
|
}
|
|
1671
1747
|
|
|
1672
1748
|
log?.(
|
|
1673
|
-
`Renewing the tokens now as the
|
|
1749
|
+
`Renewing the tokens now as otherwise the session will be terminated by the auth server in ${toHumanReadableDuration(
|
|
1674
1750
|
RENEW_MS_BEFORE_EXPIRES
|
|
1675
1751
|
)}`
|
|
1676
1752
|
);
|
|
@@ -1695,19 +1771,22 @@ export async function createOidc_nonMemoized<
|
|
|
1695
1771
|
|
|
1696
1772
|
auto_logout: {
|
|
1697
1773
|
const getCurrentRefreshTokenTtlInSeconds = () => {
|
|
1698
|
-
if (
|
|
1774
|
+
if (currentTokens.refreshTokenExpirationTime === undefined) {
|
|
1699
1775
|
return idleSessionLifetimeInSeconds;
|
|
1700
1776
|
}
|
|
1701
1777
|
|
|
1702
|
-
if (currentTokens.refreshTokenExpirationTime
|
|
1703
|
-
return
|
|
1778
|
+
if (currentTokens.refreshTokenExpirationTime >= INFINITY_TIME) {
|
|
1779
|
+
return idleSessionLifetimeInSeconds ?? 0;
|
|
1704
1780
|
}
|
|
1705
1781
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1782
|
+
const ttlInSeconds =
|
|
1783
|
+
(currentTokens.refreshTokenExpirationTime - currentTokens.issuedAtTime) / 1000;
|
|
1784
|
+
|
|
1785
|
+
if (idleSessionLifetimeInSeconds !== undefined) {
|
|
1786
|
+
return Math.min(idleSessionLifetimeInSeconds, ttlInSeconds);
|
|
1708
1787
|
}
|
|
1709
1788
|
|
|
1710
|
-
return
|
|
1789
|
+
return ttlInSeconds;
|
|
1711
1790
|
};
|
|
1712
1791
|
|
|
1713
1792
|
if (getCurrentRefreshTokenTtlInSeconds() === 0) {
|
|
@@ -9,16 +9,15 @@ export function createCreateValidateAndGetAccessTokenClaims_rfc9068<
|
|
|
9
9
|
>(params: {
|
|
10
10
|
accessTokenClaimsSchema?: ZodSchemaLike<AccessTokenClaims_RFC9068, AccessTokenClaims>;
|
|
11
11
|
accessTokenClaims_mock?: AccessTokenClaims;
|
|
12
|
-
expectedAudience?:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}) => string);
|
|
12
|
+
expectedAudience?: (params: {
|
|
13
|
+
paramsOfBootstrap: ParamsOfBootstrap.Real<boolean>;
|
|
14
|
+
process: { env: Record<string, string> };
|
|
15
|
+
}) => string;
|
|
17
16
|
}) {
|
|
18
17
|
const {
|
|
19
18
|
accessTokenClaimsSchema,
|
|
20
19
|
accessTokenClaims_mock,
|
|
21
|
-
expectedAudience:
|
|
20
|
+
expectedAudience: expectedAudienceGetter
|
|
22
21
|
} = params;
|
|
23
22
|
|
|
24
23
|
const createValidateAndGetAccessTokenClaims: CreateValidateAndGetAccessTokenClaims<
|
|
@@ -66,13 +65,56 @@ export function createCreateValidateAndGetAccessTokenClaims_rfc9068<
|
|
|
66
65
|
})();
|
|
67
66
|
|
|
68
67
|
const expectedAudience = (() => {
|
|
69
|
-
if (
|
|
68
|
+
if (expectedAudienceGetter === undefined) {
|
|
70
69
|
return undefined;
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
|
|
72
|
+
const missingEnvNames = new Set<string>();
|
|
73
|
+
|
|
74
|
+
const env_proxy = new Proxy<Record<string, string>>(
|
|
75
|
+
{},
|
|
76
|
+
{
|
|
77
|
+
get: (...[, envName]) => {
|
|
78
|
+
assert(typeof envName === "string");
|
|
79
|
+
|
|
80
|
+
const value = process.env[envName];
|
|
81
|
+
|
|
82
|
+
if (value === undefined) {
|
|
83
|
+
missingEnvNames.add(envName);
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return value;
|
|
88
|
+
},
|
|
89
|
+
has: (...[, envName]) => {
|
|
90
|
+
assert(typeof envName === "string");
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const expectedAudience = expectedAudienceGetter?.({
|
|
97
|
+
paramsOfBootstrap,
|
|
98
|
+
process: { env: env_proxy }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!expectedAudience) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
[
|
|
104
|
+
"oidc-spa: The expectedAudience() you provided returned empty.",
|
|
105
|
+
"If you specified the expectedAudience in withAccessTokenValidation",
|
|
106
|
+
"it's probably and error.",
|
|
107
|
+
missingEnvNames.size !== 0 &&
|
|
108
|
+
`Did you forget to set the env var: ${Array.from(missingEnvNames).join(
|
|
109
|
+
", "
|
|
110
|
+
)} ?`
|
|
111
|
+
]
|
|
112
|
+
.filter(line => typeof line === "string")
|
|
113
|
+
.join(" ")
|
|
114
|
+
);
|
|
74
115
|
}
|
|
75
|
-
|
|
116
|
+
|
|
117
|
+
return expectedAudience;
|
|
76
118
|
})();
|
|
77
119
|
|
|
78
120
|
return {
|
|
@@ -41,15 +41,10 @@ export type OidcSpaApiBuilder<
|
|
|
41
41
|
accessTokenClaimsSchema?: ZodSchemaLike<AccessTokenClaims_RFC9068, AccessTokenClaims>;
|
|
42
42
|
accessTokenClaims_mock?: NoInfer<AccessTokenClaims>;
|
|
43
43
|
|
|
44
|
-
expectedAudience?:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
boolean,
|
|
49
|
-
Record<string, unknown>,
|
|
50
|
-
AccessTokenClaims
|
|
51
|
-
>;
|
|
52
|
-
}) => string);
|
|
44
|
+
expectedAudience?: (params: {
|
|
45
|
+
paramsOfBootstrap: ParamsOfBootstrap.Real<boolean>;
|
|
46
|
+
process: { env: Record<string, string> };
|
|
47
|
+
}) => string;
|
|
53
48
|
}): OidcSpaApiBuilder<
|
|
54
49
|
AutoLogin,
|
|
55
50
|
DecodedIdToken,
|
|
@@ -663,7 +663,9 @@ export function createOidcSpaApi<
|
|
|
663
663
|
noIframe: paramsOfBootstrap.noIframe,
|
|
664
664
|
debugLogs: paramsOfBootstrap.debugLogs,
|
|
665
665
|
__unsafe_clientSecret: paramsOfBootstrap.__unsafe_clientSecret,
|
|
666
|
-
__metadata: paramsOfBootstrap.__metadata
|
|
666
|
+
__metadata: paramsOfBootstrap.__metadata,
|
|
667
|
+
__unsafe_useIdTokenAsAccessToken:
|
|
668
|
+
paramsOfBootstrap.__unsafe_useIdTokenAsAccessToken
|
|
667
669
|
});
|
|
668
670
|
} catch (error) {
|
|
669
671
|
if (!(error instanceof OidcInitializationError)) {
|
|
@@ -22,7 +22,7 @@ export type CreateOidcComponent<DecodedIdToken> = <
|
|
|
22
22
|
export namespace CreateOidcComponent {
|
|
23
23
|
export type WithAutoLogin<DecodedIdToken> = <Props>(params: {
|
|
24
24
|
pendingComponent?: (params: NoInfer<Props>) => ReactNode;
|
|
25
|
-
component: (props: Props) =>
|
|
25
|
+
component: (props: Props) => any;
|
|
26
26
|
}) => ((props: Props) => ReactNode) & {
|
|
27
27
|
useOidc: () => Oidc.LoggedIn<DecodedIdToken>;
|
|
28
28
|
};
|
|
@@ -338,6 +338,13 @@ export namespace ParamsOfBootstrap {
|
|
|
338
338
|
* or non-standard deployments), and you cannot fix the server-side configuration.
|
|
339
339
|
*/
|
|
340
340
|
__metadata?: Partial<OidcMetadata>;
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* WARNING: Setting this to true is a workaround for provider
|
|
344
|
+
* like Google OAuth that don't support JWT access token.
|
|
345
|
+
* Use at your own risk, this is a hack.
|
|
346
|
+
*/
|
|
347
|
+
__unsafe_useIdTokenAsAccessToken?: boolean;
|
|
341
348
|
} & (AutoLogin extends true ? {} : {});
|
|
342
349
|
|
|
343
350
|
export type Mock<AutoLogin, DecodedIdToken, AccessTokenClaims> = {
|