1

I created sample react login application where user can login by implict oauth2 login via azure ad by refering to this documentation.

After successful login I am not able to access keyvault secrets by using microsoft graph api with the help of access token.

This is the error I am getting while I am trying to access secrets from keyvault enter image description here

I also updated the scopes in my code by adding additional keyvault scope in config.js file.

module.exports ={
  appId: "6c90510c-82fd-4113-8aa5-6abdac107839",
  scopes: [
    "user.read",
    "https://vault.azure.net/user_impersonation"
  ]
};

I also added Keyvault Api permissions in app registration from azure portal. enter image description here.

Here is my login code

import { UserAgentApplication } from 'msal'
class Login extends React.Component {

  constructor(props) {
    super(props);
    this.submitHandler = this.submitHandler.bind(this);
    this.email = React.createRef();
    this.password = React.createRef();
    this.token = "";
    this.expiresOn = 0;
    this.userAgentApplication = new UserAgentApplication({
      auth: {
        clientId: config.appId,
        clientSecret: "W~~4iZ.uKv~eoAd-hKKU35WJVv.---83Gm",
        redirectUri: "http://localhost:3000/events"
      },
      cache: {
        cacheLocation: "localStorage", // This configures where your cache will be stored
        storeAuthStateInCookie: true, // Set this to "true" if you are having issues on IE11 or Edge
      }
    });

    this.state = {
      error: null,
      isAuthenticated: false,
      user: {}
    };
  }

  login = async () => {
    try {
      // Login via popup
      await this.userAgentApplication.loginPopup(
        {
          scopes: config.scopes,
          prompt: "select_account"
        });

      // After login, get the user's profile
      await this.getUserProfile();
      var user = this.userAgentApplication.getAccount();
    }
    catch (err) {
      console.log("errror", err.message)
      this.setState({
        isAuthenticated: false,
        user: {},
        error: err.message
      });
    }
  }

  logout = () => {
    this.userAgentApplication.logout();
  }

  getUserProfile = async () => {
    try {
      const data = await this.userAgentApplication.acquireTokenSilent({
        scopes: config.scopes
      });

      if (data.accessToken) {
        console.log("Token", data.accessToken);
        this.token = data.accessToken;
        this.expiresOn = data.expiresOn;
      }
    }
    catch (err) {
      console.log("err", err.message);
    }
  }

  render() {
    const { isLogin } = this.props;
    const buttonTitle = isLogin ? "Sign Up" : "Login";
    return (
      <form className="login-form">
        { !isLogin && <IconButton backgroundColor="#0A66C2" font="14px" width='35%' padding='8px' microsoftLoginHandler={this.login} />}
      </form>
    );
  }
}

After getting access token when I tried the hit api from postman. It is showing some error. Can anyone please guide me to resolve this error as I am new to Azure Ad and Keyvault

Thanks in advance

akhil
  • 1,649
  • 3
  • 19
  • 31
  • 2
    You should not be using Client Authentication for SPAs because your `clientSecret` won't be a secret. Similarly, client-side applications must never access a Key Vault directly for the same reason. You should be using OIDC Implicit authentication for this and handling secret-management in your own server-side application code. – Dai Jan 16 '21 at 08:16
  • @Dai, Is there any way to retrieve the key vault information for a specific user who has permission (added user service principal for a specific user) to get a secret from the key vault? – akhil Jan 16 '21 at 09:24
  • I've never worked with Key Vault in this scenario, but provided the user has authenticated against AzureAD and they have permission to access the Key Vault with their `access_token` (check the `scope` member in the JWT) then that should work _in theory_ - but I wouldn't be surprised if AzureAD disallows it for insecure clients. – Dai Jan 16 '21 at 11:14

1 Answers1

1

Take my code into consideration, it offers function to get access token which can used to call api to access key vault:

And pls note, at first I got the same error message as yours when I call api with access token in ["openid", "profile", "User.Read", "https://vault.azure.net/user_impersonation"], then I decode the token and found it didn't contain 'user_impersonation' in claim 'sub', so I changed the scope in the code and then it worked.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <script src="js/jquery-1.10.2.min.js"></script>
        <script type="text/javascript" src="https://alcdn.msftauth.net/lib/1.2.1/js/msal.js" integrity="sha384-9TV1245fz+BaI+VvCjMYL0YDMElLBwNS84v3mY57pXNOt6xcUYch2QLImaTahcOP" crossorigin="anonymous"></script>
    </head>
    <body>
        <button id="btn">click</button>
        <div>
            userName:<input type="text" id="userName" />
        </div>
        <div id="accessToken"></div>
        
        <script type="text/javascript">
            $("#btn").click(function(){
                showWelcome();
            })
            
            const msalConfig = {
                auth: {
                  clientId: "<your azure ad app id>", // pls add api permission with azure key vault
                  authority: "https://login.microsoftonline.com/<your-tenant>",
                  redirectUri: "http://localhost:8848/msalTest/index.html",
                },
                cache: {
                  cacheLocation: "sessionStorage", // This configures where your cache will be stored
                  storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
                }
              };
            
            const myMSALObj = new Msal.UserAgentApplication(msalConfig);
            
            //at first, I used scope like ["openid", "profile", "User.Read", "https://vault.azure.net/user_impersonation"]
            //but with this accesstoken, I got the same error as yours
            //and I try to use the scope below, and it worked 
            //I decode the token with jwt, when I get error, the token didn't contains correct scope
            const loginRequest = {
                scopes: ["openid", "profile", "https://vault.azure.net/user_impersonation"],
            };
            
            function showWelcome(){
                myMSALObj.loginPopup(loginRequest)
                    .then((loginResponse) => {
                        console.info(loginResponse);
                        console.log("========= myMSALObj.getAccount() =======");
                        console.log(myMSALObj.getAccount());
                        $("#userName").val(myMSALObj.getAccount().name);
                        getAccessToken();
                    //Login Success callback code here
                }).catch(function (error) {
                    console.log(error);
                });
            }
            
            function getAccessToken(){
                getTokenPopup(loginRequest)
                      .then(response => {
                        $("#accessToken").text(response.accessToken);
                      }).catch(error => {
                        console.log(error);
                      });
            }
            
            function getTokenPopup(request) {
              return myMSALObj.acquireTokenSilent(request)
                .catch(error => {
                  console.log(error);
                  console.log("silent token acquisition fails. acquiring token using popup");
            
                  // fallback to interaction when silent call fails
                    return myMSALObj.acquireTokenPopup(request)
                      .then(tokenResponse => {
                        return tokenResponse;
                      }).catch(error => {
                        console.log(error);
                      });
                });
            }
            
        </script>
    </body>
</html>

enter image description here

Tiny Wang
  • 10,423
  • 1
  • 11
  • 29
  • thank you so much for the response, I will try by updating the scopes – akhil Jan 18 '21 at 03:27
  • Thank you so much @Tiny-wa, I am able to access the secrets from key vaults with the access token. – akhil Jan 23 '21 at 06:11
  • I have one more doubt is there a scope hierarchy? when I placed the same scopes in this order scopes: ["openid", "profile", "User.Read", "https://vault.azure.net/user_impersonation"] it is not working and when I debug the token it is showing like this "scp": "openid profile User.Read email" but when i changed the order of the scopes like this scopes: ["openid", "profile", "https://vault.azure.net/user_impersonation","User.Read"] in jwt.io debugger it is showing only "scp": "user_impersonation", what about the other scopes? – akhil Jan 23 '21 at 06:30
  • it will be very helpful if you provide an answer to the above comment – akhil Jan 23 '21 at 06:47
  • 1
    @akhil I also met the same problem as yours recently, and I found that we can't set different groups of api permission in one access token, that means if we add graph api and key vault api in the same scope, then we can get a token successfully but it only has the first api permission. Just like [this](https://stackoverflow.com/a/65842793/14574199) – Tiny Wang Jan 23 '21 at 15:06