AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

Cannot log in using a mobile and desktop applications redirect URL prefixed with `tauri://` #6241

Closed clemlesne closed 1 year ago

clemlesne commented 1 year ago

Core Library

MSAL.js (@azure/msal-browser)

Core Library Version

2.38.0

Wrapper Library

MSAL React (@azure/msal-react)

Wrapper Library Version

1.5.9

Public or Confidential Client?

Public

Description

Cannot log in using a mobile and desktop applications redirect URL prefixed with tauri://. It works with http://localhost but not from tauri://localhost.

Error Message

{
    "error": "invalid_request",
    "error_description": "AADSTS90023: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type or 'Native' client-type with origin registered in AllowedOriginForNativeAppCorsRequestInOAuthToken allow list.\r\nTrace ID: xxx\r\nCorrelation ID: xxx\r\nTimestamp: 2023-07-17 16:28:13Z",
    "error_codes": [
        90023
    ],
    "timestamp": "2023-07-17 16:28:13Z",
    "trace_id": "xxx",
    "correlation_id": "xxx"
}

MSAL Logs

[Debug] Permission granted (index-95a4e7e3.js, line 107)
[Error] Unhandled Promise Rejection: Scope not defined for window `main` and URL `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=xxx&scope=openid%20profile%20offline_access&redir...
[Info] Successfully preconnected to https://aadcdn.msauth.net/ (x2)
[Warning] [TAURI] Couldn't find callback id 1866876788 in window. This happens when the app is reloaded while Rust is running an asynchronous operation. (authorize, line 5)
[Error] Unhandled Promise Rejection: Scope not defined for window `main` and URL `https://login.live.com/oauth20_authorize.srf?client_id=xxx&scope=openid+profile+offline_access&redirect_uri=tauri%3a%2f%2f...
[Error] Unhandled Promise Rejection: Scope not defined for window `main` and URL `https://account.live.com/App/Confirm?mkt=EN-GB&uiflavor=host&id=293577&client_id=0000000048FB303C&ru=https://login.live.com/oauth20_authorize.srf%3fuaid%3d...
[Info] Successfully preconnected to https://acctcdn.msftauth.net/ (x4)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:FlowController.showControl(appConfirm) (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:New State [appConfirm] from [none] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:Hooking control events for [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:PageDialogControl.show() (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:FlowController.handleControlEvent [onSetupEvents] for [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:FlowController.handleControlEvent [onShow] for [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:PageDialogControl.~show() (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:45 GMT:FlowController.notifyVisible [appConfirm] (Confirm, line 78)
[Error] Failed to load resource: the server responded with a status of 404 () (CustomFunctions.js.map, line 0)
[Error] Failed to load resource: the server responded with a status of 404 () (ms.properties-3.2.6.min.js.map, line 0)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:FlowController.handleControlEvent [onAction] for [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:FlowController.processActionEvent[next] for [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:FlowController.processActionEvent newState [success] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:FlowController.showControl(success) (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:New State [success] from [appConfirm] (Confirm, line 78)
[Log] Mon, 17 Jul 2023 16:37:47 GMT:Navigate to [https://login.live.com/oauth20_authorize.srf?uaid=65df5d6b0986473e8dcae64a41db9303&client_id=xxx&opid=39F05E265291377E&mkt=EN-GB&opidt=1689611864&route=C107_BL2&res=success] (Confirm, line 78)
[Debug] Permission granted (index-95a4e7e3.js, line 107)
[Error] Failed to load resource: the server responded with a status of 400 (Bad Request) (token, line 0)

MSAL Configuration

const pcaConfig = {
  auth: {
    clientId: import.meta.env.VITE_OIDC_CLIENT_ID,
    navigateToLoginRequestUrl: true, // Go back to the original page after login
    postLogoutRedirectUri: "/", // Go back to the app root after logout
    redirectUri: "/", // Go back to the app root after login
  },
  cache: {
    cacheLocation: "localStorage",
    temporaryCacheLocation: "sessionStorage",
  },
  system: {
    navigationClient: new CustomNavigationClient(router.navigate),
    loggerOptions: {
      logLevel: import.meta.env.DEV || import.meta.env.TAURI_DEBUG ? LogLevel.Verbose : LogLevel.Warning,
      loggerCallback: (level, message, containsPii) => {
        if (containsPii) {
          return;
        }
        switch (level) {
          case LogLevel.Error:
            console.error(message);
            return;
          case LogLevel.Info:
            console.info(message);
            return;
          case LogLevel.Verbose:
            console.debug(message);
            return;
          case LogLevel.Warning:
            console.warn(message);
            return;
        }
      },
      piiLoggingEnabled: false,
    },
  },
};

Relevant Code Snippets

The application is run from Tauri (embedded web app on desktop, no Node.js runtime).

const IS_TAURI = window.__TAURI_METADATA__ != undefined;

const login = async (instance) => {
  IS_TAURI ? await instance.loginRedirect() : await instance.loginPopup();
};

const getIdToken = async (account, instance) => {
  if (!account) return null;

  const req = {
    account: account,
    loginHint: account.username,
    scopes: ["openid", "profile", "email", "User.Read"],
  };

  // Try silent first
  const idToken = await instance
    .acquireTokenSilent(req)
    .then((res) => {
      return res.idToken;
    })
    .catch((error) => {
      if (!(error instanceof InteractionRequiredAuthError)) {
        console.error(error);
        return null;
      }

      const onSuccess = (res) => {
        return res.idToken;
      };

      const onError = (error) => {
        console.error(error);
        return null;
      };

      if (IS_TAURI) {
        // Failback to redirect
        return instance
          .acquireTokenRedirect(req)
          .then(onSuccess)
          .catch(onError);
      }

      // Failback to popup
      return instance.acquireTokenPopup(req).then(onSuccess).catch(onError);
    });

  return idToken;
};

AAD app manifest:

{
    "id": "xxx",
    "acceptMappedClaims": null,
    "accessTokenAcceptedVersion": 2,
    "addIns": [],
    "allowPublicClient": true,
    "appId": "e9d5f20f-7f14-4204-a9a2-0d91d6af5c82",
    "appRoles": [
        {
            "allowedMemberTypes": [
                "User"
            ],
            "description": "Ability to access the application.",
            "displayName": "Contributors",
            "id": "687bbba0-26c1-435e-9e48-5cdd93d423cb",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "value": "Contributors"
        }
    ],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2023-06-28T12:43:54Z",
    "description": null,
    "certification": null,
    "disabledByMicrosoftStatus": null,
    "groupMembershipClaims": "None",
    "identifierUris": [],
    "informationalUrls": {
        "termsOfService": null,
        "support": null,
        "privacy": null,
        "marketing": null
    },
    "keyCredentials": [],
    "knownClientApplications": [],
    "logoUrl": "https://aadcdn.msftauthimages.net/dbd5a2dd-arfqpbjsje9u4fcbla9kbny3eqocpihhyg0ntigcgqg/appbranding/kjco3fpdmdpzymxko05usfexpyybabekbazxi4jp40y/1033/bannerlogo?ts=638251791039670967",
    "logoutUrl": null,
    "name": "Private GPT",
    "notes": null,
    "oauth2AllowIdTokenImplicitFlow": true,
    "oauth2AllowImplicitFlow": false,
    "oauth2Permissions": [],
    "oauth2RequirePostResponse": false,
    "optionalClaims": {
        "idToken": [
            {
                "name": "login_hint",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ],
        "accessToken": [],
        "saml2Token": []
    },
    "orgRestrictions": [],
    "parentalControlSettings": {
        "countriesBlockedForMinors": [],
        "legalAgeGroupRule": "Allow"
    },
    "passwordCredentials": [],
    "preAuthorizedApplications": [],
    "publisherDomain": "xxx.onmicrosoft.com",
    "replyUrlsWithType": [
        {
            "url": "https://private-gpt.shopping-cart-devops-demo.lesne.pro",
            "type": "Spa"
        },
        {
            "url": "http://localhost:8080",
            "type": "Spa"
        },
        {
            "url": "tauri://localhost",
            "type": "InstalledClient"
        },
        {
            "url": "https://private-gpt.shopping-cart-devops-demo.lesne.pro/auth",
            "type": "Spa"
        }
    ],
    "requiredResourceAccess": [
        {
            "resourceAppId": "00000003-0000-0000-c000-000000000000",
            "resourceAccess": [
                {
                    "id": "14dad69e-099b-42c9-810b-d002981feec1",
                    "type": "Scope"
                },
                {
                    "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
                    "type": "Scope"
                },
                {
                    "id": "37f7f235-527c-4136-accd-4a02d197296e",
                    "type": "Scope"
                },
                {
                    "id": "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0",
                    "type": "Scope"
                }
            ]
        }
    ],
    "samlMetadataUrl": null,
    "signInUrl": "https://github.com/clemlesne/private-gpt",
    "signInAudience": "AzureADandPersonalMicrosoftAccount",
    "tags": [
        "NoLiveSdkSupport"
    ],
    "tokenEncryptionKeyId": null
}

Expected Behavior

I would expect the logging to work the same it is working for the web version.

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

Other

Related links (suggested)

Source

Internal (Microsoft)

clemlesne commented 1 year ago

Also created an issue to the doc, if this is a missing piece of doc: https://github.com/MicrosoftDocs/azure-docs/issues/112289.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @konstantin-msft please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @konstantin-msft please follow up.

sameerag commented 1 year ago

@clemlesne SPAs only support only URLs that start with https: for production apps and http://localhost for local dev. The format you mentioned above can be supported only for mobile or web apps as they have a confidential component unlike browser apps. Hope this clarifies.

clemlesne commented 1 year ago

Well Tauri is a like Electron but without the Node.js backend. It’s, sort of, a web browser with a Rust backend handling communication with the host. Not having a HTTP server exposed lowers the risk of man in the middle attack and attacks: no server, no security issues. Enable the HTTP server is feasible but flawed.

Thus… why restricting the protocol name to “https://” only? Security is the same, integration is the same.

Am I missing something?

sameerag commented 1 year ago

Yes, our verification of redirectUri depends on the https messaging to the service. Since it is a SPA and no secrets are exchanged, this is the only way we can confirm the app's validity to receive the tokens.

More docs can be found here.

clemlesne commented 1 year ago

What do you advise making this work? I'm still stuck with Tauri and MSAL.js.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

microsoft-github-policy-service[bot] commented 1 year ago

This issue requires attention from the MSAL.js team and has not seen activity in 5 days. @sameerag please follow up.

sameerag commented 1 year ago

What do you advise making this work? I'm still stuck with Tauri and MSAL.js.

You cannot use msal js without having a web URL as a redirectUri. It is not something MSAL JS controls, it is how SPAs backend is designed for AAD. Sorry we cannot be of much help here.

datner commented 1 year ago

This is a joke, this could be theoretically done using msal-node if it didn't rely on node-exclusive api like util.inherits. I understand that supporting not-c-based native clients, especially browser-emulating ones is a daunting task. But as it stands the official advice of the authentication library is "well, we're only talking about security here. Theres no need to invest so much in security. Just use the node server impl and pkce. So what if the tokens are open to hijacking?"

The question here asks about the tauri:// protocol (as outlined and required by both the oauth2 spec, and the microsoft docs, btw.), I understand that the answer to that literal question is "we don't have the ability to allow that", but the actual question that was asked was "how do you perform auth in conditions such as tauri".

Working with azures oauth2 api is hard enough, really. I don't think that needing to find a hack around msals stated impossibility (despite it being entirely possible) should be another hurdle in the herculean task that is working with azure.

I have a working implementation for tauri. It's 100% possible. I wanted to swap over to msal because I started to need to juggle a few tokens around and I really didn't want to. Especially with the teeth-grinding experience that is implementing it myself.