🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@fluojs/jwt

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/jwt - npm Package Compare versions

Comparing version
1.0.0-beta.4
to
1.0.0-beta.5
+1
-1
dist/module.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAiC,MAAM,cAAc,CAAC;AAOhH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,KAAK,UAAU,GAAG,WAAW,CAAC;AAyE9B;;GAEG;AACH,qBAAa,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAQvD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,UAAU;IASvG,OAAO,CAAC,MAAM,CAAC,YAAY;CAkB5B"}
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAsD,MAAM,cAAc,CAAC;AAOrI,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIrD,KAAK,UAAU,GAAG,WAAW,CAAC;AAyE9B;;GAEG;AACH,qBAAa,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAQvD,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,kBAAkB,CAAC,kBAAkB,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,UAAU;IASvG,OAAO,CAAC,MAAM,CAAC,YAAY;CAkB5B"}

@@ -12,2 +12,3 @@ import type { DefaultJwtSigner } from '../signing/signer.js';

consume?(input: RefreshTokenConsumeInput): Promise<RefreshTokenConsumeResult>;
rotate?(input: RefreshTokenRotateInput): Promise<RefreshTokenConsumeResult>;
}

@@ -24,2 +25,8 @@ /**

/**
* Describes the durable refresh token rotation input contract.
*/
export interface RefreshTokenRotateInput extends RefreshTokenConsumeInput {
replacement: RefreshTokenRecord;
}
/**
* Defines the refresh token consume result type.

@@ -72,4 +79,6 @@ */

private issueRefreshTokenWithFamily;
private consumeRefreshToken;
private createRefreshTokenWithFamily;
private verifyRefreshClaims;
}
//# sourceMappingURL=refresh-token.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC/E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA2B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA+DhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YA6B3B,mBAAmB;CA4BlC"}
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC9E,MAAM,CAAC,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA6B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA0EhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YAQ3B,mBAAmB;YAUnB,4BAA4B;YA8B5B,mBAAmB;CA4BlC"}

@@ -13,2 +13,6 @@ import { randomUUID } from 'node:crypto';

/**
* Describes the durable refresh token rotation input contract.
*/
/**
* Defines the refresh token consume result type.

@@ -44,4 +48,4 @@ */

}
if (options.rotation && typeof options.store.consume !== 'function') {
throw new JwtConfigurationError('Refresh token rotation requires an atomic store.consume() implementation.');
if (options.rotation && typeof options.store.rotate !== 'function' && typeof options.store.consume !== 'function') {
throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
}

@@ -69,19 +73,28 @@ return {

if (this.options.rotation) {
if (!this.options.store.consume) {
throw new JwtConfigurationError('Refresh token rotation requires an atomic store.consume() implementation.');
if (!this.options.store.rotate && !this.options.store.consume) {
throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
}
const consumeResult = await this.options.store.consume({
const next = await this.createRefreshTokenWithFamily(claims.sub, claims.family);
const accessToken = await this.signer.signAccessToken({
sub: claims.sub
});
const consumeResult = this.options.store.rotate ? await this.options.store.rotate({
family: claims.family,
now: new Date(),
replacement: next.record,
subject: claims.sub,
tokenId: claims.jti
}) : await this.consumeRefreshToken({
family: claims.family,
now: new Date(),
subject: claims.sub,
tokenId: claims.jti
});
if (consumeResult === 'consumed') {
const refreshToken = await this.issueRefreshTokenWithFamily(claims.sub, claims.family);
const accessToken = await this.signer.signAccessToken({
sub: claims.sub
});
if (!this.options.store.rotate) {
await this.options.store.save(next.record);
}
return {
accessToken,
refreshToken
refreshToken: next.token
};

@@ -130,6 +143,20 @@ }

async issueRefreshTokenWithFamily(subject, family) {
const {
record,
token
} = await this.createRefreshTokenWithFamily(subject, family);
await this.options.store.save(record);
return token;
}
async consumeRefreshToken(input) {
if (!this.options.store.consume) {
throw new JwtConfigurationError('Refresh token rotation requires an atomic store.rotate() or store.consume() implementation.');
}
return this.options.store.consume(input);
}
async createRefreshTokenWithFamily(subject, family) {
const now = Math.floor(Date.now() / 1000);
const tokenId = randomUUID();
const expiresAt = new Date((now + this.options.expiresInSeconds) * 1000);
const tokenRecord = {
const record = {
createdAt: new Date(now * 1000),

@@ -150,5 +177,7 @@ expiresAt,

};
const refreshToken = await this.signer.signRefreshToken(claims);
await this.options.store.save(tokenRecord);
return refreshToken;
const token = await this.signer.signRefreshToken(claims);
return {
record,
token
};
}

@@ -155,0 +184,0 @@ async verifyRefreshClaims(token) {

@@ -23,3 +23,4 @@ import { DefaultJwtSigner } from './signing/signer.js';

* Accepts seconds or a short duration literal such as `"60s"`, `"15m"`,
* `"1h"`, or `"7d"`.
* `"1h"`, or `"7d"`. Numeric seconds preserve fractional JWT
* NumericDate precision.
*/

@@ -26,0 +27,0 @@ expiresIn?: number | `${number}${DurationUnit}`;

@@ -1,1 +0,1 @@

{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAe,MAAM,uBAAuB,CAAC;AAExE,KAAK,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAoD1C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;IAChD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,qBACa,UAAU;IAGnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFzB,QAAQ,EAAE,kBAAkB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAG/C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBnE;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ7E;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAmB/B"}
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAe,MAAM,uBAAuB,CAAC;AAExE,KAAK,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAoD1C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;IAChD;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,UAAU,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,qBACa,UAAU;IAGnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFzB,QAAQ,EAAE,kBAAkB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAG/C;;;;;;;;;;;;;;;OAeG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBnE;;;;;;;;;;;;;;;;;;OAkBG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ7E;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAmB/B"}

@@ -35,3 +35,3 @@ let _initClass;

}
return Math.floor(expiresIn);
return expiresIn;
}

@@ -38,0 +38,0 @@ const trimmed = expiresIn.trim();

@@ -180,3 +180,3 @@ let _initClass;

}
const scopes = Array.isArray(claims.scopes) ? claims.scopes.filter(scope => typeof scope === 'string') : typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : undefined;
const scopes = Array.isArray(claims.scopes) ? claims.scopes.filter(scope => typeof scope === 'string' && scope.length > 0) : typeof claims.scope === 'string' ? claims.scope.split(' ').filter(Boolean) : undefined;
const roles = Array.isArray(claims.roles) ? claims.roles.filter(role => typeof role === 'string') : undefined;

@@ -183,0 +183,0 @@ return {

@@ -12,3 +12,3 @@ {

],
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"private": false,

@@ -40,4 +40,4 @@ "license": "MIT",

"dependencies": {
"@fluojs/core": "^1.0.0-beta.5",
"@fluojs/di": "^1.0.0-beta.7",
"@fluojs/core": "^1.0.0-beta.6",
"@fluojs/di": "^1.0.0-beta.8",
"@fluojs/runtime": "^1.0.0-beta.12"

@@ -44,0 +44,0 @@ },

@@ -113,3 +113,3 @@ # @fluojs/jwt

`JwtService.sign(payload, { expiresIn })`를 사용할 때는 payload 안에 기존 `exp` 값이 있더라도 호출 시점의 `expiresIn` 재정의가 항상 우선합니다. 따라서 토큰 수명은 호출 위치에서 결정적으로 제어됩니다. `expiresIn`은 초 단위의 0 이상 숫자 또는 `60s`, `15m`, `1h`, `7d` 같은 짧은 duration 문자열을 받을 수 있습니다.
`JwtService.sign(payload, { expiresIn })`를 사용할 때는 payload 안에 기존 `exp` 값이 있더라도 호출 시점의 `expiresIn` 재정의가 항상 우선합니다. 따라서 토큰 수명은 호출 위치에서 결정적으로 제어됩니다. `expiresIn`은 초 단위의 0 이상 숫자 또는 `60s`, `15m`, `1h`, `7d` 같은 짧은 duration 문자열을 받을 수 있습니다. 숫자 초 값은 JWT NumericDate의 소수 정밀도를 보존하며, 문자열 duration은 기존처럼 정수 초 리터럴로 처리됩니다.

@@ -158,3 +158,3 @@ ## 일반적인 패턴

`RefreshTokenService`는 전용 HMAC refresh-token 경로를 사용합니다. `refreshToken.secret`은 access-token 서명 키와 별도로 설정하세요. Rotation은 재사용된 토큰을 안정적으로 감지할 수 있도록 atomic `RefreshTokenStore.consume(...)` 구현을 필요로 합니다.
`RefreshTokenService`는 전용 HMAC refresh-token 경로를 사용합니다. `refreshToken.secret`은 access-token 서명 키와 별도로 설정하세요. Rotation은 `RefreshTokenStore.rotate(...)`를 사용해 현재 토큰을 소비 처리하고 대체 토큰을 같은 durable store 작업 안에서 저장할 수 있습니다. 따라서 성공한 rotation은 저장된 후속 토큰 없이 기존 토큰만 소비하지 않습니다. 기존 atomic `consume(...)` hook만 구현한 store도 계속 지원하지만, 대체 토큰 저장의 내구성은 store가 소유한 `rotate(...)` 작업에 달려 있습니다.

@@ -161,0 +161,0 @@ ## 설정 가드레일

@@ -113,3 +113,3 @@ # @fluojs/jwt

When you use `JwtService.sign(payload, { expiresIn })`, the per-call `expiresIn` override always wins over any pre-existing `payload.exp` value so token lifetime stays deterministic at the call site. `expiresIn` accepts a non-negative number of seconds or short duration strings such as `60s`, `15m`, `1h`, or `7d`.
When you use `JwtService.sign(payload, { expiresIn })`, the per-call `expiresIn` override always wins over any pre-existing `payload.exp` value so token lifetime stays deterministic at the call site. `expiresIn` accepts a non-negative number of seconds or short duration strings such as `60s`, `15m`, `1h`, or `7d`. Numeric seconds preserve fractional JWT NumericDate precision; string durations remain whole-second literals.

@@ -158,3 +158,3 @@ ## Common Patterns

`RefreshTokenService` uses a dedicated HMAC refresh-token path. Configure `refreshToken.secret` separately from access-token signing keys. Rotation requires an atomic `RefreshTokenStore.consume(...)` implementation so replayed tokens can be detected reliably.
`RefreshTokenService` uses a dedicated HMAC refresh-token path. Configure `refreshToken.secret` separately from access-token signing keys. Rotation can use `RefreshTokenStore.rotate(...)` to atomically mark the current token as consumed and persist the replacement token in the same durable store operation, so a successful rotation never consumes the old token without a stored successor. Stores that only implement the older atomic `consume(...)` hook remain supported, but durable replacement persistence depends on the store-owned `rotate(...)` operation.

@@ -161,0 +161,0 @@ ## Configuration Guardrails