@hyper.authio/server 0.9.0 → 0.10.1
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/README.md +27 -2
- package/dist/index.cjs +8 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -7
- package/dist/index.d.ts +16 -7
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ try {
|
|
|
36
36
|
| `requiredScopes` | | Scopes every request must carry (all); `authioBearer` answers `403 insufficient_scope`. |
|
|
37
37
|
| `clientEncryptionKey`, `clientEncryptionKeyPath` | | Decrypts access tokens Authio encrypts to this key and validates them locally. Without it nothing is decrypted. RSA1_5 is refused (`legacy_algorithm`), even with `allowLegacyEncryptionAlgorithms`. |
|
|
38
38
|
| `encryptionAlgorithms`, `encryptionMethods` | `RSA-OAEP`, `RSA-OAEP-256`; the CBC-HS methods | The JWE `alg` and `enc` accepted, as registered in Authio (AES-GCM may be listed; RSA1_5 cannot: Node cannot decrypt it). |
|
|
39
|
-
| `introspection` | `none` | `none`: local JWT validation. `activeCheck`: then confirmed active by introspection (cached per `jti`), so a revoked token, an ended session or a suspension is refused before the token expires. `introspectionOnly`: introspection alone, for opaque tokens (or encrypted ones without the key). |
|
|
39
|
+
| `introspection` | `none` | `none`: local JWT validation; the caller is read from the token alone. `activeCheck`: then confirmed active by introspection (cached per `jti`), so a revoked token, an ended session or a suspension is refused before the token expires; the caller is read from the token, plus the claims of the answer the token does not carry. `introspectionOnly`: introspection alone, for opaque (`Reference`) tokens (or encrypted ones without the key); the caller is read from the answer. A `Lightweight` access token needs `activeCheck` or `introspectionOnly` (see below). |
|
|
40
40
|
| `client` | | The API's confidential client, needed by introspection: an `AuthioClient` or `{ clientId, clientSecret }` (or a private key), with `tokenEndpointAuthMethod` as the client is registered. It must be the resource server's introspection client. |
|
|
41
41
|
| `introspectionCacheDuration` | 30 s | Never past the token's expiry; only active answers are kept. |
|
|
42
42
|
| `clockSkew` | 30 s | |
|
|
@@ -51,6 +51,31 @@ A refusal is an `AuthioBearerError` whose `wwwAuthenticate` is `Bearer` when the
|
|
|
51
51
|
`not_access_token`, `reference_token`, `decryption_failed`, …, and `inactive` / `unconfirmed` for introspection). A
|
|
52
52
|
token addressed elsewhere is described with the audience the API accepts; introspection failures fail closed.
|
|
53
53
|
|
|
54
|
+
### Lightweight and reference access tokens
|
|
55
|
+
|
|
56
|
+
Authio hands out a client's access tokens in the form its access token format setting names:
|
|
57
|
+
|
|
58
|
+
| Format | The token | Validate it with |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `Jwt` | A self-contained JWT with every claim. | Any mode. |
|
|
61
|
+
| `Lightweight` | A JWT with only the identity and session claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`, `typ`, `client_id`, `azp`, `scope`, `sid`, `auth_time`, `acr`, `amr`, `idp`, `act`, `delegation_id`, `session_client`, `org_id`, `org_alias`). The `*_resource_access` roles and permissions, `subscription`, the profile and the email are only in the introspection answer. | `activeCheck` or `introspectionOnly`, which read the caller with the answer's claims, so `roles`, `permissions`, `kind` and `email` are complete. With `none` the caller has only what the token carries: no role or permission, and a consumer reads as a `client`. |
|
|
62
|
+
| `Reference` | An opaque handle, no JWT (refused locally as `reference_token`). | `introspectionOnly`. |
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const verifier = createAuthioVerifier({
|
|
66
|
+
issuer,
|
|
67
|
+
resource: "https://orders.example.com",
|
|
68
|
+
introspection: "activeCheck", // the client's access token format is Lightweight
|
|
69
|
+
client: { clientId: "orders-api", clientSecret: process.env.AUTHIO_CLIENT_SECRET! },
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Nothing tells a lightweight token apart from a full one that carries no role (a person without roles has no
|
|
74
|
+
`*_resource_access` either), so the verifier never guesses: the mode decides. The introspection answer is cached per
|
|
75
|
+
`jti` for `introspectionCacheDuration`, so `activeCheck` asks Authio once per token and cache period. The answer only
|
|
76
|
+
adds claims: one the token carries keeps the token's value, which its signature vouches for, whatever the answer says
|
|
77
|
+
(it names `client_id` for `azp`).
|
|
78
|
+
|
|
54
79
|
### Protected resource metadata (RFC 9728)
|
|
55
80
|
|
|
56
81
|
```ts
|
|
@@ -83,7 +108,7 @@ option.
|
|
|
83
108
|
|
|
84
109
|
## The caller
|
|
85
110
|
|
|
86
|
-
`AuthioUser` reads every form of permission, the same way from a JWT
|
|
111
|
+
`AuthioUser` reads every form of permission, the same way from a JWT, an introspection answer or both (`activeCheck`):
|
|
87
112
|
|
|
88
113
|
```ts
|
|
89
114
|
user.subject; user.clientId; user.scopes;
|
package/dist/index.cjs
CHANGED
|
@@ -89,7 +89,10 @@ var AuthioUser = class _AuthioUser {
|
|
|
89
89
|
credentialType;
|
|
90
90
|
/** `credential_id`. */
|
|
91
91
|
credentialId;
|
|
92
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Every claim, as the token or the introspection answer carried it (with `activeCheck`, the token's, plus the
|
|
94
|
+
* answer's the token does not carry).
|
|
95
|
+
*/
|
|
93
96
|
claims;
|
|
94
97
|
flatRoles;
|
|
95
98
|
/**
|
|
@@ -657,8 +660,9 @@ function createAuthioVerifier(options) {
|
|
|
657
660
|
const type = typeof payload.typ === "string" ? payload.typ : void 0;
|
|
658
661
|
if (rejectNonAccessTokens && type !== void 0 && type.toLowerCase() !== "bearer")
|
|
659
662
|
throw new AuthioBearerError(`Only access tokens are accepted; this token's typ is '${type}'.`, { reason: "not_access_token" });
|
|
660
|
-
if (introspection
|
|
661
|
-
|
|
663
|
+
if (introspection !== "activeCheck") return new AuthioUser(payload, claimSettings, token);
|
|
664
|
+
const answer = await confirmActive(token, payload, signal);
|
|
665
|
+
return new AuthioUser({ ...claimsOfIntrospection(answer), ...payload }, claimSettings, token);
|
|
662
666
|
};
|
|
663
667
|
const confirmActive = async (token, payload, signal) => {
|
|
664
668
|
const key = payload.jti ? `jti:${payload.jti}` : await keyForToken(token);
|
|
@@ -670,6 +674,7 @@ function createAuthioVerifier(options) {
|
|
|
670
674
|
throw new AuthioBearerError(AuthioBearerErrorDescriptions.Unconfirmed, { cause: error, reason: "unconfirmed" });
|
|
671
675
|
}
|
|
672
676
|
if (!result) throw new AuthioBearerError(AuthioBearerErrorDescriptions.Inactive, { reason: "inactive" });
|
|
677
|
+
return result;
|
|
673
678
|
};
|
|
674
679
|
const verifyByIntrospection = async (token, signal) => {
|
|
675
680
|
let result;
|