Skip to content

Commit 239b81e

Browse files
authored
refactor: loose redirect uri restrictions (#6846)
* refactor: loose redirect uri restrictions * refactor: fix types and add tests * chore: add changeset
1 parent 352f4d1 commit 239b81e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+350
-157
lines changed

.changeset/nervous-apes-suffer.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@logto/integration-tests": patch
3+
"@logto/core-kit": patch
4+
"@logto/console": patch
5+
"@logto/phrases": patch
6+
"@logto/core": patch
7+
---
8+
9+
loose redirect uri restrictions
10+
11+
Logto has been following the industry best practices for OAuth2.0 and OIDC from the start. However, in the real world, there are things we cannot control, like third-party services or operation systems like Windows.
12+
13+
This update relaxes restrictions on redirect URIs to allow the following:
14+
15+
1. A mix of native and HTTP(S) redirect URIs. For example, a native app can now use a redirect URI like `https://example.com`.
16+
2. Native schemes without a period (`.`). For example, `myapp://callback` is now allowed.
17+
18+
When such URIs are configured, Logto Console will display a prominent warning. This change is backward-compatible and will not affect existing applications.
19+
20+
We hope this change will make it easier for you to integrate Logto with your applications.

packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/Settings.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import FormCard from '@/components/FormCard';
88
import MultiTextInputField from '@/components/MultiTextInputField';
99
import CodeEditor from '@/ds-components/CodeEditor';
1010
import FormField from '@/ds-components/FormField';
11+
import InlineNotification from '@/ds-components/InlineNotification';
1112
import type { MultiTextInputRule } from '@/ds-components/MultiTextInput/types';
1213
import {
1314
convertRhfErrorMessage,
@@ -19,8 +20,33 @@ import useDocumentationUrl from '@/hooks/use-documentation-url';
1920
import { isJsonObject } from '@/utils/json';
2021

2122
import ProtectedAppSettings from './ProtectedAppSettings';
23+
import styles from './index.module.scss';
2224
import { type ApplicationForm } from './utils';
2325

26+
const hasMixedUriProtocols = (applicationType: ApplicationType, uris: string[]): boolean => {
27+
switch (applicationType) {
28+
case ApplicationType.Native: {
29+
return uris.some((uri) => validateRedirectUrl(uri, 'web'));
30+
}
31+
case ApplicationType.Traditional:
32+
case ApplicationType.SPA: {
33+
return uris.some((uri) => validateRedirectUrl(uri, 'mobile'));
34+
}
35+
default: {
36+
return false;
37+
}
38+
}
39+
};
40+
41+
function MixedUriWarning() {
42+
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
43+
return (
44+
<InlineNotification severity="alert" className={styles.mixedUriWarning}>
45+
{t('application_details.mixed_redirect_uri_warning')}
46+
</InlineNotification>
47+
);
48+
}
49+
2450
type Props = {
2551
readonly data: Application;
2652
};
@@ -31,19 +57,27 @@ function Settings({ data }: Props) {
3157
const {
3258
control,
3359
register,
60+
watch,
3461
formState: { errors },
3562
} = useFormContext<ApplicationForm>();
3663

3764
const { type: applicationType } = data;
3865

39-
const isNativeApp = applicationType === ApplicationType.Native;
4066
const isProtectedApp = applicationType === ApplicationType.Protected;
4167
const uriPatternRules: MultiTextInputRule = {
4268
pattern: {
43-
verify: (value) => !value || validateRedirectUrl(value, isNativeApp ? 'mobile' : 'web'),
69+
verify: (value) =>
70+
!value || validateRedirectUrl(value, 'web') || validateRedirectUrl(value, 'mobile'),
4471
message: t('errors.invalid_uri_format'),
4572
},
4673
};
74+
const redirectUris = watch('oidcClientMetadata.redirectUris');
75+
const postLogoutRedirectUris = watch('oidcClientMetadata.postLogoutRedirectUris');
76+
const showRedirectUriMixedWarning = hasMixedUriProtocols(applicationType, redirectUris);
77+
const showPostLogoutUriMixedWarning = hasMixedUriProtocols(
78+
applicationType,
79+
postLogoutRedirectUris
80+
);
4781

4882
if (isProtectedApp) {
4983
return <ProtectedAppSettings data={data} />;
@@ -113,6 +147,7 @@ function Settings({ data }: Props) {
113147
)}
114148
/>
115149
)}
150+
{showRedirectUriMixedWarning && <MixedUriWarning />}
116151
{applicationType !== ApplicationType.MachineToMachine && (
117152
<Controller
118153
name="oidcClientMetadata.postLogoutRedirectUris"
@@ -133,6 +168,7 @@ function Settings({ data }: Props) {
133168
)}
134169
/>
135170
)}
171+
{showPostLogoutUriMixedWarning && <MixedUriWarning />}
136172
{applicationType !== ApplicationType.MachineToMachine && (
137173
<Controller
138174
name="customClientMetadata.corsAllowedOrigins"

packages/console/src/pages/ApplicationDetails/ApplicationDetailsContent/index.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@
2222
display: flex;
2323
}
2424
}
25+
26+
.mixedUriWarning {
27+
margin-block-start: _.unit(2);
28+
}

packages/core/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@
8181
"lru-cache": "^11.0.0",
8282
"nanoid": "^5.0.1",
8383
"node-forge": "^1.3.1",
84-
"oidc-provider": "^8.4.6",
84+
"oidc-provider": "github:logto-io/node-oidc-provider#de2d8fd68e91b76d71fb910d44142f9eccd844bc",
8585
"openapi-types": "^12.1.3",
8686
"otplib": "^12.0.1",
8787
"p-map": "^7.0.2",
8888
"p-retry": "^6.0.0",
8989
"pg-protocol": "^1.6.0",
9090
"pluralize": "^8.0.0",
9191
"qrcode": "^1.5.3",
92-
"raw-body": "^2.5.2",
92+
"raw-body": "^3.0.0",
9393
"redis": "^4.6.14",
9494
"roarr": "^7.11.0",
9595
"samlify": "2.8.11",
@@ -116,7 +116,7 @@
116116
"@types/koa__cors": "^5.0.0",
117117
"@types/node": "^20.9.5",
118118
"@types/node-forge": "^1.3.1",
119-
"@types/oidc-provider": "^8.4.4",
119+
"@types/oidc-provider": "^8.5.2",
120120
"@types/pluralize": "^0.0.33",
121121
"@types/qrcode": "^1.5.2",
122122
"@types/semver": "^7.3.12",

packages/core/src/event-listeners/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type Provider from 'oidc-provider';
1+
import type { Provider } from 'oidc-provider';
22

33
import type Queries from '#src/tenants/Queries.js';
44
import { getConsoleLogFromContext } from '#src/utils/console.js';

packages/core/src/include.d/oidc-provider/lib-keep/helpers/filter_claims.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// https://github.com/panva/node-oidc-provider/blob/cf2069cbb31a6a855876e95157372d25dde2511c/lib/helpers/filter_claims.js
22
declare module 'oidc-provider/lib/helpers/filter_claims.js' {
3-
import { type ClaimsParameter } from 'oidc-provider';
4-
import type Provider from 'oidc-provider';
3+
import type { ClaimsParameter, Provider } from 'oidc-provider';
54

65
export default function filterClaims(
76
source: ClaimsParameter | undefined,

packages/core/src/include.d/oidc-provider/lib-keep/helpers/weak_cache.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
declare module 'oidc-provider/lib/helpers/weak_cache.js' {
2-
import type Provider, { type Configuration } from 'oidc-provider';
2+
import type { Provider, Configuration } from 'oidc-provider';
33

44
/** Deeply make all properties of a record required. */
55
type DeepRequired<T> = T extends Record<string | number | symbol, unknown>

packages/core/src/libraries/session.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type User } from '@logto/schemas';
22
import { generateStandardId } from '@logto/shared';
3-
import type Provider from 'oidc-provider';
3+
import type { Provider } from 'oidc-provider';
44

55
import { mockUser } from '#src/__mocks__/user.js';
66
import type Queries from '#src/tenants/Queries.js';

packages/core/src/libraries/session.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { conditional } from '@silverhand/essentials';
22
import type { Context } from 'koa';
3-
import type { InteractionResults, PromptDetail } from 'oidc-provider';
4-
import type Provider from 'oidc-provider';
3+
import type { InteractionResults, PromptDetail, Provider } from 'oidc-provider';
54
import { z } from 'zod';
65

76
import type Queries from '#src/tenants/Queries.js';

packages/core/src/middleware/koa-auth/koa-oidc-auth.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { pickDefault } from '@logto/shared/esm';
22
import type { Context } from 'koa';
33
import type { IRouterParamContext } from 'koa-router';
4-
import Provider from 'oidc-provider';
4+
import { Provider } from 'oidc-provider';
55
import Sinon from 'sinon';
66

77
import RequestError from '#src/errors/RequestError/index.js';

0 commit comments

Comments
 (0)