Skip to content

Commit

Permalink
feat: autodiscover provider configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabauke committed Dec 7, 2023
1 parent 778ebb3 commit 58fd139
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 22 deletions.
83 changes: 63 additions & 20 deletions addon/authenticators/oidc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { later } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { camelize } from "@ember/string";
import { lastValue, task } from "ember-concurrency";
import {
isServerErrorResponse,
isAbortError,
Expand All @@ -13,12 +15,47 @@ import { TrackedObject } from "tracked-built-ins";
import config from "ember-simple-auth-oidc/config";
import getAbsoluteUrl from "ember-simple-auth-oidc/utils/absolute-url";

const camelizeObjectKeys = (obj) => {
Object.keys(obj).forEach((key) => {
obj[camelize(key)] = obj[key];
delete obj[key];
});
return obj;
};

export default class OidcAuthenticator extends BaseAuthenticator {
@service router;
@service session;

@config config;

get configuration() {
return { ...this.config, ...this.fetchedConfig };
}

get hasEndpointsConfigured() {
return (
this.configuration.tokenEndpoint && this.configuration.userinfoEndpoint
);
}

/**
* Tries to fetch the OIDC provider configuration from the specified host/realm.
* SPEC: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
*/
@lastValue("_fetchAuthConfiguration") fetchedConfig;
_fetchAuthConfiguration = task(async () => {
if (!this.config.host) {
throw new Error("Please define a OIDC host.");
}
const response = await fetch(
`${this.config.host}/.well-known/openid-configuration`,
);
const _json = await response.json();

return camelizeObjectKeys(_json.data);
});

/**
* Authenticate the client with the given authentication code. The
* authentication call will return an access and refresh token which will
Expand All @@ -29,10 +66,14 @@ export default class OidcAuthenticator extends BaseAuthenticator {
* @returns {Object} The parsed response data
*/
async authenticate({ code, redirectUri, codeVerifier, isRefresh }) {
if (!this.config.tokenEndpoint || !this.config.userinfoEndpoint) {
throw new Error(
"Please define all OIDC endpoints (auth, token, userinfo)",
);
if (!this.hasEndpointsConfigured) {
await this._fetchAuthConfiguration.perform();

if (!this.hasEndpointsConfigured) {
throw new Error(
"Please define all OIDC endpoints (auth, token, userinfo)",
);
}
}

if (isRefresh) {
Expand All @@ -44,12 +85,12 @@ export default class OidcAuthenticator extends BaseAuthenticator {

const bodyObject = {
code,
client_id: this.config.clientId,
client_id: this.configuration.clientId,
grant_type: "authorization_code",
redirect_uri: redirectUri,
};

if (this.config.enablePkce) {
if (this.configuration.enablePkce) {
bodyObject.code_verifier = codeVerifier;
}

Expand All @@ -58,7 +99,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
.join("&");

const response = await fetch(
getAbsoluteUrl(this.config.tokenEndpoint, this.config.host),
getAbsoluteUrl(this.configuration.tokenEndpoint, this.config.host),
{
method: "POST",
headers: {
Expand Down Expand Up @@ -102,16 +143,16 @@ export default class OidcAuthenticator extends BaseAuthenticator {
* @param {String} idToken The id_token of the session to invalidate
*/
singleLogout(idToken) {
if (!this.config.endSessionEndpoint) {
if (!this.configuration.endSessionEndpoint) {
return;
}

const params = [];

if (this.config.afterLogoutUri) {
if (this.configuration.afterLogoutUri) {
params.push(
`post_logout_redirect_uri=${getAbsoluteUrl(
this.config.afterLogoutUri,
this.configuration.afterLogoutUri,
)}`,
);
}
Expand All @@ -122,8 +163,8 @@ export default class OidcAuthenticator extends BaseAuthenticator {

this._redirectToUrl(
`${getAbsoluteUrl(
this.config.endSessionEndpoint,
this.config.host,
this.configuration.endSessionEndpoint,
this.configuration.host,
)}?${params.join("&")}`,
);
}
Expand Down Expand Up @@ -168,7 +209,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
try {
const bodyObject = {
refresh_token,
client_id: this.config.clientId,
client_id: this.configuration.clientId,
grant_type: "refresh_token",
redirect_uri: redirectUri,
};
Expand All @@ -177,7 +218,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
.join("&");

const response = await fetch(
getAbsoluteUrl(this.config.tokenEndpoint, this.config.host),
getAbsoluteUrl(this.configuration.tokenEndpoint, this.config.host),
{
method: "POST",
headers: {
Expand All @@ -203,7 +244,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
} catch (e) {
if (
(isServerError || isAbortError(e)) &&
retryCount < this.config.amountOfRetries - 1
retryCount < this.configuration.amountOfRetries - 1
) {
return new Promise((resolve) => {
later(
Expand All @@ -212,7 +253,7 @@ export default class OidcAuthenticator extends BaseAuthenticator {
resolve(
this._refresh(refresh_token, redirectUri, retryCount + 1),
),
this.config.retryTimeout,
this.configuration.retryTimeout,
);
});
}
Expand All @@ -228,10 +269,10 @@ export default class OidcAuthenticator extends BaseAuthenticator {
*/
async _getUserinfo(accessToken) {
const response = await fetch(
getAbsoluteUrl(this.config.userinfoEndpoint, this.config.host),
getAbsoluteUrl(this.configuration.userinfoEndpoint, this.config.host),
{
headers: {
Authorization: `${this.config.authPrefix} ${accessToken}`,
Authorization: `${this.configuration.authPrefix} ${accessToken}`,
Accept: "application/json",
},
},
Expand Down Expand Up @@ -263,9 +304,11 @@ export default class OidcAuthenticator extends BaseAuthenticator {

const expireInMilliseconds = expires_in
? expires_in * 1000
: this.config.expiresIn;
: this.configuration.expiresIn;
const expireTime =
new Date().getTime() + expireInMilliseconds - this.config.refreshLeeway;
new Date().getTime() +
expireInMilliseconds -
this.configuration.refreshLeeway;

return new TrackedObject({
access_token,
Expand Down
25 changes: 23 additions & 2 deletions tests/dummy/mirage/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { discoverEmberDataModels } from "ember-cli-mirage";
import { createServer, Response } from "miragejs";

const REALM = "test-realm";
const REALM_PATH = `/realms/${REALM}`;

export default function makeServer(config) {
return createServer({
...config,
Expand All @@ -10,12 +13,30 @@ export default function makeServer(config) {
this.namespace = "";
this.timing = 0;

this.post("/realms/test-realm/protocol/openid-connect/token", {
this.get(`${REALM_PATH}/.well-known/openid-configuration`, () => {
const config = {
issuer: this.urlPrefix,
authorization_endpoint: `${this.urlPrefix}/connect/authorize`,
token_endpoint: `${this.urlPrefix}${REALM_PATH}/protocol/openid-connect/token`,
userinfo_endpoint: `${this.urlPrefix}${REALM_PATH}/protocol/openid-connect/userinfo`,
end_session_endpoint: `${this.urlPrefix}/connect/end_session`,
jwks_uri: `${this.urlPrefix}/jwks.json`,
registration_endpoint: `${this.urlPrefix}/connect/register`,
};
return new Response(
200,
{},
{
data: config,
},
);
});
this.post(`${REALM_PATH}/protocol/openid-connect/token`, {
access_token: "access.token",
refresh_token: "refresh.token",
id_token: "id.token",
});
this.get("/realms/test-realm/protocol/openid-connect/userinfo", {
this.get(`${REALM_PATH}/protocol/openid-connect/userinfo`, {
sub: 1,
});
this.get("/users");
Expand Down

0 comments on commit 58fd139

Please sign in to comment.