Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

OIDC: refresh tokens #11699

Merged
merged 64 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
2607997
test persistCredentials without a pickle key
Jul 11, 2023
3506c06
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 12, 2023
609f790
test setLoggedIn with pickle key
Jul 12, 2023
f3092c7
lint
Jul 12, 2023
fad7f33
type error
Jul 12, 2023
32d5fb0
extract token persisting code into function, persist refresh token
Jul 12, 2023
e6529f1
store has_refresh_token too
Jul 12, 2023
66d57e5
pass refreshToken from oidcAuthGrant into credentials
Jul 12, 2023
b33e347
rest restore session with pickle key
Jul 13, 2023
823ba2e
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 13, 2023
e91bbf4
retreive stored refresh token and add to credentials
Jul 13, 2023
b7e0603
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 13, 2023
b8b0c86
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 13, 2023
3ed9cc1
Merge branch 'develop' into kerry/25708/restore-refresh-token
Jul 13, 2023
221d306
extract token decryption into function
Jul 13, 2023
64dbc94
remove TODO
Jul 13, 2023
f059642
Merge branch 'develop' into kerry/25708/test-persist-credentials
Jul 16, 2023
9272110
Merge branch 'kerry/25708/test-persist-credentials' into kerry/25708/…
Jul 17, 2023
0f5fc31
Merge branch 'develop' into kerry/25708/restore-refresh-token
Jul 17, 2023
1708bef
very messy poc
Jul 17, 2023
1b76c18
Merge branch 'develop' into kerry/token-refresh-poc
Jul 18, 2023
d24fbd0
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 19, 2023
880c258
Merge branch 'kerry/25708/save-refresh-token' of https://github.com/m…
Jul 19, 2023
65c0734
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 19, 2023
70ddb4a
comments
Jul 20, 2023
22329b9
Merge branch 'kerry/25708/save-refresh-token' into kerry/25708/restor…
Jul 20, 2023
56441dc
Merge branch 'develop' into kerry/25708/save-refresh-token
Jul 20, 2023
af481b2
prettier
Jul 20, 2023
976ec8c
Merge branch 'kerry/25708/save-refresh-token' into kerry/25708/restor…
Jul 20, 2023
ae80087
Merge branch 'kerry/25708/restore-refresh-token' into kerry/token-ref…
Jul 21, 2023
ddd8ed7
Merge branch 'develop' into kerry/25708/restore-refresh-token
Sep 25, 2023
8cd5823
comment pedantry
Sep 25, 2023
ca24f0a
Merge branch 'kerry/25708/restore-refresh-token' into kerry/token-ref…
Sep 25, 2023
1fa7809
Merge branch 'develop' into kerry/token-refresh-poc
Oct 1, 2023
97fad4d
working refresh without persistence
Oct 1, 2023
e3673ee
extract token persistence functions to utils
Oct 2, 2023
41a4eb3
Merge branch 'kerry/25392/extract-token-functions' into kerry/token-r…
Oct 2, 2023
1c8e8cb
add sugar
Oct 2, 2023
0d558d7
Merge branch 'kerry/25392/extract-token-functions' into kerry/token-r…
Oct 2, 2023
5986b6c
implement TokenRefresher class with persistence
Oct 2, 2023
7db7291
tidying
Oct 2, 2023
dcd3026
persist idTokenClaims
Oct 2, 2023
4921e78
persist idTokenClaims
Oct 2, 2023
6ba08a2
tests
Oct 2, 2023
c962ca1
remove unused cde
Oct 2, 2023
0b4c4d8
Merge branch 'develop' into kerry/25392/persist-oidc-token-claims
Oct 2, 2023
3590f9c
Merge branch 'develop' into kerry/25392/persist-oidc-token-claims
Oct 2, 2023
87eb820
Merge branch 'kerry/25392/persist-oidc-token-claims' into kerry/token…
Oct 2, 2023
153ec78
create token refresher during doSetLoggedIn
Oct 2, 2023
ebdf0d5
tidying
Oct 2, 2023
2b1e73c
also tidying
Oct 2, 2023
7e081d4
update Lifecycle test replaceUsingCreds calls
Oct 4, 2023
d302374
Merge branch 'develop' into kerry/token-refresh-poc
Oct 10, 2023
df70f53
tidy
Oct 10, 2023
0b0bb61
test tokenrefresher creation in login flow
Oct 10, 2023
8a47e6e
test token refresher
Oct 10, 2023
a32ca16
Update src/utils/oidc/TokenRefresher.ts
Oct 11, 2023
5370474
use literal value for m.authentication
Oct 11, 2023
7f40f86
improve comments
Oct 11, 2023
ead0aae
Merge branch 'kerry/token-refresh-poc' of https://github.com/matrix-o…
Oct 11, 2023
9c0fdf9
Merge branch 'develop' into kerry/token-refresh-poc
Oct 11, 2023
6953b45
Merge branch 'develop' into kerry/token-refresh-poc
Oct 11, 2023
541a6e6
Merge branch 'develop' into kerry/token-refresh-poc
Oct 11, 2023
c333331
Merge branch 'develop' into kerry/token-refresh-poc
Oct 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions src/Lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ limitations under the License.
*/

import { ReactNode } from "react";
import { createClient, MatrixClient, SSOAction } from "matrix-js-sdk/src/matrix";
import { createClient, MatrixClient, SSOAction, OidcTokenRefresher } from "matrix-js-sdk/src/matrix";
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
import { QueryDict } from "matrix-js-sdk/src/utils";
Expand Down Expand Up @@ -65,7 +65,12 @@ import { OverwriteLoginPayload } from "./dispatcher/payloads/OverwriteLoginPaylo
import { SdkContextClass } from "./contexts/SDKContext";
import { messageForLoginError } from "./utils/ErrorUtils";
import { completeOidcLogin } from "./utils/oidc/authorize";
import { persistOidcAuthenticatedSettings } from "./utils/oidc/persistOidcSettings";
import {
getStoredOidcClientId,
getStoredOidcIdTokenClaims,
getStoredOidcTokenIssuer,
persistOidcAuthenticatedSettings,
} from "./utils/oidc/persistOidcSettings";
import GenericToast from "./components/views/toasts/GenericToast";
import {
ACCESS_TOKEN_IV,
Expand All @@ -78,6 +83,7 @@ import {
REFRESH_TOKEN_STORAGE_KEY,
tryDecryptToken,
} from "./utils/tokens/tokens";
import { TokenRefresher } from "./utils/oidc/TokenRefresher";

const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
Expand Down Expand Up @@ -746,6 +752,45 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise<M
return doSetLoggedIn(credentials, overwrite);
}

/**
* When we have a authenticated via OIDC-native flow and have a refresh token
* try to create a token refresher.
* @param credentials from current session
* @returns Promise that resolves to a TokenRefresher, or undefined
*/
async function createOidcTokenRefresher(credentials: IMatrixClientCreds): Promise<OidcTokenRefresher | undefined> {
if (!credentials.refreshToken) {
return;
}
// stored token issuer indicates we authenticated via OIDC-native flow
const tokenIssuer = getStoredOidcTokenIssuer();
if (!tokenIssuer) {
return;
}
try {
const clientId = getStoredOidcClientId();
const idTokenClaims = getStoredOidcIdTokenClaims();
const redirectUri = window.location.origin;
const deviceId = credentials.deviceId;
if (!deviceId) {
throw new Error("Expected deviceId in user credentials.");
}
const tokenRefresher = new TokenRefresher(
{ issuer: tokenIssuer },
clientId,
redirectUri,
deviceId,
idTokenClaims!,
credentials.userId,
);
// wait for the OIDC client to initialise
await tokenRefresher.oidcClientReady;
return tokenRefresher;
} catch (error) {
logger.error("Failed to initialise OIDC token refresher", error);
}
}

/**
* optionally clears localstorage, persists new credentials
* to localstorage, starts the new client.
Expand Down Expand Up @@ -787,9 +832,11 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable
await abortLogin();
}

const tokenRefresher = await createOidcTokenRefresher(credentials);

// check the session lock just before creating the new client
checkSessionLock();
MatrixClientPeg.replaceUsingCreds(credentials);
MatrixClientPeg.replaceUsingCreds(credentials, tokenRefresher?.doRefreshAccessToken.bind(tokenRefresher));
const client = MatrixClientPeg.safeGet();

setSentryUser(credentials.userId);
Expand Down
13 changes: 9 additions & 4 deletions src/MatrixClientPeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
IStartClientOpts,
MatrixClient,
MemoryStore,
TokenRefreshFunction,
} from "matrix-js-sdk/src/matrix";
import * as utils from "matrix-js-sdk/src/utils";
import { verificationMethods } from "matrix-js-sdk/src/crypto";
Expand Down Expand Up @@ -122,8 +123,10 @@ export interface IMatrixClientPeg {
* homeserver / identity server URLs and active credentials
*
* @param {IMatrixClientCreds} creds The new credentials to use.
* @param {TokenRefreshFunction} tokenRefreshFunction OPTIONAL function used by MatrixClient to attempt token refresh
* see {@link ICreateClientOpts.tokenRefreshFunction}
*/
replaceUsingCreds(creds: IMatrixClientCreds): void;
replaceUsingCreds(creds: IMatrixClientCreds, tokenRefreshFunction?: TokenRefreshFunction): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document the new param please

}

/**
Expand Down Expand Up @@ -196,8 +199,8 @@ class MatrixClientPegClass implements IMatrixClientPeg {
}
}

public replaceUsingCreds(creds: IMatrixClientCreds): void {
this.createClient(creds);
public replaceUsingCreds(creds: IMatrixClientCreds, tokenRefreshFunction?: TokenRefreshFunction): void {
this.createClient(creds, tokenRefreshFunction);
}

private onUnexpectedStoreClose = async (): Promise<void> => {
Expand Down Expand Up @@ -378,11 +381,13 @@ class MatrixClientPegClass implements IMatrixClientPeg {
});
}

private createClient(creds: IMatrixClientCreds): void {
private createClient(creds: IMatrixClientCreds, tokenRefreshFunction?: TokenRefreshFunction): void {
const opts: ICreateClientOpts = {
baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl,
accessToken: creds.accessToken,
refreshToken: creds.refreshToken,
tokenRefreshFunction,
userId: creds.userId,
deviceId: creds.deviceId,
pickleKey: creds.pickleKey,
Expand Down
47 changes: 47 additions & 0 deletions src/utils/oidc/TokenRefresher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { IDelegatedAuthConfig, OidcTokenRefresher, AccessTokens } from "matrix-js-sdk/src/matrix";
import { IdTokenClaims } from "oidc-client-ts";

import PlatformPeg from "../../PlatformPeg";
import { persistAccessTokenInStorage, persistRefreshTokenInStorage } from "../tokens/tokens";

/**
* OidcTokenRefresher that implements token persistence.
* Stores tokens in the same way as login flow in Lifecycle.
*/
export class TokenRefresher extends OidcTokenRefresher {
private readonly deviceId!: string;

public constructor(
authConfig: IDelegatedAuthConfig,
clientId: string,
redirectUri: string,
deviceId: string,
idTokenClaims: IdTokenClaims,
private readonly userId: string,
) {
super(authConfig, clientId, deviceId, redirectUri, idTokenClaims);
this.deviceId = deviceId;
}

public async persistTokens({ accessToken, refreshToken }: AccessTokens): Promise<void> {
const pickleKey = (await PlatformPeg.get()?.getPickleKey(this.userId, this.deviceId)) ?? undefined;
await persistAccessTokenInStorage(accessToken, pickleKey);
await persistRefreshTokenInStorage(refreshToken, pickleKey);
}
}
12 changes: 12 additions & 0 deletions src/utils/oidc/persistOidcSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,15 @@ export const getStoredOidcClientId = (): string => {
}
return clientId;
};

/**
* Retrieve stored id token claims from session storage
* @returns idtokenclaims or undefined
*/
export const getStoredOidcIdTokenClaims = (): IdTokenClaims | undefined => {
const idTokenClaims = sessionStorage.getItem(idTokenClaimsStorageKey);
if (!idTokenClaims) {
return;
}
return JSON.parse(idTokenClaims) as IdTokenClaims;
};
Loading
Loading