Как включить CORS в Azure регистрации приложения при использовании в потоке авторизации OAuth с PKCE? - PullRequest
3 голосов
/ 21 января 2020

У меня есть чистое Javascript приложение, которое пытается получить токен доступа от Azure, используя OAuth-поток авторизации с PKCE.

Приложение не размещено в Azure. Я использую только Azure в качестве сервера авторизации OAuth.

    //Based on: https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead

    var config = {
        client_id: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx",
        redirect_uri: "http://localhost:8080/",
        authorization_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize",
        token_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token",
        requested_scopes: "openid api://{tenant-id}/user_impersonation"
    };

    // PKCE HELPER FUNCTIONS

    // Generate a secure random string using the browser crypto functions
    function generateRandomString() {
        var array = new Uint32Array(28);
        window.crypto.getRandomValues(array);
        return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
    }

    // Calculate the SHA256 hash of the input text. 
    // Returns a promise that resolves to an ArrayBuffer
    function sha256(plain) {
        const encoder = new TextEncoder();
        const data = encoder.encode(plain);
        return window.crypto.subtle.digest('SHA-256', data);
    }

    // Base64-urlencodes the input string
    function base64urlencode(str) {
        // Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
        // btoa accepts chars only within ascii 0-255 and base64 encodes them.
        // Then convert the base64 encoded to base64url encoded
        //   (replace + with -, replace / with _, trim trailing =)
        return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
    .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    }

    // Return the base64-urlencoded sha256 hash for the PKCE challenge
    async function pkceChallengeFromVerifier(v) {
        const hashed = await sha256(v);
        return base64urlencode(hashed);
    }

    // Parse a query string into an object
    function parseQueryString(string) {
        if (string == "") { return {}; }
        var segments = string.split("&").map(s => s.split("="));
        var queryString = {};
        segments.forEach(s => queryString[s[0]] = s[1]);
        return queryString;
    }

    // Make a POST request and parse the response as JSON
    function sendPostRequest(url, params, success, error) {
        var request = new XMLHttpRequest();
        request.open('POST', url, true);
        request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
        request.onload = function () {
            var body = {};
            try {
              body = JSON.parse(request.response);
            } catch (e) { }

            if (request.status == 200) {
              success(request, body);
            } else {
              error(request, body);
            }
        }

        request.onerror = function () {
            error(request, {});
        }
        var body = Object.keys(params).map(key => key + '=' + params[key]).join('&');
        request.send(body);
    }

    function component() {
        const element = document.createElement('div');
        const btn = document.createElement('button');
        element.innerHTML = 'Hello'+ 'webpack';
        element.classList.add('hello');
        return element;
    }


    (async function () {
        document.body.appendChild(component());

        const isAuthenticating = JSON.parse(window.localStorage.getItem('IsAuthenticating'));
        console.log('init -> isAuthenticating', isAuthenticating);
        if (!isAuthenticating) {
        window.localStorage.setItem('IsAuthenticating', JSON.stringify(true));

        // Create and store a random "state" value
        var state = generateRandomString();
        localStorage.setItem("pkce_state", state);

        // Create and store a new PKCE code_verifier (the plaintext random secret)
        var code_verifier = generateRandomString();
        localStorage.setItem("pkce_code_verifier", code_verifier);

        // Hash and base64-urlencode the secret to use as the challenge
        var code_challenge = await pkceChallengeFromVerifier(code_verifier);

        // Build the authorization URL
        var url = config.authorization_endpoint
      + "?response_type=code"
      + "&client_id=" + encodeURIComponent(config.client_id)
      + "&state=" + encodeURIComponent(state)
      + "&scope=" + encodeURIComponent(config.requested_scopes)
      + "&redirect_uri=" + encodeURIComponent(config.redirect_uri)
      + "&code_challenge=" + encodeURIComponent(code_challenge)
      + "&code_challenge_method=S256"
      ;

        // Redirect to the authorization server
        window.location = url;
    } else {

        // Handle the redirect back from the authorization server and
        // get an access token from the token endpoint

        var q = parseQueryString(window.location.search.substring(1));

        console.log('queryString', q);

        // Check if the server returned an error string
        if (q.error) {
          alert("Error returned from authorization server: " + q.error);
          document.getElementById("error_details").innerText = q.error + "\n\n" + q.error_description;
          document.getElementById("error").classList = "";
        }

        // If the server returned an authorization code, attempt to exchange it for an access token
        if (q.code) {

          // Verify state matches what we set at the beginning
          if (localStorage.getItem("pkce_state") != q.state) {
            alert("Invalid state");
          } else {

            // Exchange the authorization code for an access token
            // !!!!!!! This POST fails because of CORS policy.
            sendPostRequest(config.token_endpoint, {
              grant_type: "authorization_code",
              code: q.code,
              client_id: config.client_id,
              redirect_uri: config.redirect_uri,
              code_verifier: localStorage.getItem("pkce_code_verifier")
            }, function (request, body) {

              // Initialize your application now that you have an access token.
              // Here we just display it in the browser.
              document.getElementById("access_token").innerText = body.access_token;
              document.getElementById("start").classList = "hidden";
              document.getElementById("token").classList = "";

              // Replace the history entry to remove the auth code from the browser address bar
              window.history.replaceState({}, null, "/");

            }, function (request, error) {
              // This could be an error response from the OAuth server, or an error because the 
              // request failed such as if the OAuth server doesn't allow CORS requests
              document.getElementById("error_details").innerText = error.error + "\n\n" + error.error_description;
              document.getElementById("error").classList = "";
            });
          }

          // Clean these up since we don't need them anymore
          localStorage.removeItem("pkce_state");
          localStorage.removeItem("pkce_code_verifier");
        }
    }

    }());

В Azure У меня есть только регистрация приложения (не служба приложения).

Azure Регистрация приложения

Первый шаг для получения кода авторизации работает.

Но POST для получения токена доступа не выполняется. (изображение из здесь )

Поток кода авторизации OAuth с PKCE

Доступ к XMLHttpRequest по адресу https://login.microsoftonline.com/ {tenant-id} /oauth2/v2.0/token 'from origin' http://localhost: 8080 'заблокировано политикой CORS: нет заголовка' Access-Control-Allow-Origin ' присутствует на запрошенном ресурсе.

Где в Azure настроить политику CORS для регистрации приложения?

Ответы [ 3 ]

1 голос
/ 21 января 2020

Последнее, что я слышал, конечная точка токена Azure AD не разрешает запросы CORS от браузеров к конечной точке токена, поэтому вам может потребоваться использовать неявный поток (response_type = token id_token).

Позор если это все еще верно - потому что поток, который вы пытаетесь использовать, определенно правильный (response_type = code).

Я бы порекомендовал использовать библиотеку, которая позволяет вам легко переключаться, когда Microsoft наконец-то доходит до исправления этого , Мои примеры ниже имеют пример обоих потоков:

Приведенный выше * README файл репозитория с примером кода 1020 * ссылается на запись, в которой, кстати, упоминаются некоторые другие Azure проблемы CORS + обходные пути.

1 голос
/ 21 января 2020

Вы должны определить внутренний URL с вашим локальным адресом хоста.

enter image description here

https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/application-proxy-understand-cors-issues

0 голосов
/ 08 апреля 2020

Ладно, после нескольких дней стука моей головы против глупости реализации Azure я наткнулся на небольшой скрытый кусок информации здесь: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser#prerequisites

Если вы измените тип redirectUri в манифесте от «Web» до «Spa» возвращает мне токен доступа! Мы в деле! Это нарушает пользовательский интерфейс в Azure, но пусть будет так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...