Skip to content

Commit 75be6ce

Browse files
committed
refactor: relax native app custom URI scheme validation
closes #1411 Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent e5d92f4 commit 75be6ce

3 files changed

Lines changed: 29 additions & 58 deletions

File tree

lib/helpers/client_schema.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import pick from './_/pick.js';
99
import omitBy from './_/omit_by.js';
1010

1111
const W3CEmailRegExp = /^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
12+
// eslint-disable-next-line no-script-url
13+
const FORBIDDEN_SCHEMES = new Set(['javascript:', 'vbscript:', 'data:', 'blob:', 'file:', 'about:']);
1214
const needsJwks = {
1315
jwe: /^(RSA|ECDH)/,
1416
jws: /^(?:(?:P|E|R)S(?:256|384|512)|Ed(?:DSA|25519)|ML-DSA-(?:44|65|87))$/,
@@ -636,9 +638,12 @@ export default function getSchema(provider) {
636638
}
637639
break;
638640
default: // Private-use URI Scheme Redirection
639-
if (!protocol.includes('.')) {
640-
this.invalidate(`${label} for native clients using Custom URI scheme should use reverse domain name based scheme`);
641+
if (FORBIDDEN_SCHEMES.has(protocol)) {
642+
this.invalidate(`${label} must not use the ${protocol.slice(0, -1)} URI scheme`);
641643
}
644+
// no further validation - RFC 8252 Section 7.1 recommends reverse domain
645+
// name based schemes but in practice app vendors don't follow this
646+
break;
642647
}
643648
break;
644649
}

test/configuration/client_metadata.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,11 +506,28 @@ describe('Client metadata validation', () => {
506506
rejects(this.title, ['not-a-uri'], undefined, {
507507
application_type: 'native',
508508
});
509+
rejects(this.title, ['https://localhost/op/callback'], /for native clients using claimed HTTPS URIs must not be using localhost as hostname/, {
510+
application_type: 'native',
511+
});
512+
allows(this.title, ['com.example.app://localhost/op/callback', 'com.example.app:/op/callback'], {
513+
application_type: 'native',
514+
});
515+
allows(this.title, ['myapp:/op/callback'], {
516+
application_type: 'native',
517+
});
518+
allows(this.title, ['cursor://anysphere.cursor-mcp/oauth/callback'], {
519+
application_type: 'native',
520+
});
509521
rejects(this.title, ['http://foo/bar'], undefined, {
510522
application_type: 'web',
511523
grant_types: ['implicit'],
512524
response_types: ['id_token'],
513525
});
526+
for (const scheme of ['javascript', 'vbscript', 'data', 'blob', 'file', 'about']) {
527+
rejects(this.title, [`${scheme}:foo`], `redirect_uris must not use the ${scheme} URI scheme`, {
528+
application_type: 'native',
529+
});
530+
}
514531
it('has an schema invalidation hook for forcing https on implicit', async () => {
515532
const sandbox = sinon.createSandbox();
516533
sandbox.spy(DefaultProvider.Client.Schema.prototype, 'invalidate');
@@ -601,6 +618,11 @@ describe('Client metadata validation', () => {
601618
grant_types: ['implicit'],
602619
response_types: ['id_token'],
603620
});
621+
for (const scheme of ['javascript', 'vbscript', 'data', 'blob', 'file', 'about']) {
622+
rejects(this.title, [`${scheme}:foo`], `post_logout_redirect_uris must not use the ${scheme} URI scheme`, {
623+
application_type: 'native',
624+
});
625+
}
604626
});
605627

606628
context('request_object_signing_alg', function () {

test/oauth_native_apps/oauth_native_apps.test.js

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,62 +9,6 @@ describe('OAuth 2.0 for Native Apps Best Current Practice features', () => {
99
before(bootstrap(import.meta.url));
1010

1111
describe('changed native client validations', () => {
12-
describe('Private-use URI Scheme Redirection', () => {
13-
it('allows custom uri scheme uris with localhost', function () {
14-
return addClient(this.provider, {
15-
application_type: 'native',
16-
client_id: 'native-custom',
17-
grant_types: ['implicit'],
18-
response_types: ['id_token'],
19-
token_endpoint_auth_method: 'none',
20-
redirect_uris: ['com.example.app://localhost/op/callback', 'com.example.app:/op/callback'],
21-
});
22-
});
23-
24-
it('rejects custom schemes without dots with reverse domain name scheme recommendation', function () {
25-
return assert.rejects(addClient(this.provider, {
26-
application_type: 'native',
27-
client_id: 'native-custom',
28-
grant_types: ['implicit'],
29-
response_types: ['id_token'],
30-
token_endpoint_auth_method: 'none',
31-
redirect_uris: ['myapp:/op/callback'],
32-
}), (err) => {
33-
expect(err).to.have.property('message', 'invalid_redirect_uri');
34-
expect(err).to.have.property('error_description', 'redirect_uris for native clients using Custom URI scheme should use reverse domain name based scheme');
35-
return true;
36-
});
37-
});
38-
});
39-
40-
describe('Claimed HTTPS URI Redirection', () => {
41-
it('allows claimed https uris', function () {
42-
return addClient(this.provider, {
43-
application_type: 'native',
44-
client_id: 'native-custom',
45-
grant_types: ['implicit'],
46-
response_types: ['id_token'],
47-
token_endpoint_auth_method: 'none',
48-
redirect_uris: ['https://claimed.example.com/op/callback'],
49-
});
50-
});
51-
52-
it('rejects https if using loopback uris', function () {
53-
return assert.rejects(addClient(this.provider, {
54-
application_type: 'native',
55-
client_id: 'native-custom',
56-
grant_types: ['implicit'],
57-
response_types: ['id_token'],
58-
token_endpoint_auth_method: 'none',
59-
redirect_uris: ['https://localhost/op/callback'],
60-
}), (err) => {
61-
expect(err).to.have.property('message', 'invalid_redirect_uri');
62-
expect(err).to.have.property('error_description', 'redirect_uris for native clients using claimed HTTPS URIs must not be using localhost as hostname');
63-
return true;
64-
});
65-
});
66-
});
67-
6812
describe('Loopback Interface Redirection', () => {
6913
it('catches invalid urls being passed in', function () {
7014
return addClient(this.provider, {

0 commit comments

Comments
 (0)