Re: [PATCH proxmox-widget-toolkit v4 11/24] utils: Add OAuth2 flow handlers

Shannon Sterz
2026-04-23T12:24:42Z

On Tue Apr 21, 2026 at 1:59 PM CEST, Arthur Bied-Charreton wrote:
> Introduce the Proxmox.OAuth2 singleton supporting Google and Microsoft
> OAuth2. The flow is handled by opening a new window with the
> authorization URL, and expecting to receive the resulting authorization
> code from the redirect handler via a BroadcastChannel [0], which allows
> communication between any two browsing contexts.
>
> [0]
> https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
>
> Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
> ---
> src/Utils.js | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 91 insertions(+)
>
> diff --git a/src/Utils.js b/src/Utils.js
> index 5457ffa..ceed4c8 100644
> --- a/src/Utils.js
> +++ b/src/Utils.js
> @@ -1723,6 +1723,97 @@ Ext.define('Proxmox.Utils', {
> },
> });
>
> +Ext.define('Proxmox.OAuth2', {
> + singleton: true,
> +
> + handleGoogleFlow: function (clientId, clientSecret) {
> + return this._handleFlow({
> + clientId,
> + clientSecret,
> + authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
> + tokenUrl: 'https://oauth2.googleapis.com/token',
> + scope: 'https://mail.google.com',
> + extraAuthParams: {
> + access_type: 'offline',
> + prompt: 'consent',
> + },
> + });
> + },
> +
> + handleMicrosoftFlow: function (clientId, clientSecret, tenantId) {
> + return this._handleFlow({
> + clientId,
> + clientSecret,
> + authUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
> + tokenUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
> + scope: 'https://outlook.office.com/SMTP.Send offline_access',
> + extraAuthParams: {
> + prompt: 'consent',
> + },
> + });
> + },
> +
> + _handleFlow: function (config) {
> + return new Promise((resolve, reject) => {
> + let redirectUri = window.location.origin;
> + let channelName = `oauth2_${crypto.randomUUID()}`;
> + let state = encodeURIComponent(JSON.stringify({ channelName }));
> +
> + let authParams = new URLSearchParams({
> + client_id: config.clientId,
> + response_type: 'code',
> + redirect_uri: redirectUri,
> + scope: config.scope,
> + state,
> + ...config.extraAuthParams,
> + });
> +
> + let authUrl = `${config.authUrl}?${authParams}`;
> +
> + // Opens OAuth2 authorization window. The app's redirect handler must
> + // extract the authorization code from the callback URL and send it via
> + // the BroadcastChannel whose name we passed along as a state parameter.
> + let channel = new BroadcastChannel(channelName);
> + let popup = window.open(authUrl);
> + if (!popup) {
> + reject(new Error(gettext('Could not open authentication window')));
> + return;
> + }
> +
> + channel.addEventListener('message', async (event) => {
> + if (popup && !popup.closed) {
> + popup.close();
> + }
> + channel.close();
> +
> + try {
> + let response = await fetch(config.tokenUrl, {
> + method: 'POST',
> + headers: {
> + 'Content-Type': 'application/x-www-form-urlencoded',
> + },
> + body: new URLSearchParams({
> + grant_type: 'authorization_code',
> + code: event.data.code,

should we check if code is set here properly? i know the sending code
always sets this in theory, but it might be safer to check here again
before triggering a request.

also if i remember correctly there was an issue with using the async
fetch api in extjs, maybe @Dominik Csapak could take a look if this is
fine here.

> + client_id: config.clientId,
> + client_secret: config.clientSecret,
> + redirect_uri: redirectUri,
> + }),
> + });
> +
> + let tokens = await response.json();
> + if (!tokens.refresh_token) {
> + reject(tokens.error_description || gettext('Token exchange failed'));
> + }
> + resolve(tokens.refresh_token);
> + } catch (error) {
> + reject(error);
> + }
> + });
> + });
> + },
> +});
> +
> Ext.define('Proxmox.Async', {
> singleton: true,
>