Skip to content

Commit 0fcebfc

Browse files
derrabaukeGaurav0
authored andcommitted
feat: autodiscover provider configuration
1 parent 47614b5 commit 0fcebfc

2 files changed

Lines changed: 86 additions & 22 deletions

File tree

addon/authenticators/oidc.js

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { later } from "@ember/runloop";
22
import { inject as service } from "@ember/service";
3+
import { camelize } from "@ember/string";
4+
import { lastValue, task } from "ember-concurrency";
35
import BaseAuthenticator from "ember-simple-auth/authenticators/base";
46
import { resolve } from "rsvp";
57
import { TrackedObject } from "tracked-built-ins";
@@ -12,12 +14,47 @@ import {
1214
isBadRequestResponse,
1315
} from "ember-simple-auth-oidc/utils/errors";
1416

17+
const camelizeObjectKeys = (obj) => {
18+
Object.keys(obj).forEach((key) => {
19+
obj[camelize(key)] = obj[key];
20+
delete obj[key];
21+
});
22+
return obj;
23+
};
24+
1525
export default class OidcAuthenticator extends BaseAuthenticator {
1626
@service router;
1727
@service session;
1828

1929
@config config;
2030

31+
get configuration() {
32+
return { ...this.config, ...this.fetchedConfig };
33+
}
34+
35+
get hasEndpointsConfigured() {
36+
return (
37+
this.configuration.tokenEndpoint && this.configuration.userinfoEndpoint
38+
);
39+
}
40+
41+
/**
42+
* Tries to fetch the OIDC provider configuration from the specified host/realm.
43+
* SPEC: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
44+
*/
45+
@lastValue("_fetchAuthConfiguration") fetchedConfig;
46+
_fetchAuthConfiguration = task(async () => {
47+
if (!this.config.host) {
48+
throw new Error("Please define a OIDC host.");
49+
}
50+
const response = await fetch(
51+
`${this.config.host}/.well-known/openid-configuration`,
52+
);
53+
const json = await response.json();
54+
55+
return camelizeObjectKeys(json.data);
56+
});
57+
2158
/**
2259
* Authenticate the client with the given authentication code. The
2360
* authentication call will return an access and refresh token which will
@@ -28,10 +65,14 @@ export default class OidcAuthenticator extends BaseAuthenticator {
2865
* @returns {Object} The parsed response data
2966
*/
3067
async authenticate({ code, redirectUri, codeVerifier, isRefresh }) {
31-
if (!this.config.tokenEndpoint || !this.config.userinfoEndpoint) {
32-
throw new Error(
33-
"Please define all OIDC endpoints (auth, token, userinfo)",
34-
);
68+
if (!this.hasEndpointsConfigured) {
69+
await this._fetchAuthConfiguration.perform();
70+
71+
if (!this.hasEndpointsConfigured) {
72+
throw new Error(
73+
"Please define all OIDC endpoints (auth, token, userinfo)",
74+
);
75+
}
3576
}
3677

3778
if (isRefresh) {
@@ -43,12 +84,12 @@ export default class OidcAuthenticator extends BaseAuthenticator {
4384

4485
const bodyObject = {
4586
code,
46-
client_id: this.config.clientId,
87+
client_id: this.configuration.clientId,
4788
grant_type: "authorization_code",
4889
redirect_uri: redirectUri,
4990
};
5091

51-
if (this.config.enablePkce) {
92+
if (this.configuration.enablePkce) {
5293
bodyObject.code_verifier = codeVerifier;
5394
}
5495

@@ -57,7 +98,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
5798
.join("&");
5899

59100
const response = await fetch(
60-
getAbsoluteUrl(this.config.tokenEndpoint, this.config.host),
101+
getAbsoluteUrl(this.configuration.tokenEndpoint, this.config.host),
61102
{
62103
method: "POST",
63104
headers: {
@@ -101,16 +142,16 @@ export default class OidcAuthenticator extends BaseAuthenticator {
101142
* @param {String} idToken The id_token of the session to invalidate
102143
*/
103144
singleLogout(idToken) {
104-
if (!this.config.endSessionEndpoint) {
145+
if (!this.configuration.endSessionEndpoint) {
105146
return;
106147
}
107148

108149
const params = [];
109150

110-
if (this.config.afterLogoutUri) {
151+
if (this.configuration.afterLogoutUri) {
111152
params.push(
112153
`post_logout_redirect_uri=${getAbsoluteUrl(
113-
this.config.afterLogoutUri,
154+
this.configuration.afterLogoutUri,
114155
)}`,
115156
);
116157
}
@@ -121,8 +162,8 @@ export default class OidcAuthenticator extends BaseAuthenticator {
121162

122163
this._redirectToUrl(
123164
`${getAbsoluteUrl(
124-
this.config.endSessionEndpoint,
125-
this.config.host,
165+
this.configuration.endSessionEndpoint,
166+
this.configuration.host,
126167
)}?${params.join("&")}`,
127168
);
128169
}
@@ -167,7 +208,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
167208
try {
168209
const bodyObject = {
169210
refresh_token,
170-
client_id: this.config.clientId,
211+
client_id: this.configuration.clientId,
171212
grant_type: "refresh_token",
172213
redirect_uri: redirectUri,
173214
};
@@ -176,7 +217,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
176217
.join("&");
177218

178219
const response = await fetch(
179-
getAbsoluteUrl(this.config.tokenEndpoint, this.config.host),
220+
getAbsoluteUrl(this.configuration.tokenEndpoint, this.config.host),
180221
{
181222
method: "POST",
182223
headers: {
@@ -202,7 +243,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
202243
} catch (e) {
203244
if (
204245
(isServerError || isAbortError(e)) &&
205-
retryCount < this.config.amountOfRetries - 1
246+
retryCount < this.configuration.amountOfRetries - 1
206247
) {
207248
return new Promise((resolve) => {
208249
later(
@@ -211,7 +252,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
211252
resolve(
212253
this._refresh(refresh_token, redirectUri, retryCount + 1),
213254
),
214-
this.config.retryTimeout,
255+
this.configuration.retryTimeout,
215256
);
216257
});
217258
}
@@ -227,10 +268,10 @@ export default class OidcAuthenticator extends BaseAuthenticator {
227268
*/
228269
async _getUserinfo(accessToken) {
229270
const response = await fetch(
230-
getAbsoluteUrl(this.config.userinfoEndpoint, this.config.host),
271+
getAbsoluteUrl(this.configuration.userinfoEndpoint, this.config.host),
231272
{
232273
headers: {
233-
Authorization: `${this.config.authPrefix} ${accessToken}`,
274+
Authorization: `${this.configuration.authPrefix} ${accessToken}`,
234275
Accept: "application/json",
235276
},
236277
},
@@ -262,9 +303,11 @@ export default class OidcAuthenticator extends BaseAuthenticator {
262303

263304
const expireInMilliseconds = expires_in
264305
? expires_in * 1000
265-
: this.config.expiresIn;
306+
: this.configuration.expiresIn;
266307
const expireTime =
267-
new Date().getTime() + expireInMilliseconds - this.config.refreshLeeway;
308+
new Date().getTime() +
309+
expireInMilliseconds -
310+
this.configuration.refreshLeeway;
268311

269312
return new TrackedObject({
270313
access_token,

tests/dummy/mirage/config.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { discoverEmberDataModels } from "ember-cli-mirage";
22
import { createServer, Response } from "miragejs";
33

4+
const REALM = "test-realm";
5+
const REALM_PATH = `/realms/${REALM}`;
6+
47
export default function makeServer(config) {
58
return createServer({
69
...config,
@@ -10,12 +13,30 @@ export default function makeServer(config) {
1013
this.namespace = "";
1114
this.timing = 0;
1215

13-
this.post("/realms/test-realm/protocol/openid-connect/token", {
16+
this.get(`${REALM_PATH}/.well-known/openid-configuration`, () => {
17+
const config = {
18+
issuer: this.urlPrefix,
19+
authorization_endpoint: `${this.urlPrefix}/connect/authorize`,
20+
token_endpoint: `${this.urlPrefix}${REALM_PATH}/protocol/openid-connect/token`,
21+
userinfo_endpoint: `${this.urlPrefix}${REALM_PATH}/protocol/openid-connect/userinfo`,
22+
end_session_endpoint: `${this.urlPrefix}/connect/end_session`,
23+
jwks_uri: `${this.urlPrefix}/jwks.json`,
24+
registration_endpoint: `${this.urlPrefix}/connect/register`,
25+
};
26+
return new Response(
27+
200,
28+
{},
29+
{
30+
data: config,
31+
},
32+
);
33+
});
34+
this.post(`${REALM_PATH}/protocol/openid-connect/token`, {
1435
access_token: "access.token",
1536
refresh_token: "refresh.token",
1637
id_token: "id.token",
1738
});
18-
this.get("/realms/test-realm/protocol/openid-connect/userinfo", {
39+
this.get(`${REALM_PATH}/protocol/openid-connect/userinfo`, {
1940
sub: 1,
2041
});
2142
this.get("/users");

0 commit comments

Comments
 (0)