Azure-Samples / ms-identity-javascript-v2

VanillaJS sample using MSAL.js v2.x and OAuth 2.0 Authorization Code Flow with PKCE on Microsoft identity platform
MIT License
105 stars 84 forks source link

401 even with a valid token #14

Closed dochoss closed 4 years ago

dochoss commented 4 years ago

Hi, MS Identity team,

First, I want to say that I appreciate the work you guys are doing to simplify auth as a whole. It's my weakest point in development, by far. I just feel like there's something I'm not getting and your work is helping. So here's my situation...

I have a .Net Core 3.1 Web API I want to protect with an AAD-issued bearer access token. I'm getting the token using MSAL and that is working fine. I've verified the token at jwt.ms and everything looks good: aud is my API's client ID from AAD, scp is my custom scope that I'm requesting, and iss is login.microsoftonline.com/<my tenant id> as expected. I get the token with MSAL and add it to my request using JavaScript fetch and adding the token to the 'Authentication' header with the value Bearer <token>.

If I leave off the [Authorize] attribute in the web API (allowing anonymous access), I can see that the token is indeed included in the request headers and that the value is the same as it should be. However, when I add the [Authorize] attribute, the response is a 401 and no code is being run in the API.

Details of my configurations are below. Please let me know what other information I can provide to figure this out. If need be, also let me know if there's a better place to ask for this type of help.

CLIENT

msalConfig.js

import * as msal from '@azure/msal-browser'

// Config object to be passed to Msal on creation
export const msalConfig = {
  auth: {
      clientId: "...my client id...",
      authority: "https://login.microsoftonline.com/... my tenant id ...",
      navigateToLoginRequestUrl: false
  },
  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
  },
  system: {
      loggerOptions: {
          loggerCallback: (level, message, containsPii) => {
              if (containsPii) {
                  return;
              }
              switch (level) {
                  case msal.LogLevel.Error:
                      console.error(message);
                      return;
                  case msal.LogLevel.Info:
                      console.info(message);
                      return;
                  case msal.LogLevel.Verbose:
                      console.debug(message);
                      return;
                  case msal.LogLevel.Warning:
                      console.warn(message);
                      return;
              }
          }
      }
  }
};

// Add here scopes for id token to be used at MS Identity Platform endpoints.
export const loginRequest = {
  scopes: ["User.Read"]
};

// Add here the endpoints for MS Graph API services you would like to use.
export const graphConfig = {
  graphMeEndpoint: "https://graph.microsoft-ppe.com/v1.0/me",
  graphMailEndpoint: "https://graph.microsoft-ppe.com/v1.0/me/messages"
};

// Add here scopes for access token to be used at MS Graph API endpoints.
export const tokenRequest = {
  scopes: ["Mail.Read"],
  forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token
};

export const silentRequest = {
  scopes: ["openid", "profile", "User.Read", "Mail.Read"]
};

export const apiRequest = {
  scopes: ["api://...my client id.../Access.Grant"]
}

auth.js

import * as msal from '@azure/msal-browser'

import {
  msalConfig,
  loginRequest
} from './msalConfig'

// Browser check variables
// If you support IE, our recommendation is that you sign-in using Redirect APIs
// If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
const ua = window.navigator.userAgent;
const msie = ua.indexOf("MSIE ");
const msie11 = ua.indexOf("Trident/");
// const msedge = ua.indexOf("Edge/");
const isIE = msie > 0 || msie11 > 0;
// const isEdge = msedge > 0;

let signInType;
let username = "";

// Create the main myMSALObj instance
// configuration parameters are located at authConfig.js
const myMSALObj = new msal.PublicClientApplication(msalConfig);

// Redirect: once login is successful and redirects with tokens, call Graph API
myMSALObj.handleRedirectPromise().then(handleResponse).catch(err => {
    console.error(err);
});

function handleResponse(resp) {
    if (resp !== null) {
        username = resp.account.username;
        // showWelcomeMessage(resp.account);
    } else {
        // need to call getAccount here?
        const currentAccounts = myMSALObj.getAllAccounts();
        if (!currentAccounts || currentAccounts.length < 1) {
            return;
        } else if (currentAccounts.length > 1) {
            // Add choose account code here
        } else if (currentAccounts.length === 1) {
            username = currentAccounts[0].username;
            // showWelcomeMessage(currentAccounts[0]);
        }
    }
}

export async function getAccounts() {
  let accounts = myMSALObj.getAllAccounts();
  return accounts;
}

export async function signIn(method) {
    signInType = isIE ? "loginRedirect" : method;
    if (signInType === "loginPopup") {
      try {
        let response = await myMSALObj.loginPopup(loginRequest);
        return response;
      }
      catch (err) {
        console.error(err);
      }       
    } else if (signInType === "loginRedirect") {
      return myMSALObj.loginRedirect(loginRequest)
    }
}

export function signOut() {
    const logoutRequest = {
        account: myMSALObj.getAccountByUsername(username)
    };

    myMSALObj.logout(logoutRequest);
}

export async function getTokenPopup(request, account) {
    request.account = account;
    return await myMSALObj.acquireTokenSilent(request).catch(async (error) => {
        console.log("silent token acquisition fails.");
        if (error instanceof msal.InteractionRequiredAuthError) {
            console.log("acquiring token using popup");
            return myMSALObj.acquireTokenPopup(request).catch(error => {
                console.error(error);
            });
        } else {
            console.error(error);
        }
    });
}

// This function can be removed if you do not need to support IE
export async function getTokenRedirect(request, account) {
    request.account = account;
    return await myMSALObj.acquireTokenSilent(request).catch(async (error) => {
        console.log("silent token acquisition fails.");
        if (error instanceof msal.InteractionRequiredAuthError) {
            // fallback to interaction when silent call fails
            console.log("acquiring token using redirect");
            myMSALObj.acquireTokenRedirect(request);
        } else {
            console.error(error);
        }
    });
}

Vue component (only showing script tag for brevity)

<script>
import * as auth from '../msal';

export default {
  name: 'HelloWorld',
  props: {
    msg: String

  },
  data() {
    return {
      user: {},
      requests: { ...auth.requests },
      holds: []
    }
  },
  methods: {
    async login() {
      let response = await auth.signIn('loginPopup');
      this.user = {...response.account};
    },
    async getApiToken() {
      let response = await auth.getTokenPopup(this.requests.api, this.user);
      console.log(response);
      return response;
    },
    async getData() {
      let tokenResponse = await this.getApiToken();
      let response = await fetch('https://localhost:4001/api/data', { headers: new Headers ({ "Authentication": `Bearer ${tokenResponse.accessToken}`})});
      if (response.status == 401) {
        // eslint-disable-next-line no-debugger
        debugger
        console.log('nope')
      } else {
        let data = await response.json();
        this.holds = data;
      }
    }
  },
  computed: {
    auth() {
      return auth;
    }
  }
}
</script>

API

Startup.cs (only showing relevant parts)

public void ConfigureServices (IServiceCollection services)
        {
            services.AddMicrosoftWebApiAuthentication(Configuration);

            services.AddCors();
            services.AddControllers();
            services.AddHttpContextAccessor();
            services.AddDbContext<ConfigContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("Config")));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection(); 
            app.UseCors(o => o.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();            
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {                
                endpoints.MapControllers();
            });
        }

appSettings.json

...
"AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "... my domain ... .onmicrosoft.com", 
        "TenantId": "... my AAD tenant id ...", 
        "ClientId": "... my API's AAD client id ..."
    }
...
derisen commented 4 years ago

@dochoss thank you very much : ) There isn't anything wrong with your code as far as I can see, so I think it would be best if you could send us a Fiddler trace (or any other network trace) and if you have any repository that we could look into that would be great as well. Please send them over mail to v-doeris at microsoft com.

dochoss commented 4 years ago

@derisen I'll be able to get that done on Monday. Don't close this over the weekend! :)

derisen commented 4 years ago

@dochoss any updates on this?

derisen commented 4 years ago

Closing due to inactivity.

pavandev-lab commented 1 year ago

HI Team,

I am facing similar issue, token is generated but still i am facing same issue (401 even with a valid token)

Below is my code

export const getAuthToken = async () => {
 const msal = require("@azure/msal-node");
//  const msal = require("@azure/msal-browser");

const msalConfig = {
    auth: {
        clientId: "ClientID",
        authority: Authority,
        clientSecret: "clientSecret",
        navigateToLoginRequestUrl: false
    },
    cache: {
        // your implementation of caching
        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
    },
    system: {
        loggerOptions: { /** logging related options */ }
    }
};

const cca = new msal.ConfidentialClientApplication(msalConfig); //const myMSALObj = new msal.PublicClientApplication(msalConfig); const usernamePasswordRequest = { //scopes: ["user.read"], username: EnvVariables.OrgAdminUserName, // Add your username here password: EnvVariables.OrgAdminPassword, // Add your password here }; // await myMSALObj.acquireTokenWithUsernamePassword const accessToken = await cca.acquireTokenByUsernamePassword(usernamePasswordRequest).then((response) => { console.log("acquired token by password grant in confidential clients"); return response.accessToken; }).catch((error) => { console.log(error); }); };

 //token passing to the axios Get call
 export const ExecuteGetRequest = async (getUrl: string) => {
const token = await getAuthToken();
return await axiosDefault.get(getUrl, {
    headers: {
        "Authorization": `Bearer ${token}`,
        "Content-Type": "application/json",
    },
});

};