Skip to content

[Feature] Support OAuth2 authorization flow when localhost redirect URIs are not allowed #748

@elebeaup

Description

@elebeaup

Is your feature request related to a problem? Please describe.
The OAuth2 authorization process typically relies on a redirect_uri pointing to a localhost address (e.g., http://localhost:9000) so that the application can capture the authorization_code.
However, some OAuth providers do not allow localhost redirect URIs.
In this case, it becomes impossible to retrieve the authorization_code and complete the token exchange flow.

Describe the solution you'd like
Add an option allowing the user to choose how the authorization URL is launched:

  • either with the system browser (current behavior),
  • or by executing an external program, such as a small Node.js script that opens a WebView (e.g., Electron).

An example of an external program that :

  • open a webview
  • load the authorization URL in a parameter
  • intercept the redirect internally, and then performs a 302 redirect to the local server
#!/usr/bin/env electron

import { app, BrowserWindow } from "electron";

const argv = process.argv.slice(2).reduce((acc, arg) => {
  const i = arg.indexOf("=");
  const key = i == -1 ? arg : arg.slice(0, i);
  const value = i == -1 ? true : arg.slice(i + 1);
  const cleanKey = key.startsWith("--") ? key.slice(2) : key;
  acc[cleanKey] = value;
  return acc;
}, {});

async function createWindow() {
  const authorizeUrl = argv.authorizeUrl;
  const callbackUrl = argv.callbackUrl;
  const resetCookies = argv.resetCookies;
  const url = new URL(authorizeUrl);
  const redirectUrl = url.searchParams.get("redirect_uri");

  const mainWindow = new BrowserWindow({
    width: 900,
    height: 720,
  });

  if (resetCookies) {
    await mainWindow.webContents.session.clearStorageData({
      storages: ["cookies"],
    });
  }

  mainWindow.webContents.on("will-redirect", (event, url) => {
    if (url.startsWith(redirectUrl)) {
      event.preventDefault();
      const interceptedUrl = new URL(url);
      const newCallbackUrl = new URL(callbackUrl);
      interceptedUrl.searchParams.forEach((value, key) => {
        newCallbackUrl.searchParams.append(key, value);
      });
      mainWindow.loadURL(newCallbackUrl.toString());
    }
  });

  mainWindow.loadURL(authorizeUrl);
}

app.whenReady().then(createWindow);

app.on("window-all-closed", () => {
  app.quit();
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions