OIDC: persist id token claims (#11691)
* persist idTokenClaims * tests * remove unused cde
This commit is contained in:
parent
1c553eae4e
commit
feb7e9899b
6 changed files with 70 additions and 24 deletions
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue