diff --git a/docs/README.md b/docs/README.md
index 28e251177..751a4f87f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2288,6 +2288,7 @@ _**default value**_:
{
AccessToken: 'opaque',
ClientCredentials: 'opaque',
+ bitsOfOpaqueRandomness: 256,
customizers: {
'jwt-ietf': undefined,
jwt: undefined
@@ -2320,6 +2321,29 @@ Configure `formats`:
```
+### formats.bitsOfOpaqueRandomness
+
+The value should be an integer (or a function returning an integer) and the resulting opaque token length is equal to `Math.ceil(i / Math.log2(n))` where n is the number of symbols in the used alphabet, 64 in our case.
+
+
+
+_**default value**_:
+```js
+256
+```
+(Click to expand) To have e.g. Refresh Tokens values longer than Access Tokens.
+
+
+```js
+function bitsOfOpaqueRandomness(ctx, token) {
+ if (token.kind === 'RefreshToken') {
+ return 384;
+ }
+ return 256;
+}
+```
+
+
### formats.customizers
Functions used before signing a structured Access Token of a given type, such as a JWT one. Customizing here only changes the structured Access Token, not your storage, introspection or anything else. For such extras use [`extraTokenClaims`](#extratokenclaims) instead.
diff --git a/lib/helpers/defaults.js b/lib/helpers/defaults.js
index 991be2b1b..9313a6720 100644
--- a/lib/helpers/defaults.js
+++ b/lib/helpers/defaults.js
@@ -1698,6 +1698,26 @@ function getDefaults() {
* This helper should resolve with a JWS Algorithm string.
*/
tokenSigningAlg,
+
+ /*
+ * formats.bitsOfOpaqueRandomness
+ *
+ * description: The value should be an integer (or a function returning an integer) and the
+ * resulting opaque token length is equal to `Math.ceil(i / Math.log2(n))` where n is the
+ * number of symbols in the used alphabet, 64 in our case.
+ *
+ * example: To have e.g. Refresh Tokens values longer than Access Tokens.
+ * ```js
+ * function bitsOfOpaqueRandomness(ctx, token) {
+ * if (token.kind === 'RefreshToken') {
+ * return 384;
+ * }
+ *
+ * return 256;
+ * }
+ * ```
+ */
+ bitsOfOpaqueRandomness: 256,
AccessToken: 'opaque',
ClientCredentials: 'opaque',
diff --git a/lib/models/formats/opaque.js b/lib/models/formats/opaque.js
index 0658e3af5..1ea6e59df 100644
--- a/lib/models/formats/opaque.js
+++ b/lib/models/formats/opaque.js
@@ -8,12 +8,21 @@ const nanoid = require('../../helpers/nanoid');
const ctxRef = require('../ctx_ref');
const withExtra = new Set(['AccessToken', 'ClientCredentials']);
+const bitsPerSymbol = Math.log2(64);
+const tokenLength = (i) => Math.ceil(i / bitsPerSymbol);
module.exports = (provider) => ({
- // Default nanoid has a (26+26+10+2 = 64) symbol alphabet (6 bits). So with 6 bits per symbol, and
- // 43 symbols => (6*27 = 258) total bits.
generateTokenId() {
- return nanoid(43);
+ let length;
+ if (this.kind !== 'PushedAuthorizationRequest') {
+ const bitsOfOpaqueRandomness = instance(provider).configuration('formats.bitsOfOpaqueRandomness');
+ if (typeof bitsOfOpaqueRandomness === 'function') {
+ length = tokenLength(bitsOfOpaqueRandomness(ctxRef.get(this), this));
+ } else {
+ length = tokenLength(bitsOfOpaqueRandomness);
+ }
+ }
+ return nanoid(length);
},
async getValueAndPayload() {
const now = epochTime();
diff --git a/types/index.d.ts b/types/index.d.ts
index bed9d9dea..432a84a8c 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -972,6 +972,7 @@ export interface Configuration {
AccessToken?: AccessTokenFormatFunction | TokenFormat;
ClientCredentials?: ClientCredentialsFormatFunction | TokenFormat;
tokenSigningAlg?: (ctx: KoaContextWithOIDC, token: AccessToken | ClientCredentials) => CanBePromise;
+ bitsOfOpaqueRandomness?: number | ((ctx: KoaContextWithOIDC, token: BaseToken) => number);
customizers?: {
jwt?: (ctx: KoaContextWithOIDC, token: AccessToken | ClientCredentials, parts: JWTStructured) => CanBePromise;
'jwt-ietf'?: (ctx: KoaContextWithOIDC, token: AccessToken | ClientCredentials, parts: JWTStructured) => CanBePromise;