@aplons/auth 0.1.0
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 +164 -0
- package/dist/client.d.ts +93 -0
- package/dist/client.js +248 -0
- package/dist/discovery.d.ts +20 -0
- package/dist/discovery.js +57 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +80 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +16 -0
- package/dist/next.d.ts +72 -0
- package/dist/next.js +263 -0
- package/dist/pkce.d.ts +48 -0
- package/dist/pkce.js +75 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/dist/verify.d.ts +26 -0
- package/dist/verify.js +94 -0
- package/package.json +43 -0
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ein Token prüfen, statt ihm zu glauben.
|
|
3
|
+
*
|
|
4
|
+
* Ein JWT ist lesbar, ohne dass jemand etwas prüft — `atob` auf den mittleren
|
|
5
|
+
* Teil, fertig. Wer das für eine Prüfung hält, lässt jeden herein, der sich
|
|
6
|
+
* ein Token selbst schreibt. Geprüft wird deshalb die Unterschrift gegen die
|
|
7
|
+
* öffentlichen Schlüssel von Aplons, dazu Aussteller, Empfänger und Ablauf.
|
|
8
|
+
*
|
|
9
|
+
* `jose` erledigt das Kryptografische. Es ist die einzige Abhängigkeit dieses
|
|
10
|
+
* Pakets, und es ist dieselbe, die der Aplons-Server selbst benutzt.
|
|
11
|
+
*/
|
|
12
|
+
import { createRemoteJWKSet } from "jose";
|
|
13
|
+
import type { AccessTokenClaims, IdTokenClaims } from "./types.js";
|
|
14
|
+
type Schluesselquelle = ReturnType<typeof createRemoteJWKSet>;
|
|
15
|
+
export declare function schluesselFuer(jwksUri: string): Schluesselquelle;
|
|
16
|
+
export declare function pruefeZugriffstoken(token: string, options: {
|
|
17
|
+
issuer: string;
|
|
18
|
+
schluessel: Schluesselquelle;
|
|
19
|
+
}): Promise<AccessTokenClaims>;
|
|
20
|
+
export declare function pruefeIdToken(token: string, options: {
|
|
21
|
+
issuer: string;
|
|
22
|
+
audience: string;
|
|
23
|
+
nonce?: string;
|
|
24
|
+
schluessel: Schluesselquelle;
|
|
25
|
+
}): Promise<IdTokenClaims>;
|
|
26
|
+
export {};
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ein Token prüfen, statt ihm zu glauben.
|
|
3
|
+
*
|
|
4
|
+
* Ein JWT ist lesbar, ohne dass jemand etwas prüft — `atob` auf den mittleren
|
|
5
|
+
* Teil, fertig. Wer das für eine Prüfung hält, lässt jeden herein, der sich
|
|
6
|
+
* ein Token selbst schreibt. Geprüft wird deshalb die Unterschrift gegen die
|
|
7
|
+
* öffentlichen Schlüssel von Aplons, dazu Aussteller, Empfänger und Ablauf.
|
|
8
|
+
*
|
|
9
|
+
* `jose` erledigt das Kryptografische. Es ist die einzige Abhängigkeit dieses
|
|
10
|
+
* Pakets, und es ist dieselbe, die der Aplons-Server selbst benutzt.
|
|
11
|
+
*/
|
|
12
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
13
|
+
import { AplonsError } from "./errors.js";
|
|
14
|
+
/*
|
|
15
|
+
Eine Schlüsselquelle je Adresse, nicht je Aufruf.
|
|
16
|
+
|
|
17
|
+
`createRemoteJWKSet` bringt einen eigenen Zwischenspeicher mit und holt bei
|
|
18
|
+
einem unbekannten `kid` neu — das ist genau das Verhalten, das einen
|
|
19
|
+
Schlüsselwechsel überlebt. Für jeden Aufruf eine neue Quelle anzulegen
|
|
20
|
+
hieße, den Zwischenspeicher wegzuwerfen und bei jeder Anfrage die
|
|
21
|
+
Schlüssel erneut zu laden.
|
|
22
|
+
*/
|
|
23
|
+
const quellen = new Map();
|
|
24
|
+
export function schluesselFuer(jwksUri) {
|
|
25
|
+
let quelle = quellen.get(jwksUri);
|
|
26
|
+
if (!quelle) {
|
|
27
|
+
quelle = createRemoteJWKSet(new URL(jwksUri));
|
|
28
|
+
quellen.set(jwksUri, quelle);
|
|
29
|
+
}
|
|
30
|
+
return quelle;
|
|
31
|
+
}
|
|
32
|
+
export async function pruefeZugriffstoken(token, options) {
|
|
33
|
+
try {
|
|
34
|
+
const { payload } = await jwtVerify(token, options.schluessel, {
|
|
35
|
+
issuer: options.issuer,
|
|
36
|
+
// Bewusst ohne `audience`: ein Zugriffstoken ist an die Anwendung
|
|
37
|
+
// gerichtet, die es benutzt, und die kann eine andere sein als die,
|
|
38
|
+
// die es geholt hat. Wer das enger fassen will, prüft `aud` selbst.
|
|
39
|
+
});
|
|
40
|
+
return payload;
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
throw alsFehler(cause, "Das Zugriffstoken");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function pruefeIdToken(token, options) {
|
|
47
|
+
let claims;
|
|
48
|
+
try {
|
|
49
|
+
const { payload } = await jwtVerify(token, options.schluessel, {
|
|
50
|
+
issuer: options.issuer,
|
|
51
|
+
// Hier schon: ein ID-Token ist an genau diese Anwendung gerichtet.
|
|
52
|
+
// Eines, das für eine andere ausgestellt wurde, darf nicht zählen.
|
|
53
|
+
audience: options.audience,
|
|
54
|
+
});
|
|
55
|
+
claims = payload;
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
throw alsFehler(cause, "Das ID-Token");
|
|
59
|
+
}
|
|
60
|
+
/*
|
|
61
|
+
Der Nonce bindet das ID-Token an genau diesen Anmeldevorgang.
|
|
62
|
+
|
|
63
|
+
Ohne ihn ließe sich ein früher abgefangenes, noch gültiges ID-Token in
|
|
64
|
+
einem neuen Vorgang einreichen. Geprüft wird nur, wenn beim Start einer
|
|
65
|
+
gesetzt wurde — sonst gäbe es nichts zu vergleichen.
|
|
66
|
+
*/
|
|
67
|
+
if (options.nonce && claims.nonce !== options.nonce) {
|
|
68
|
+
throw new AplonsError({
|
|
69
|
+
code: "nonce_mismatch",
|
|
70
|
+
message: "Der nonce im ID-Token gehört nicht zu diesem Anmeldevorgang. " +
|
|
71
|
+
"Die Anmeldung wird nicht fortgesetzt.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return claims;
|
|
75
|
+
}
|
|
76
|
+
function alsFehler(cause, was) {
|
|
77
|
+
const code = cause && typeof cause === "object" && "code" in cause
|
|
78
|
+
? String(cause.code)
|
|
79
|
+
: "invalid_token";
|
|
80
|
+
const erklaerung = {
|
|
81
|
+
ERR_JWT_EXPIRED: "ist abgelaufen. Erneuere es mit dem Refresh-Token.",
|
|
82
|
+
ERR_JWT_CLAIM_VALIDATION_FAILED: "wurde für einen anderen Aussteller oder Empfänger ausgestellt.",
|
|
83
|
+
ERR_JWS_SIGNATURE_VERIFICATION_FAILED: "trägt keine gültige Unterschrift von Aplons.",
|
|
84
|
+
ERR_JOSE_NOT_SUPPORTED: "benutzt ein Verfahren, das hier nicht zugelassen ist — bei " +
|
|
85
|
+
'`alg: "none"` ist das ein selbst geschriebenes Token ohne Unterschrift.',
|
|
86
|
+
ERR_JWKS_NO_MATCHING_KEY: "ist mit einem Schlüssel unterschrieben, den Aplons nicht kennt. " +
|
|
87
|
+
"Bei einem gerade gewechselten Schlüssel hilft ein zweiter Versuch.",
|
|
88
|
+
};
|
|
89
|
+
return new AplonsError({
|
|
90
|
+
code,
|
|
91
|
+
cause,
|
|
92
|
+
message: `${was} ${erklaerung[code] ?? "ist nicht gültig."}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aplons/auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Anmeldung über Aplons in eigenen Anwendungen — OAuth 2.1 mit PKCE, ohne den Ablauf selbst zu schreiben.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./next": {
|
|
22
|
+
"types": "./dist/next.d.ts",
|
|
23
|
+
"default": "./dist/next.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
28
|
+
"prepublishOnly": "pnpm build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"jose": "^6.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"oauth",
|
|
38
|
+
"oidc",
|
|
39
|
+
"pkce",
|
|
40
|
+
"authentication",
|
|
41
|
+
"aplons"
|
|
42
|
+
]
|
|
43
|
+
}
|