azure-samples / ms-identity-javascript-v2 Goto Github PK
View Code? Open in Web Editor NEWVanillaJS sample using MSAL.js v2.x and OAuth 2.0 Authorization Code Flow with PKCE on Microsoft identity platform
License: MIT License
VanillaJS sample using MSAL.js v2.x and OAuth 2.0 Authorization Code Flow with PKCE on Microsoft identity platform
License: MIT License
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.
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>
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 ..."
}
...
username = currentAccounts[0].username;
showWelcomeMessage(username);
function showWelcomeMessage(account) {
// Reconfiguring DOM elements
cardDiv.style.display = 'initial';
welcomeDiv.innerHTML = Welcome ${account.**username**}
;
signInButton.setAttribute("onclick", "signOut();");
signInButton.setAttribute('class', "btn btn-success")
signInButton.innerHTML = "Sign Out";
}
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
@azure/[email protected]
Important: Please fill in your exact version number above, e.g. [email protected]
.
The sample application ms-identity-javascript-2 works if the app is registered manually but the provided Configuration.ps1 script will cause the app to fail with a CORS error. This is because the script registers a web app and not a Single Page Application.
When running the app with the incorrectly configured app registration, this error appears in the browser console:
Access to fetch at 'https://login.microsoftonline.com/common/oauth2/v2.0/token' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Version:
// Provide configuration values here.
// For Azure B2C issues, please include your policies.
Use the sample following the instructions in the AppCreationScripts folder, and running the supplied Configure.ps1 script before executing the application. In this case, the application logs the user in but fails to provide an access token. No information is displayed on the web page and the error above appears in the browser console.
The application should succeed and display information from the Microsoft graph
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
[email protected]
or @azure/[email protected]
The loginpopup call works fine and I can log in and get a accesstoken that I have tested in postman, and the token works fine as well. My problem is that when I make a call to graph like described in this tutorial I get an cors response from graph.
In the network tab the request is displayed as "200 OK"
But the object that is returned is of type "cors"
I copied the function from this tutorial.
.....
fetch(endpoint, options)
.then((response) => {
console.log(response); //This is the response that is "type: cors" from image above
})
.then((response) => callback(response, endpoint)) //Here the response is null/undefined
.catch((error) => console.log(error));
}
It is a SPA that I have registered in Azure AD like described here: https://docs.microsoft.com/en-us/graph/tutorials/javascript?tutorial-step=2
Maybe I have to do some more configuration to avoid the cors response??
We;re having a bit of a similar issue as described here. Currently when running the sample code with msal
version 1, which uses the implicit grant
flow, all works fine. However, when we try to use this sample that uses msal
2 it fails with the following error:
Updating the Azure App Regisrtation to use the "SPA Redirect URI" did not fix it:
We also tried to disable "implicit grant" in the hope that would resolve the CORSS issue but no luck. Is there still something we're missing here?
"dependencies": {
"@azure/msal-browser": "^2.0.0-beta.0",
"express": "^4.17.1",
"morgan": "^1.10.0",
"yargs": "^15.3.1"
},
@azure/[email protected]
See related issue to the msal library here: AzureAD/microsoft-authentication-library-for-js#2476
Version: 2.3.0
// Provide configuration values here.
// For Azure B2C issues, please include your policies.
Configure the app
Run the app
Try to log
Page stays the same as the error occurs in console.
Works with msal-browser 2.3.0
After login, the /token request fails as described in the related issue
The console logs this error:
msal.js:46 [AcquireTokenSilent] Unhandled Error:TypeError TypeError: Cannot read property 'length' of null
at Function.ServerTelemetryManager.maxErrorsToSend (index.es.js:7220)
at ServerTelemetryManager.generateLastRequestHeaderValue (index.es.js:7136)
at RefreshTokenClient.BaseClient.createDefaultTokenRequestHeaders (index.es.js:4286)
at RefreshTokenClient. (index.es.js:6357)
at step (index.es.js:394)
at Object.next (index.es.js:324)
at fulfilled (index.es.js:276)
In Readme.md line 55, The link to "For more information, see the documentation" in 1.3 of "Running the sample" is below,
but this is the Quickstart of Implict grant Flow.
https://docs.microsoft.com/azure/active-directory/develop/quickstart-v2-javascript
The following authentication code flow Quickstart also exists, so it is better to change.
https://docs.microsoft.com/azure/active-directory/develop/quickstart-v2-javascript-auth-code
Can I create Pull Requests to fix link?
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
[email protected]
or @azure/[email protected]
@azure/[email protected]
@azure/[email protected]
@azure/[email protected]
@azure/[email protected]
Hello, I have a question regarding "Oauth2AllowImplicitFlow" option in script "Configure.ps1"
Why -Oauth2AllowImplicitFlow is set to True? Is this different Implicit parameter?
$spaAadApplication = New-AzureADApplication -DisplayName "ms-identity-javascript-v2" `
-HomePage "http://localhost:3000/" `
-ReplyUrls "http://localhost:3000/" `
-IdentifierUris "https://$tenantName/ms-identity-javascript-v2" `
-AvailableToOtherTenants $True `
-Oauth2AllowImplicitFlow $true `
-PublicClient $False
Reading more MSAL.js 2.x apps do not support Implicit flow. I have tested authentication with and without and it worked in both ways:
Looking here it seems enabling Implicit will not support refresh token?
Access to fetch at 'https://login.microsoftonline.com/common/oauth2/v2.0/token' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
When using msal 1.3.0
there was no need to use the handleRedirectCallback()
method. The same is true for "@azure/msal-browser": "^2.0.0-beta.2"
but they handle things differently. In the new version authentication after a redirect does not happen automatically unless the page is refreshed a second time after the user authenticated on the Microsoft sign on page.
I have created a repo with the original code used by msal 1.3.0
in the master branch without issues and the new code used by msal 2
in the test-msal-2 branch that needs an extra refresh after the user is navigated back to the SPA to see that he is authenticated.
When I enable the code for authRedirectCallBack()
, which comes with the sample code, it keeps looping and refreshing the page all the time.
Thank you for having a look at what I did wrong or if this is an msal code issue.
In our project we upgraded from msal 1.3.0
to "@azure/msal-browser": "^2.0.0-beta.2"
. Everything seems to work fine except for the following TypeScript error message:
TS2322: Type 'string | undefined' is not assignable to type 'string'.
This comes from expecting the idTokenClaims.oid
to be a string:
const accountID = ref('')
export const setAccountID = () => {
const account = auth.getAccount()
if (account) {
accountID.value = account.idTokenClaims.oid
} else {
accountID.value = ''
}
}
In the old library this was not an issue. Should the property oid
not always be populated with a string when getAccount()
is successful as it identifies the logged on user?
Our current workaround is this:
const accountID = ref('')
export const setAccountID = () => {
const account = auth.getAccount()
if (account) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
accountID.value = account.idTokenClaims.oid!
} else {
accountID.value = ''
}
}
Being fairly new to TypeScript and web development in general I was wondering if I did something wrong or if there's a better way to handle this? Full code can be found here.
Hello,
I get this error:
ServerError: invalid_client: 7000218 - [2020-09-25 07:40:47Z]: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.
after removing "Mail.Read" from scope, because I don't need it:
scopes: ["Mail.Read", "openid", "profile", "User.Read"],
what could be the issue here?
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
Is the AccessToken supposed to be Version 1? I have followed the setup instructions for the app registration and the nodejs app. I have tried three different app registrations in my tenant and they all are returning an access token with v1 and an id token as v2. I found a comment here that explains to add a property to the manifest. However it is still generating v1 access tokens even after adding it.
AzureAD/microsoft-authentication-library-for-js#3414 (comment)
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
@azure/[email protected]
Important: Please fill in your exact version number above, e.g. [email protected]
.
[email protected]
Version:
// Provide configuration values here.
// For Azure B2C issues, please include your policies.
run npm install
localhost:3000/index.html
// Provide relevant code snippets here.
// For Azure B2C issues, please include your policies.
Get auth code back in the response but currently only getting authToken back instead.
Starting the server with "npm start" and calling the website results in an error (Error: ENOENT: no such file or directory, stat '.../ms-identity-javascript-v2/index.html').
The index.html can't be found, because it's not located in the root directory.
In server.js:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
should be:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/app/index.html'));
});
Doing so brings up the website but results in the following java-script errors:
Uncaught SyntaxError: Unexpected token '<' authConfig.js:1
Uncaught SyntaxError: Unexpected token '<' graphConfig.js:1
Uncaught SyntaxError: Unexpected token '<' ui.js:1
Uncaught SyntaxError: Unexpected token '<' authPopup.js:1
Uncaught SyntaxError: Unexpected token '<' graph.js:1
I have set authority to https://tenant.b2clogin.com/tenant.onmicrosoft.com/B2C_1A_Samssignup_signin
and I receive error 400 - AADSTS50049: Unknown or invalid instance.
I have also tried https://tenant.b2clogin.com/tenant.onmicrosoft.com/tfp/B2C_1A_Samssignup_signin
const msalConfig = {
auth: {
clientId: 'theclientid',
authority: 'https://tenant.b2clogin.com/tenant.onmicrosoft.com/B2C_1A_Samssignup_signin',
redirectUri: 'http://localhost:3000/',
},
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
}
};
Could anyone advise what's wrong?
I went through this sample and was able to access the Graph API.
I have a use case where I would like to protect my own web api by verifying the same Bearer token obtained with this flow.
My web api is using Node.js and I tried using passport-azure-ad middleware with the BearerToken strategy to protect my routes. However the token generated with PKCE flow does not seem to be compatible with the JWT verify logic from passport-azure-ad. I get an error saying the JWT has an invalid signature.
Are there any samples with Node.js where I could protect my own web apis using the Auth token obtained from this PKCE flow?
Please follow the issue template below. Failure to do so will result in a delay in answering your question.
@azure/[email protected]
Important: Please fill in your exact version number above, e.g. [email protected]
.
The sample doesnt work on IE 11, throws syntax error on ES6 comptatiblity for =>
Syntax error
Version:
// Provide configuration values here.
// For Azure B2C issues, please include your policies.
// Provide relevant code snippets here.
// For Azure B2C issues, please include your policies.
Tried babel polyfill , but tht doesnt seem to fix the issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.