OIDC: persist id token claims (#11691)

* persist idTokenClaims

* tests

* remove unused cde
This commit is contained in:
Kerry 2023-10-04 17:06:04 +13:00 committed by GitHub
parent 1c553eae4e
commit feb7e9899b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 24 deletions

View file

@ -272,7 +272,7 @@ export async function attemptDelegatedAuthLogin(
*/ */
async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean> { async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean> {
try { try {
const { accessToken, refreshToken, homeserverUrl, identityServerUrl, clientId, issuer } = const { accessToken, refreshToken, homeserverUrl, identityServerUrl, idTokenClaims, clientId, issuer } =
await completeOidcLogin(queryParams); await completeOidcLogin(queryParams);
const { const {
@ -294,7 +294,7 @@ async function attemptOidcNativeLogin(queryParams: QueryDict): Promise<boolean>
logger.debug("Logged in via OIDC native flow"); logger.debug("Logged in via OIDC native flow");
await onSuccessfulDelegatedAuthLogin(credentials); await onSuccessfulDelegatedAuthLogin(credentials);
// this needs to happen after success handler which clears storages // this needs to happen after success handler which clears storages
persistOidcAuthenticatedSettings(clientId, issuer); persistOidcAuthenticatedSettings(clientId, issuer, idTokenClaims);
return true; return true;
} catch (error) { } catch (error) {
logger.error("Failed to login via OIDC", error); logger.error("Failed to login via OIDC", error);

View file

@ -18,6 +18,7 @@ import { completeAuthorizationCodeGrant, generateOidcAuthorizationUrl } from "ma
import { QueryDict } from "matrix-js-sdk/src/utils"; import { QueryDict } from "matrix-js-sdk/src/utils";
import { OidcClientConfig } from "matrix-js-sdk/src/matrix"; import { OidcClientConfig } from "matrix-js-sdk/src/matrix";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
import { IdTokenClaims } from "oidc-client-ts";
/** /**
* Start OIDC authorization code flow * Start OIDC authorization code flow
@ -81,6 +82,8 @@ type CompleteOidcLoginResponse = {
clientId: string; clientId: string;
// issuer used during authentication // issuer used during authentication
issuer: string; issuer: string;
// claims of the given access token; used during token refresh to validate new tokens
idTokenClaims: IdTokenClaims;
}; };
/** /**
* Attempt to complete authorization code flow to get an access token * Attempt to complete authorization code flow to get an access token
@ -90,7 +93,7 @@ type CompleteOidcLoginResponse = {
*/ */
export const completeOidcLogin = async (queryParams: QueryDict): Promise<CompleteOidcLoginResponse> => { export const completeOidcLogin = async (queryParams: QueryDict): Promise<CompleteOidcLoginResponse> => {
const { code, state } = getCodeAndStateFromQueryParams(queryParams); const { code, state } = getCodeAndStateFromQueryParams(queryParams);
const { homeserverUrl, tokenResponse, identityServerUrl, oidcClientSettings } = const { homeserverUrl, tokenResponse, idTokenClaims, identityServerUrl, oidcClientSettings } =
await completeAuthorizationCodeGrant(code, state); await completeAuthorizationCodeGrant(code, state);
return { return {
@ -100,5 +103,6 @@ export const completeOidcLogin = async (queryParams: QueryDict): Promise<Complet
refreshToken: tokenResponse.refresh_token, refreshToken: tokenResponse.refresh_token,
clientId: oidcClientSettings.clientId, clientId: oidcClientSettings.clientId,
issuer: oidcClientSettings.issuer, issuer: oidcClientSettings.issuer,
idTokenClaims,
}; };
}; };

View file

@ -14,8 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IdTokenClaims } from "oidc-client-ts";
const clientIdStorageKey = "mx_oidc_client_id"; const clientIdStorageKey = "mx_oidc_client_id";
const tokenIssuerStorageKey = "mx_oidc_token_issuer"; const tokenIssuerStorageKey = "mx_oidc_token_issuer";
const idTokenClaimsStorageKey = "mx_oidc_id_token_claims";
/** /**
* Persists oidc clientId and issuer in session storage * Persists oidc clientId and issuer in session storage
@ -23,9 +26,14 @@ const tokenIssuerStorageKey = "mx_oidc_token_issuer";
* @param clientId * @param clientId
* @param issuer * @param issuer
*/ */
export const persistOidcAuthenticatedSettings = (clientId: string, issuer: string): void => { export const persistOidcAuthenticatedSettings = (
clientId: string,
issuer: string,
idTokenClaims: IdTokenClaims,
): void => {
sessionStorage.setItem(clientIdStorageKey, clientId); sessionStorage.setItem(clientIdStorageKey, clientId);
sessionStorage.setItem(tokenIssuerStorageKey, issuer); sessionStorage.setItem(tokenIssuerStorageKey, issuer);
sessionStorage.setItem(idTokenClaimsStorageKey, JSON.stringify(idTokenClaims));
}; };
/** /**

View file

@ -924,7 +924,9 @@ describe("<MatrixChat />", () => {
}; };
beforeEach(() => { beforeEach(() => {
mocked(completeAuthorizationCodeGrant).mockClear().mockResolvedValue({ mocked(completeAuthorizationCodeGrant)
.mockClear()
.mockResolvedValue({
oidcClientSettings: { oidcClientSettings: {
clientId, clientId,
issuer, issuer,
@ -932,6 +934,13 @@ describe("<MatrixChat />", () => {
tokenResponse, tokenResponse,
homeserverUrl, homeserverUrl,
identityServerUrl, identityServerUrl,
idTokenClaims: {
aud: "123",
iss: issuer,
sub: "123",
exp: 123,
iat: 456,
},
}); });
jest.spyOn(logger, "error").mockClear(); jest.spyOn(logger, "error").mockClear();

View file

@ -104,7 +104,9 @@ describe("OIDC authorization", () => {
}; };
beforeEach(() => { beforeEach(() => {
mocked(completeAuthorizationCodeGrant).mockClear().mockResolvedValue({ mocked(completeAuthorizationCodeGrant)
.mockClear()
.mockResolvedValue({
oidcClientSettings: { oidcClientSettings: {
clientId, clientId,
issuer, issuer,
@ -112,6 +114,13 @@ describe("OIDC authorization", () => {
tokenResponse, tokenResponse,
homeserverUrl, homeserverUrl,
identityServerUrl, identityServerUrl,
idTokenClaims: {
aud: "123",
iss: issuer,
sub: "123",
exp: 123,
iat: 456,
},
}); });
}); });
@ -137,6 +146,7 @@ describe("OIDC authorization", () => {
identityServerUrl, identityServerUrl,
issuer, issuer,
clientId, clientId,
idTokenClaims: result.idTokenClaims,
}); });
}); });
}); });

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IdTokenClaims } from "oidc-client-ts";
import { import {
getStoredOidcClientId, getStoredOidcClientId,
getStoredOidcTokenIssuer, getStoredOidcTokenIssuer,
@ -29,12 +31,25 @@ describe("persist OIDC settings", () => {
const clientId = "test-client-id"; const clientId = "test-client-id";
const issuer = "https://auth.org/"; const issuer = "https://auth.org/";
const idTokenClaims: IdTokenClaims = {
// audience is this client
aud: "123",
// issuer matches
iss: issuer,
sub: "123",
exp: 123,
iat: 456,
};
describe("persistOidcAuthenticatedSettings", () => { describe("persistOidcAuthenticatedSettings", () => {
it("should set clientId and issuer in session storage", () => { it("should set clientId and issuer in session storage", () => {
persistOidcAuthenticatedSettings(clientId, issuer); persistOidcAuthenticatedSettings(clientId, issuer, idTokenClaims);
expect(sessionStorage.setItem).toHaveBeenCalledWith("mx_oidc_client_id", clientId); expect(sessionStorage.setItem).toHaveBeenCalledWith("mx_oidc_client_id", clientId);
expect(sessionStorage.setItem).toHaveBeenCalledWith("mx_oidc_token_issuer", issuer); expect(sessionStorage.setItem).toHaveBeenCalledWith("mx_oidc_token_issuer", issuer);
expect(sessionStorage.setItem).toHaveBeenCalledWith(
"mx_oidc_id_token_claims",
JSON.stringify(idTokenClaims),
);
}); });
}); });
@ -50,7 +65,7 @@ describe("persist OIDC settings", () => {
}); });
}); });
describe("Name of the group", () => { describe("getStoredOidcClientId()", () => {
it("should return clientId from session storage", () => { it("should return clientId from session storage", () => {
jest.spyOn(sessionStorage.__proto__, "getItem").mockReturnValue(clientId); jest.spyOn(sessionStorage.__proto__, "getItem").mockReturnValue(clientId);
expect(getStoredOidcClientId()).toEqual(clientId); expect(getStoredOidcClientId()).toEqual(clientId);