Skip to content

Commit 5986b1c

Browse files
committed
feat(customTokenExchange): add subject_token and organization input validation
1 parent 39d6552 commit 5986b1c

3 files changed

Lines changed: 57 additions & 4 deletions

File tree

index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ interface CustomTokenExchangeOptions {
157157
*/
158158
requested_token_type?: string;
159159
/**
160-
* Auth0 organization ID or name to use for the exchange.
161-
* When provided, the issued token will be scoped to that organization.
160+
* Organization ID or name to scope the exchange to.
161+
* When provided, the issued token will be bound to that organization.
162162
*/
163163
organization?: string;
164164
/** Requested audience. Defaults to `authorizationParams.audience`. */

lib/context.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,27 @@ function validateTokenExchangeExtras(extra) {
139139
return result;
140140
}
141141

142+
/**
143+
* Fails fast on common subject_token mistakes before hitting the network.
144+
*/
145+
function validateSubjectToken(token) {
146+
if (typeof token !== 'string' || token.trim().length === 0) {
147+
throw createError(400, 'subject_token is required for token exchange');
148+
}
149+
if (token !== token.trim()) {
150+
throw createError(
151+
400,
152+
'subject_token must not include leading or trailing whitespace',
153+
);
154+
}
155+
if (/^bearer\s+/i.test(token)) {
156+
throw createError(
157+
400,
158+
"subject_token must not include the 'Bearer ' prefix",
159+
);
160+
}
161+
}
162+
142163
/**
143164
* Extracts the `act` claim for delegation flows.
144165
* Prefers the id_token claims (via the helper openid-client attaches to every
@@ -365,8 +386,10 @@ class RequestContext {
365386
extra,
366387
} = options;
367388

368-
if (!subject_token) {
369-
throw createError(400, 'subject_token is required for token exchange');
389+
validateSubjectToken(subject_token);
390+
391+
if (organization !== undefined && !organization.trim()) {
392+
throw createError(400, 'organization must not be blank');
370393
}
371394

372395
if (actor_token !== undefined && actor_token_type === undefined) {

test/customTokenExchange.tests.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,36 @@ describe('customTokenExchange', () => {
109109
);
110110
});
111111

112+
it('throws 400 when subject_token has leading or trailing whitespace', async () => {
113+
const { response } = await setup({
114+
exchangeOptions: { subject_token: ' token_with_spaces ' },
115+
});
116+
assert.equal(response.statusCode, 400);
117+
assert.equal(
118+
response.body.err.message,
119+
'subject_token must not include leading or trailing whitespace',
120+
);
121+
});
122+
123+
it("throws 400 when subject_token includes a 'Bearer ' prefix", async () => {
124+
const { response } = await setup({
125+
exchangeOptions: { subject_token: 'Bearer __test_token__' },
126+
});
127+
assert.equal(response.statusCode, 400);
128+
assert.equal(
129+
response.body.err.message,
130+
"subject_token must not include the 'Bearer ' prefix",
131+
);
132+
});
133+
134+
it('throws 400 when organization is blank', async () => {
135+
const { response } = await setup({
136+
exchangeOptions: { organization: ' ' },
137+
});
138+
assert.equal(response.statusCode, 400);
139+
assert.equal(response.body.err.message, 'organization must not be blank');
140+
});
141+
112142
it('applies authorizationParams defaults for audience and scope', async () => {
113143
const { capturedBody } = await setup({
114144
authConfig: {

0 commit comments

Comments
 (0)