简体   繁体   English

@azure/msal-node -> 用户授权访问 Azure Vault

[英]@azure/msal-node -> User auth to access Azure Vault

So,所以,

I've got a test azure instance and I'm trying to get access to a secret in a vault.我有一个测试 azure 实例,我正在尝试访问保险库中的秘密。

I've created the vault and now I want to access it with an electron application.我已经创建了保管库,现在我想使用 electron 应用程序访问它。 I do not wish to have any client secrets anywhere on the application.我不希望在应用程序的任何地方都有任何客户端机密。 So I am trying to get a bearer token to present to the vault using an authorized user and only the registered application's ID因此,我正在尝试使用授权用户和仅注册应用程序的 ID 获取不记名令牌以呈现给保管库

Image of Vault保险库的图像

In this case the user in question is part of a group that in the vault's access policies has Get and List for secrets.在这种情况下,相关用户属于在保管库的访问策略中具有获取和列出机密的组的一部分。

I have an application with a MSAL authentication added.我有一个添加了 MSAL 身份验证的应用程序。

Image of application registration申请注册图片

I'm attempting to make use of some very slightly modified versions of this code base example: https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-nodejs-desktop我正在尝试使用此代码库示例的一些非常轻微修改的版本: https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-nodejs-desktop

global.api.azure global.api.azure

// https://docs.microsoft.com/en-us/rest/api/keyvault/
// Azure key Vault (API Key Storage / etc.) 

"use strict";
const {BrowserWindow} = require("electron")
const {
    PublicClientApplication,
    CryptoProvider
} = require("@azure/msal-node")
const api = require("../api/api")

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

const { protocol } = require("electron");

/**
 * CustomProtocolListener can be instantiated in order
 * to register and unregister a custom typed protocol on which
 * MSAL can listen for Auth Code reponses.
 *
 * For information on available protocol types, check the Electron
 * protcol docs: https://www.electronjs.org/docs/latest/api/protocol/
 */
class CustomProtocolListener {
  hostName;
  /**
   * Constructor
   * @param hostName - A string that represents the host name that should be listened on (i.e. 'msal' or '127.0.0.1')
   */
  constructor(hostName) {
    this.hostName = hostName; //A string that represents the host name that should be listened on (i.e. 'msal' or '127.0.0.1')
  }

  get host() {
    return this.hostName;
  }

  /**
   * Registers a custom string protocol on which the library will
   * listen for Auth Code response.
   */
  start() {
    const codePromise = new Promise((resolve, reject) => {
      protocol.registerStringProtocol(this.host, (req, callback) => {
        const requestUrl = new URL(req.url);
        const authCode = requestUrl.searchParams.get("code");
        if (authCode) {
          resolve(authCode);
        } else {
          protocol.unregisterProtocol(this.host);
          reject(new Error("No code found in URL"));
        }
      });
    });

    return codePromise;
  }

  /**
   * Unregisters a custom string protocol to stop listening for Auth Code response.
   */
  close() {
    protocol.unregisterProtocol(this.host);
  }
}

const REDIRECT_URI = "msald3e41458-b256-4b01-87c2-54c9a4db1eef://auth";

const msalConfig = {
    auth: {
      clientId: "d3e41458-b256-4b01-87c2-54c9a4db1eef",
      authority: `https://login.microsoftonline.com/fullphaseroutlook.onmicrosoft.com`,
    },
  };

class azure { 
    constructor() {
        /**
         * Initialize a public client application. For more information, visit:
         * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/initialize-public-client-application.md
         */
        this.clientApplication = new PublicClientApplication(msalConfig);
        this.account = null;
    
        // Initialize CryptoProvider instance
        this.cryptoProvider = new CryptoProvider();
    
        /**
         * To demonstrate best security practices, this Electron sample application makes use of
         * a custom file protocol instead of a regular web (https://) redirect URI in order to
         * handle the redirection step of the authorization flow, as suggested in the OAuth2.0 specification for Native Apps.
         */
        this.customFileProtocolName = REDIRECT_URI.split(":")[0];
    
        this.setRequestObjects();
      }
    
      // Creates a "popup" window for interactive authentication
      static createAuthWindow() {
        return new BrowserWindow({
          width: 400,
          height: 600,
        });
      }
    
      /**
       * Initialize request objects used by this AuthModule.
       */
      setRequestObjects() {
        const requestScopes = ["User.Read"];
    
        this.authCodeUrlParams = {
          scopes: requestScopes,
          redirectUri: REDIRECT_URI,
        };
    
        this.authCodeRequest = {
          scopes: requestScopes,
          redirectUri: REDIRECT_URI,
          code: null,
        };
    
        this.pkceCodes = {
          challengeMethod: "S256", // Use SHA256 Algorithm
          verifier: "", // Generate a code verifier for the Auth Code Request first
          challenge: "", // Generate a code challenge from the previously generated code verifier
        };
      }
    
      async login() {
        const authResult = await this.getTokenInteractive(this.authCodeUrlParams);
        return this.handleResponse(authResult);
      }
    
      async logout() {
        if (this.account) {
          await this.clientApplication.getTokenCache().removeAccount(this.account);
          this.account = null;
        }
      }
    
      async getToken(tokenRequest) {
        let authResponse;
        const account = this.account || (await this.getAccount());
        if (account) {
          tokenRequest.account = account;
          authResponse = await this.getTokenSilent(tokenRequest);
        } else {
          const authCodeRequest = {
            ...this.authCodeUrlParams,
            ...tokenRequest,
          };
    
          authResponse = await this.getTokenInteractive(authCodeRequest);
        }
    
        return authResponse.accessToken || null;
      }
    
      async getTokenSilent(tokenRequest) {
        try {
          var data = await this.clientApplication.acquireTokenSilent(tokenRequest);
          console.log(data)
          return data 
        } catch (error) {
          console.log(
            "Silent token acquisition failed, acquiring token using pop up"
          );
          const authCodeRequest = {
            ...this.authCodeUrlParams,
            ...tokenRequest,
          };
          return await this.getTokenInteractive(authCodeRequest);
        }
      }
    
      async getTokenInteractive(tokenRequest) {
        /**
         * Proof Key for Code Exchange (PKCE) Setup
         *
         * MSAL enables PKCE in the Authorization Code Grant Flow by including the codeChallenge and codeChallengeMethod parameters
         * in the request passed into getAuthCodeUrl() API, as well as the codeVerifier parameter in the
         * second leg (acquireTokenByCode() API).
         *
         * MSAL Node provides PKCE Generation tools through the CryptoProvider class, which exposes
         * the generatePkceCodes() asynchronous API. As illustrated in the example below, the verifier
         * and challenge values should be generated previous to the authorization flow initiation.
         *
         * For details on PKCE code generation logic, consult the
         * PKCE specification https://tools.ietf.org/html/rfc7636#section-4
         */
    
        const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
        this.pkceCodes.verifier = verifier;
        this.pkceCodes.challenge = challenge;
        const popupWindow = azure.createAuthWindow();
    
        // Add PKCE params to Auth Code URL request
        const authCodeUrlParams = {
          ...this.authCodeUrlParams,
          scopes: tokenRequest.scopes,
          codeChallenge: this.pkceCodes.challenge, // PKCE Code Challenge
          codeChallengeMethod: this.pkceCodes.challengeMethod, // PKCE Code Challenge Method
        };
    
        try {
          // Get Auth Code URL
          const authCodeUrl = await this.clientApplication.getAuthCodeUrl(
            authCodeUrlParams
          );
          const authCode = await this.listenForAuthCode(authCodeUrl, popupWindow);
          // Use Authorization Code and PKCE Code verifier to make token request
          const authResult = await this.clientApplication.acquireTokenByCode({
            ...this.authCodeRequest,
            code: authCode,
            codeVerifier: verifier,
          });
    
          popupWindow.close();
          return authResult;
        } catch (error) {
          popupWindow.close();
          throw error;
        }
      }
    
      async listenForAuthCode(navigateUrl, authWindow) {
        // Set up custom file protocol to listen for redirect response
        const authCodeListener = new CustomProtocolListener(
          this.customFileProtocolName
        );
        const codePromise = authCodeListener.start();
        authWindow.loadURL(navigateUrl);
        const code = await codePromise;
        authCodeListener.close();
        return code;
      }
    
      /**
       * Handles the response from a popup or redirect. If response is null, will check if we have any accounts and attempt to sign in.
       * @param response
       */
      async handleResponse(response) {
        if (response !== null) {
          this.account = response.account;
        } else {
          this.account = await this.getAccount();
        }
        return response;
      }
    
      /**
       * Calls getAllAccounts and determines the correct account to sign into, currently defaults to first account found in cache.
       * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
       */
      async getAccount() {
        // need to call getAccount here?
        const cache = this.clientApplication.getTokenCache();
        const currentAccounts = await cache.getAllAccounts();
    
        if (currentAccounts === null) {
          console.log("No accounts detected");
          return null;
        }
    
        if (currentAccounts.length > 1) {
          // Add choose account code here
          console.log(
            "Multiple accounts detected, need to add choose account code."
          );
          return currentAccounts[0];
        } else if (currentAccounts.length === 1) {
          return currentAccounts[0];
        } else {
          return null;
        }
      }
}

const vaultURL = "https://vault-test.vault.azure.net/"
class vault extends api{
    constructor(config){
        super(config)
        this.base = vaultURL
        this.headers = { 
            'Authorization': `Bearer ${config.token}`,
        }
    }
    async getSecret(secretName){
        return await this._req(`secrets/${secretName}/`,{
            method: "GET",
        })
    }
}

module.exports = { azure, vault } 
async function go(){
    var login = new global.api.azure.azure()
    var resp = await login.login() 
    var vault = new global.api.azure.vault({
        token: resp.accessToken
    })
    var secret = await vault.getSecret("test")
}
go() 

The "extends API" is little more then a wrapper for the axios npm package. “扩展 API”只不过是 axios npm package 的包装器。 Everything for the most part works fine.大多数情况下一切正常。 Login Prompt in Electron Electron 中的登录提示

The login prompt is presented, the user logs in and the correct account is retrieved along with a bearer token出现登录提示,用户登录并检索正确的帐户以及不记名令牌

Bearer Token不记名令牌

However the vault specifies that the Audience is invalid但是保险库指定受众无效

Vault Response保险柜响应

I know I'm not presenting the correct audience in the scope, but I was curious how I would go about doing that.我知道我没有在 scope 中展示正确的观众,但我很好奇 go 如何做到这一点。 I tried adding the Azure Key Vault / User Impersonation API permission under the registered app;我尝试在注册的应用程序下添加 Azure Key Vault / User Impersonation API 权限; however, when changing然而,当改变

const requestScopes = ["User.Read"];

To something like user_impersonation, the CustomProtocolListener returns an error that the code was undefined / null.对于 user_impersonation 之类的东西,CustomProtocolListener 返回一个错误,即代码未定义 / null。 Any advice would be greatly appreciated.任何建议将不胜感激。

For KV, you need to define the entire URL of the scope, you cannot pass only user_impersonation.对于KV,需要定义整个scope的URL,不能只传递user_impersonation。 The short version of the scopes can only be used with MS Graph.范围的短版本只能与 MS Graph 一起使用。 For example, the below are equivalent:例如,以下是等价的:

const requestScopes = ["User.Read"];
const requestScopes = ["https://graph.microsoft.com/User.Read"];

Try changing your scopes array to the following, that should do the trick:尝试将您的范围数组更改为以下内容,这应该可以解决问题:

const requestScopes = ["https://vault.azure.net/user_impersonation"];

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 msal-node js 中使用带有证书的 SPN 获取访问令牌的 azure 活动目录面临错误“ERR_OSSL_PEM_NO_START_LINE” - azure active directory get access token using SPN with certificate in msal-node js facing error "ERR_OSSL_PEM_NO_START_LINE " Azure MSAL 节点 ClientAuthError - Azure MSAL node ClientAuthError 从浏览器访问 Azure Key Vault 的秘密(node.js 和 Vue.js) - Access secret from Azure Key Vault from browser (node.js with Vue.js) 如何在利用路由和中间件模块的非单体应用程序设计中实现 msal-node? - How to implement msal-node in a non-monolithic application design that utilises route and middleware modules? 有什么方法可以使用@azure/msal-angular 登录用户 azure 订阅 ID? - Is there any way to get logged in user azure subscription Id's using @azure/msal-angular? 401-未经授权的访问:Auth0和azure - 401 - unauthorized access : Auth0 and azure 如何获取自定义 Azure App API 的有效访问令牌? (MSAL.js) - How to get a valid access token for a custom Azure App API? (MSAL.js) 在azure webapp中使用MSAL.js进行身份验证 - Authenticating using MSAL.js in azure webapp @ azure / msal-angular的元数据版本不匹配 - MetaData version mismatch for @azure/msal-angular 使用Node.js访问azure tablestorage - Using Node.js to access azure tablestorage
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM