@ e受影响/ jwt
签名和验证 JWTs for Effect v4,并从发行商已公布的密钥集中解析校验密钥. 一层薄的地层 effect/jose,它运行每个签名、密钥导入和权利要求检查,所以它在WebCrypto的任何地方都有效:Node、Bun、Deno、浏览器和Cloudflare工人,没有 node: 导入.
预
1.0.0。 。 。 . 此软件包是@effected/*工具箱,在 稳定效果 v4并仍在0.x发展。 为了保持你的effect财务报告和审定财务报表@effect/*该工具包在线上构建和测试、安装@effected/pnpm-plugin-effected。 。 。 .稳定:不稳定. 此软件包的 API 表面尚未考虑 完成并可能改变
0.x释放 准确的版本。 满 政策: 释放策略。 。 。 .
为什么 @ effect/jwt
核心 effect/jose 已签名并验证 JWS 和 JWT, 导入 JWK 并获取密钥集 。 当您对照您已经持有的密钥或一个固定的HTTPS JWKS URL进行校验时直接使用它(Jwks.layerRemote,用 PKCS#8 键签名,或需要 HMAC 或 JWE .
当您需要核心留给您时,请联系这个包:
- OIDC发现的钥匙.
JwksResolver找到每个发行人的密钥集<issuer>/.well-known/openid-configuration,检查发现文件名称 准确的发布者,服务于许多发布者 从一个服务kid。 。 。 . - 你选择的缓存 . 获取的密钥集以
JwksStore: 与JwksStore.layerMemory,或者在工人KV中, 所以每一个孤立者共享一个获取. - PEMs的人确实有.
JwtKey.fromPkcs8Pem使用 PKCS# 8 和 PKCS# 1 ()BEGIN RSA PRIVATE KEY,格式 github.com 将App 键输出出去),包括一条新行到达的键作为逃出\n在一行环境变量中. - 更安全的违约.
Jwt.verify允许 60 秒时钟 skew, 需要exp除非你们选择退出,并且拒绝将来发行的标志。 Core的默认值为零 skew 和可选exp。 。 。 . - 一个路径错误 . 每一次失败都是
JwtError有个公寓reason绳子. - 试发会.
TestIssuer标志通过真正的解析器进行验证,没有网络.
钥匙是核心本身的类型,所以是来自 JwtKey 也与核心协作 Jws 财务报告和审定财务报表 Jwt 直接说.
安装
npm install @effected/jwt effect
pnpm add @effected/jwt effect
在节点上,它需要Node.js QQ24.11.0;它还运行在任何其他WebCrypto运行时间上,Cloudflare Workers也包括在内. effect v4是唯一的对等器. 全体 @effected/* 软件包仅限无害环境管理.
提供什么
在您程序边缘提供的核心服务上签名和验证运行 :
| 运行 | 要求数 |
|---|---|
Jws.sign, (中文(简体) ), Jws.verify, (中文(简体) ), Jwt.sign, (中文(简体) ), Jwt.verify, (中文(简体) ), JwtKey.generate, (中文(简体) ), TestIssuer | Signature.Signature |
JwksResolver.layer, (中文(简体) ), JwksResolver.layerWith | HttpClient.HttpClient 财务报告和审定财务报表 JwksStore |
Jwt.verify 键 JwksResolver.forIssuer | Signature.Signature 财务报告和审定财务报表 JwksResolver |
JwtKey.fromPkcs8Pem, (中文(简体) ), JwtKey.fromJwk 财务报告和审定财务报表 Jws.decodeUnverified 不需要任何东西.
| 服务 | 任何 WebCrypto 运行时间( 包括工人) | 节点 |
|---|---|---|
Signature.Signature | Signature.layerWebCrypto.pipe(Layer.provide(SecureRandom.layerWebCrypto)) 从 effect/crypto | NodeCrypto.layerSignature 从 @effect/platform-node |
HttpClient.HttpClient | FetchHttpClient.layer 从 effect/http | NodeHttpClient.layerUndici 从 @effect/platform-node,或 FetchHttpClient.layer |
JwksStore | JwksStore.layerMemory,或您自己的 KV 备份层 | JwksStore.layerMemory |
import { JwksResolver, JwksStore } from "@effected/jwt";
import { Layer } from "effect";
import { SecureRandom, Signature } from "effect/crypto";
import { FetchHttpClient } from "effect/http";
const CryptoLive = Signature.layerWebCrypto.pipe(Layer.provide(SecureRandom.layerWebCrypto));
const JwtLive = Layer.merge(
JwksResolver.layer.pipe(Layer.provide(Layer.merge(FetchHttpClient.layer, JwksStore.layerMemory))),
CryptoLive,
);
签署 JWT
来自 PKCS# 1 关键 github.com 的 GitHub App JWT 问题. 标题是 {"alg":"RS256","typ":"JWT"}数字 : alg 总是来自钥匙.
import { Jwt, JwtKey } from "@effected/jwt";
import { Effect, Redacted } from "effect";
const signAppJwt = (pem: Redacted.Redacted<string>, appId: string, nowSeconds: number) =>
Effect.gen(function* () {
const key = yield* JwtKey.fromPkcs8Pem(pem, { alg: "RS256" });
return yield* Jwt.sign({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: appId }, key);
});
对照发行人公布的密钥校验
把发行人交给你 forIssuer,从来没有人从符号读取: 发行者决定哪些密钥是信任的。 总是通过 audience 对于一个共享的发行者,其一键为每个依赖方设置了标志.
import { JwksResolver, Jwt } from "@effected/jwt";
import { Schema } from "effect";
const Claims = Schema.Struct({ sub: Schema.String });
const verify = (token: string) =>
Jwt.verify(token, {
key: JwksResolver.forIssuer("https://issuer.example"),
claims: Claims,
issuer: "https://issuer.example",
audience: "my-service",
});
// Effect<{ readonly sub: string }, JwtError, JwksResolver | Signature>
检查按此顺序进行:签名,然后 exp 现在, iss, (中文(简体) ), aud, (中文(简体) ), exp 未通过, nbf 到达,你的 claims 计划,最后 iat 未来不会. clockTolerance 财务报告和审定财务报表 requireExpiry 调整默认值.
用您持有的密钥校验
key 是一个 VerificationKey,或从符号头到一的函数.
import { Jwk, Jwt, JwtKey } from "@effected/jwt";
import { Effect, Schema } from "effect";
const verifyWith = (jwk: Jwk, token: string) =>
Effect.gen(function* () {
const key = yield* JwtKey.fromJwk(jwk);
return yield* Jwt.verify(token, { key, claims: Schema.Struct({ sub: Schema.String }) });
});
Jws 在没有索赔规则的情况下是同一签字层(Jws.sign, (中文(简体) ), Jws.verify),以及 Jws.decodeUnverified 读取符号头和有效载荷作为同步 Result,用于路由 kid 或者说 iss 在选择密钥之前。 永远不要相信它回来.
算法
每个不对称算法核心设备: RS256, (中文(简体) ), RS384, (中文(简体) ), RS512, (中文(简体) ), PS256, (中文(简体) ), PS384, (中文(简体) ), PS512, (中文(简体) ), ES256, (中文(简体) ), ES384, (中文(简体) ), ES512 财务报告和审定财务报表 EdDSA (Ed25519) (英语). 该算法出自密钥,从不给符头,2048位以下的RSA密钥被拒绝. HMAC 被拒绝, 因为密钥集中公布的密钥绝不能作为共享密钥; 对于 HMAC 使用 Core 的密钥 Jwk.importSecret 带有核心 Jwt。 。 。 . alg: "none" 永远不会被接受.
错误
每一次失败都是 JwtError路径 reason永远不要 message 或者说 detail当核心产生故障时 JoseError 这是 cause。 。 。 .
reason | 含义 |
|---|---|
malformed | 不是紧凑的JWS:错误的片段计数,非冠基64url,一个不是JOSE头的片头, alg: "none" 或未知算法,a crit 或者说 b64 或不是JSON的有效载荷. |
unsupportedAlgorithm | 符号或JWK命名为HMAC(或其他非不对称)算法. 未解决密钥 . |
algorithmMismatch | 标志 alg 不是钥匙的算法,也不是JWK的算法 alg 与所需的不同. |
badSignature | 签名不验证 . |
unknownKid | 组合中没有匹配符号算法的密钥和 kid,或几个做. |
expired | exp 已经过去了,超越了容忍. |
notYetValid | nbf 未来,或 iat 是,超越宽容. |
wrongIssuer | iss 缺少或没有被接受的发行人. |
wrongAudience | aud 缺少或没有被接受的听众. |
claims | 注册债权有错误的类型, exp 缺少,有效载荷 不是JSON物体,你的 claims schema拒绝了,否则钟容无效. |
key | 关键材料没有导入:一个不正确或不支持的PEM,一个2048位以下的RSA密钥,一个JWK,其类型,曲线, use 或者说 key_ops 无法验证 . |
jwksFetch | 无法发现或获取密钥集: 无法获取、 缓慢、 超大小、 不 https:,转出 https:,一个发现文件,指定另一个发行人,或者一个没有支持密钥的集. |
一个符号字符串不是身份:ECDSA签名是可商的,所以一个被签名的有效载荷可以有两个有效的符号字符串. 检测在核实的索赔中重放和撤销(jti,或 iss 与 sub 财务报告和审定财务报表 iat),从未在符号文本上.
测试
@effected/jwt/testing 出口 TestIssuer: 生成的密钥对, 用于邮票的签名符 iss, (中文(简体) ), iat 还有十分钟 exp,以及 resolverLayer真实的 JwksResolver 在一个记忆中 HttpClient 为发行人的发现文件和密钥集服务.
import { JwksResolver, Jwt } from "@effected/jwt";
import { TestIssuer } from "@effected/jwt/testing";
import { Effect, Schema } from "effect";
const program = Effect.gen(function* () {
const issuer = yield* TestIssuer.make({ issuer: "https://issuer.test" });
const token = yield* issuer.sign({ aud: "my-app", sub: "user-1" });
return yield* Jwt.verify(token, {
key: JwksResolver.forIssuer("https://issuer.test"),
claims: Schema.Struct({ sub: Schema.String }),
issuer: "https://issuer.test",
audience: "my-app",
}).pipe(Effect.provide(issuer.resolverLayer));
});
// provide CryptoLive: TestIssuer and Jwt.verify both need Signature
提供 resolverLayer 当它依赖于缓存或旋转时 整个测试一次. TestIssuer.rotate(issuer, kid) 在新键下添加密钥 kid- 解析器最多每30秒调试一次 TestClock 在用新密钥验证前。 与 @effect/vitest,提供 Signature 每个测试(layer(CryptoLive, { buildPerTest: true }):为整个区块建造的一层 TestClock 整个测试.