Auth0 connecting to Azure AD is not returning my state params - azure

Using auth0, I'm following this tutorial about connecting a multi-tenant saas to Azure AD:
https://auth0.com/docs/tutorials/building-multi-tenant-saas-applications-with-azure-active-directory
But, since my case is a bit different, I need it to pass some parameters when the user logs in. For the other connections, I'm able to set options.authParams.state = "..." and this state is sent to my callbackURL.
But, using Azure AD connection, my state variable is not correct. It's empty when I receive the callback.
I'm including the new Azure AD button according to the tutorial:
lock.once('signup ready', function () {
var link = $('<div class="a0-zocial a0-icon a0-waad" href="#">'
+ '<span>Azure AD</span></div>');
link.on('click', function () {
lock.getClient().login({
connection: 'seedtec-onmicrosoft-com'
});
});
var iconList = $(this.$container).find('.a0-iconlist');
iconList.append(link);
});
And sending my state through options:
var options = {
container: 'root'
, callbackURL: 'http://.../LoginCallback.ashx'
, responseType: 'code'
, dict: 'pt'
, socialBigButtons: false
, authParams: {
state: state
, scope: 'openid profile'
, company_id: $scope.companyId
}
, mode: mode
, callbackOnLocationHash: true
};
lock.show(options);
Do you know what is wrong in that?
Thank you!
PS: I'm using angular libraries for auth0.

I actually found out the solution to this. It was quite simple, but it's not well documented by auth0.
When calling lock.getClient().login(...) I had to pass the authparams again like this:
lock.getClient().login({
connection: 'myconn-onmicrosoft-com'
, scope: 'openid profile'
, company_id: 'blah...'
, state: 'blah blah...'
});

Related

Node.JS PowerBI App Owns Data for Customers w/ Service Principal (set "config.json" from a table in my database)

I'm attempting to refactor the "Node.JS PowerBI App Owns Data for Customers w/ Service Principal" code example (found HERE).
My objective is to import the data for the "config.json" from a table in my database and insert the "workspaceId" and "reportId" values from my database into the "getEmbedInfo()" function (inside the "embedConfigServices.js" file). Reason being, I want to use different configurations based on user attributes. I am using Auth0 to login users on the frontend, and I am sending the user metadata to the backend so that I can filter the database query by the user's company name.
I am able to console.log the config data, but I am having difficulty figuring out how to insert those results into the "getEmbedInfo()" function.
It feels like I'm making a simple syntax error somewhere, but I am stuck. Here's a sample of my code:
//----Code Snippet from "embedConfigServices.js" file ----//
async function getEmbedInfo() {
try {
const url = ;
const set_config = async function () {
let response = await axios.get(url);
const config = response.data;
console.log(config);
};
set_config();
const embedParams = await getEmbedParamsForSingleReport(
config.workspaceId,
config.reportId
);
return {
accessToken: embedParams.embedToken.token,
embedUrl: embedParams.reportsDetail,
expiry: embedParams.embedToken.expiration,
status: 200,
};
} catch (err) {
return {
status: err.status,
error: err.statusText,
}
};
}
}
This is the error I am receiving on the frontend:
"Cannot read property 'get' of undefined"
Any help would be much appreciated. Thanks in advance.
Carlos
The error is because of fetching wrong URL. The problem is with the config for the Service Principal. We will need to provide reportId, workspaceId for the SPA and also make sure you added the service principal to workspace and followed all the steps from the below documentation for the service principal authentication.
References:
https://learn.microsoft.com/power-bi/developer/embedded/embed-service-principal

Graph API does not accept my application permissions' scope

Edit:
The problem was a simple typo in the Header. You're probably wasting your time here
In essence, I have the same problem as described here. It's a somewhat different usecase and I'll try to provide as much context as I can in the hopes that someone will be able to solve the problem.
So, this has to do with Azure, which seems to be an Alias for "Crazy problem generator". My apologies.
I'm trying to write a Service in NodeJS which has the purpose of synchronizing another app's database with data from Azure.
For that reason, I'm using msal-node's Client Credential Flow as described here.
I find their comment // replace with your resource quite ridiculous, as I have not found a single full example online that specifies the format that should be used.
Intuitively, I would use something like
['GroupMember.Read.All']
//or
['https://graph.microsoft.com/GroupMember.Read.All']
Unfortunately, this does not work. Luckily, I get an error that describes the problem (even if only when this is the only scope I use, othewise the error is garbage):
{
// ...
errorMessage: '1002012 - [2022-05-23 11:39:00Z]: AADSTS1002012: The provided value for scope https://graph.microsoft.com/bla openid profile offline_access is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier (application ID URI).\r\n'
}
Okay, let's do that:
['https://graph.microsoft.com/GroupMember.Read.All/.default']
Now, the app actually performs a request, but unfortunately, I get
{
// ...
errorCode: 'invalid_resource',
errorMessage: '500011 - [2022-05-23 11:42:31Z]: AADSTS500011: The resource principal named https://graph.microsoft.com/GroupMember.Read.All was not found in the tenant named <My company name, not an ID as shown in some places>. This can happen if the application has not
been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.\r\n' +
'Trace ID: <some id>\r\n' +
'Correlation ID: <some id>\r\n' +
'Timestamp: 2022-05-23 11:42:31Z - Correlation ID: <some id> - Trace ID: <some id>',
}
And yet it is there
And I am able to get a token for the .default scope. That's just not good for anything.
The important parts of the actual code:
import fetch from 'isomorphic-fetch';
import * as msal from '#azure/msal-node';
// got env variables using dotenv package
// this is Typescript
const msalConfig = {
auth: {
clientId: process.env.OAUTH_APP_ID!,
authority: process.env.OAUTH_AUTHORITY!,
clientSecret: process.env.OAUTH_APP_SECRET!
},
system: {
loggerOptions: {
loggerCallback(loglevel: any, message: any, containsPii: any) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const msalClient = new msal.ConfidentialClientApplication(msalConfig);
const allCompanyMembersGroupId = '<some id>';
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
msalClient.acquireTokenByClientCredential(tokenRequest).then(response => {
console.log('Got token:', response);
fetch(`https://graph.microsoft.com/v1.0/groups/${allCompanyMembersGroupId}/members`, {
method: 'GET',
headers: {
Authority: `Bearer ${response!.accessToken}`
}
}).then((res: any) => {
console.log('Got response:', res);
})
});
As mentioned, the request isn't performed with my GroupMember.Read.All scope. With the default scope, I get a 401 unauthorized error.
So, these are my questions:
How to fix this?
Okay, if it you don't know how to fix it, what is the exact format required for the scope? Is the prefix https://graph.microsoft.com correct, even for my specific app?
Is this the correct library to use, or is this just broken code or not intended for such use? The other question I linked to above mentions that requests were successful using Postman, just not this lib...
Thanks heaps for any advice!
It's a problem with the headers. It should be Authorization instead of Authority.
The error is in the following variable:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/GroupMember.Read.All/.default']
};
the correct one would be:
const tokenRequest = {
scopes: ['https://graph.microsoft.com/.default']
};
Note: When using Client Credentials flow, it is a must to specify the scope string tailing with "/.default".
For eg:
If you are trying to get an access-token using client credentials
flow for MS Graph API, the scope should be
"https://graph.microsoft.com/.default".
If you are trying to get an
access-token using client credentials flow for a custom API which is
registered in AAD, the scope should be either
"api://{API-Domain-Name}/.default" or
"api://{API-Client-Id}/.default".

UI testing using Cypress with authentication to Azure AD using ADFS

These are my notes for how to UI test an Azure AD single page app using MSAL.js and ADFS (in our case on-premise) and the schema associated with the process of token creation and local storage.
From the tutorial: "It uses the ROPC authentication flow to acquire tokens for a test user account, and injects them into browser local storage before running the tests. This way MSAL.js does not attempt to acquire tokens as it already has them in cache."
After watching the awesome video here:
https://www.youtube.com/watch?v=OZh5RmCztrU
...and going through the repo here:
https://github.com/juunas11/AzureAdUiTestAutomation
I was stuck trying to match my use of on-premise ADFS with MSAL.js 2.0 and session store, with that of the above tutorial and code. So if you are using the link to Azure ending with /adfs/oauth2/token ( as opposed to oAuth /oauth2/v2.0/token ) - then follow the below!!
MOST of the changes I made were from auth.js: https://github.com/juunas11/AzureAdUiTestAutomation/blob/main/UiTestAutomation.Cypress/cypress/support/auth.js
Simply follow the tutorial and copy in that content, then change the following:
const environment = ''; (mine was corporate domain NOT login.windows.net)
for the Account entity (const buildAccountEntity) use:
authorityType: 'ADFS',
...and REMOVE the line: clientInfo: "",
for the Access Token entity: (const buildAccessTokenEntity):
...ADD the line: tokenType: 'bearer',
ADD a new function for the Refresh Token (new) entity:
const buildRefreshTokenEntity = (homeAccountId: string, accessToken: string) => {
return {
clientId,
credentialType: 'RefreshToken',
environment,
homeAccountId,
secret: accessToken,
};
};
next I had to MATCH my sessionStorage TOKEN by running it locally using VS Code and logging in then reverse-engineering the required KEY-VALUE pairs for what was stored (results are in next code block!).
Specifically I kept case-sensitivity for 'home account', I blanked-out some values, and had to add in the RefreshToken part, and mine used Session Storage (not local storage), and match the extended expires with the same value (based on my sample run through only):
const injectTokens = (tokenResponse: any) => {
const scopes = ['profile', 'openid'];
const idToken: JwtPayload = decode(tokenResponse.id_token) as JwtPayload;
const localAccountId = idToken.sub; // in /oauth2/v2.0/token this would be: idToken.oid || idToken.sid; however we are using /adfs/oauth2/token
const realm = ''; // in /oauth2/v2.0/token this would be: idToken.tid; however we are using /adfs/oauth2/token
const homeAccountId = `${localAccountId}`; // .${realm}`;
const homeAccountIdLowerCase = `${localAccountId}`.toLowerCase(); // .${realm}`;
const usernameFromToken = idToken.upn; // in /oauth2/v2.0/token this would be: idToken.preferred_username; however we are using /adfs/oauth2/token
const name = ''; // in /oauth2/v2.0/token this would be: idToken.name; however we are using /adfs/oauth2/token
const idTokenClaims = JSON.stringify(idToken);
const accountKey = `${homeAccountIdLowerCase}-${environment}-${realm}`;
const accountEntity = buildAccountEntity(homeAccountId, realm, localAccountId, idTokenClaims, usernameFromToken, name);
const idTokenKey = `${homeAccountIdLowerCase}-${environment}-idtoken-${clientId}-${realm}-`;
const idTokenEntity = buildIdTokenEntity(homeAccountId, tokenResponse.id_token, realm);
const accessTokenKey = `${homeAccountIdLowerCase}-${environment}-accesstoken-${clientId}-${realm}-${scopes.join(' ')}`;
const accessTokenEntity = buildAccessTokenEntity(
homeAccountId,
tokenResponse.access_token,
tokenResponse.expires_in,
tokenResponse.expires_in, // ext_expires_in,
realm,
scopes,
);
const refreshTokenKey = `${homeAccountIdLowerCase}-${environment}-refreshtoken-${clientId}-${realm}`;
const refreshTokenEntity = buildRefreshTokenEntity(homeAccountId, tokenResponse.access_token);
// localStorage was not working, needs to be in sessionStorage
sessionStorage.setItem(accountKey, JSON.stringify(accountEntity));
sessionStorage.setItem(idTokenKey, JSON.stringify(idTokenEntity));
sessionStorage.setItem(accessTokenKey, JSON.stringify(accessTokenEntity));
sessionStorage.setItem(refreshTokenKey, JSON.stringify(refreshTokenEntity));
};
Lastly, in the login function I used the /adfs link as we use on-premise ADFS and MSAL.js v2.0 and did NOT need that client_secret:
export const login = (cachedTokenResponse: any) => {
let tokenResponse: any = null;
let chainable: Cypress.Chainable = cy.visit('/'); // need to visit root to be able to store Storage against this site
if (!cachedTokenResponse) {
chainable = chainable.request({
url: authority + '/adfs/oauth2/token', // was this '/oauth2/v2.0/token',
method: 'POST',
body: {
grant_type: 'password',
client_id: clientId,
// client_secret: clientSecret,
scope: ['profile openid'].concat(apiScopes).join(' '),
username,
password,
},
form: true,
});
***... MORE CODE OMITTED***
finally I ran using VSCode terminal 1 (yarn start) then terminal 2 (yarn run cypress open)
TYPESCRIPT use:
rename all files from .js to .ts
update tsconfig to include the cypress type on this line:
"types": ["node", "cypress"],
Now when I run Cypress I can navigate around my site and I am authenticated!! Hope this helped you save an hour or two!!

Cypress SSO Azure without Client_secret

I'm struggling to solve an issue that I got on trying to login via Single Sign On from Microsoft Azure using Cypress. It is possible to do it without using the Client_Secret? How can I do it?
I've been spending more than a week trying to solve this situation...
I'm a junior on this, so if you could help-me I would be very grateful.
Thanks a lot,
Yes, you can. Navigate to your AD App in the portal -> Authentication -> set Allow public client flows to Yes like below.
Then in the blog, in step Cypress utility for mimicking react-adal, it uses the client credential flow, there is a comment post by #Bryce Kolton under the blog, he uses the ROPC flow, in this flow, you could use it without Client_Secret via a public client App as you changed above(Allow public client flows), just refer to it.
/* eslint-disable no-underscore-dangle */
import { AuthenticationContext } from ‘react-adal’;
import { azTenantId, azClientId } from ‘../../server/config/environment’;
// Need to get data points from server’s environment, not src
const adalConfig = {
tenant: azTenantId,
clientId: azClientId,
cacheLocation: ‘localStorage’,
replyUrl: ‘/’,
endpoints: {
api: ”,
},
};
const authContext = new AuthenticationContext(adalConfig);
export default async function doLogin() {
// getCachedToken also does an expiration check so we know for sure the tokens are usable
if (
!authContext.getCachedToken(adalConfig.endpoints.api)
|| !authContext.getCachedToken(adalConfig.clientId)
) {
const response = await cy.request({
method: ‘POST’,
url:
‘https://login.microsoftonline.com/mercedesme.onmicrosoft.com/oauth2/token’,
// qs: { ‘api-version’: ‘1.0’ }, // uncomment if your consuming resource expects the ‘aud’ to have a prefix of ‘sn:’
headers: {
‘cache-control’: ‘no-cache’,
‘content-type’:
‘multipart/form-data; boundary=—-WebKitFormBoundary7MA4YWxkTrZu0gW’,
},
form: true,
body: {
grant_type: ‘password’,
response_type: ‘code’,
client_id: ‘[[yourappsclientid]]’,
username: ‘[[yourtestuzseremail]]’,
password: ‘[[yourtestuserpassword]]!’,
scope: ‘openid’,
resource: ‘[[some-resource-id]]’,
},
});
// Store the token and data in the location where adal expects it
authContext._saveItem(authContext.CONSTANTS.STORAGE.IDTOKEN, response.body.access_token);
authContext._saveItem(
authContext.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + adalConfig.endpoints.api,
response.body.access_token,
);
authContext._saveItem(
authContext.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY + adalConfig.clientId,
response.body.access_token,
);
authContext._saveItem(
authContext.CONSTANTS.STORAGE.EXPIRATION_KEY + adalConfig.endpoints.api,
response.body.expires_on,
);
authContext._saveItem(
authContext.CONSTANTS.STORAGE.EXPIRATION_KEY + adalConfig.clientId,
response.body.expires_on,
);
authContext._saveItem(
authContext.CONSTANTS.STORAGE.TOKEN_KEYS,
[adalConfig.clientId].join(authContext.CONSTANTS.RESOURCE_DELIMETER)
+ authContext.CONSTANTS.RESOURCE_DELIMETER,
);
}
}
To use the ROPC flow successfully, make sure your scenario meets the requirements below, e.g. your user account is not MAF-enabled.

React-Native authentication with Azure AD

I am building an app in react-native and need to authenticate to a server via Azure AD. I have tried to google this but surprisingly there is not much content related to this.
I have found this library 'react-native-azure-ad' but not so much documentation about it. I dont understand how to use it.
My main questions is: Where do I need to place my server's url when using this library, in order to authenticate to it?
Thanks for any suggestions!
EDIT:
Here is my code:
import { ReactNativeAD, ADLoginView } from 'react-native-azure-ad'
const CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
class LandingView2 extends React.Component {
constructor(props) {
super(props)
this.AzureADContext = {
client_id: CLIENT_ID,
// Optional
redirect_url: 'http://localhost:8080',
// Optional
authority_host: 'xxxx',
// Optional
tenant: 'common',
// Optional
prompt: 'none',
// Optional
login_hint: 'user#domain.com',
// This is required if client_id is a web application id
// but not recommended doing this way.
client_secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
resources: [
'https://graph.microsoft.com',
'https://outlook.office365.com',
// ... more resources
]
}
}
render() {
new ReactNativeAD({
client_id: CLIENT_ID,
resources: [
'https://outlook.office365.com'
]
})
return <ADLoginView
context={ReactNativeAD.getContext(CLIENT_ID)}
onSuccess={this.onLoginSuccess.bind(this)} />
}
onLoginSuccess(credentials) {
console.log(credentials['https://outlook.office365.com'].access_token)
// use the access token ..
}
}
You need to create two applications in AAD.
a native application for your app
another application for your server/API
You can then use the clientId or AppID URI of the API as the "resource" for the code shown in the question. All the other parameters are taken from your client app.
If everything is successful, you will get an access token which you can then use as a bearer token when calling your API.
credentials['https://yourtenant.onmicrosoft.com/yourapiname'].access_token

Resources