From c56add74c6f2d06d8b5c02e2e24d3cdbe473ce68 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 11 Jul 2023 18:59:28 -0400 Subject: [PATCH] fix: Azure single-tenant SSO tokens are unable to refresh (#5551) --- server/models/UserAuthentication.ts | 6 +++++- server/utils/azure.ts | 27 +++++++++++++++++++++++++++ server/utils/oauth.ts | 15 ++++++++++----- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/server/models/UserAuthentication.ts b/server/models/UserAuthentication.ts index efab307d1..5377f38b5 100644 --- a/server/models/UserAuthentication.ts +++ b/server/models/UserAuthentication.ts @@ -93,6 +93,7 @@ class UserAuthentication extends IdModel { ): Promise { // Check a maximum of once every 5 minutes if (this.lastValidatedAt > subMinutes(Date.now(), 5) && !force) { + Logger.debug("utils", "Skipping access token validation"); return true; } @@ -158,7 +159,10 @@ class UserAuthentication extends IdModel { const client = authenticationProvider.oauthClient; if (client) { - const response = await client.rotateToken(this.refreshToken); + const response = await client.rotateToken( + this.accessToken, + this.refreshToken + ); // Not all OAuth providers return a new refreshToken so we need to guard // against setting to an empty value. diff --git a/server/utils/azure.ts b/server/utils/azure.ts index 7c1339873..da954d619 100644 --- a/server/utils/azure.ts +++ b/server/utils/azure.ts @@ -1,9 +1,36 @@ +import JWT from "jsonwebtoken"; +import env from "@server/env"; import OAuthClient from "./oauth"; +type AzurePayload = { + /* A GUID that represents the Azure AD tenant that the user is from */ + tid: string; +}; + export default class AzureClient extends OAuthClient { endpoints = { authorize: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", token: "https://login.microsoftonline.com/common/oauth2/v2.0/token", userinfo: "https://graph.microsoft.com/v1.0/me", }; + + async rotateToken( + accessToken: string, + refreshToken: string + ): Promise<{ + accessToken: string; + refreshToken?: string; + expiresAt: Date; + }> { + if (env.isCloudHosted()) { + return super.rotateToken(accessToken, refreshToken); + } + + const payload = JWT.decode(accessToken) as AzurePayload; + return super.rotateToken( + accessToken, + refreshToken, + `https://login.microsoftonline.com/${payload.tid}/oauth2/v2.0/token` + ); + } } diff --git a/server/utils/oauth.ts b/server/utils/oauth.ts index a9426e4a8..fee7312ca 100644 --- a/server/utils/oauth.ts +++ b/server/utils/oauth.ts @@ -1,4 +1,5 @@ import fetch from "fetch-with-proxy"; +import Logger from "@server/logging/Logger"; import { AuthenticationError, InvalidRequestError } from "../errors"; export default abstract class OAuthClient { @@ -41,18 +42,22 @@ export default abstract class OAuthClient { return data; }; - rotateToken = async ( - refreshToken: string + async rotateToken( + _accessToken: string, + refreshToken: string, + endpoint = this.endpoints.token ): Promise<{ accessToken: string; refreshToken?: string; expiresAt: Date; - }> => { + }> { let data; let response; try { - response = await fetch(this.endpoints.token, { + Logger.debug("utils", "Rotating token", { endpoint }); + + response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", @@ -79,5 +84,5 @@ export default abstract class OAuthClient { accessToken: data.access_token, expiresAt: new Date(Date.now() + data.expires_in * 1000), }; - }; + } }