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
Related
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".
We have a simple setup. Frontend web application gets a token from Azure AD. Frontend uses that token to authenticate requests to our backend .NET Core Web API. Both will be deployed as Azure Web Apps.
We have found a number of resources on this subject (Microsoft Docs, Blogs, Youtube) however, no matter what we try we are getting 401 Unauthorized errors when we send a request from the frontend to the backend.
Here is our setup:
We have 2 app registrations (one for the frontend, one for the backend)
In the Azure Portal I navigated to Azure AD -> App Registrations -> Backend App -> Expose an API -> Add Scope -> Filled out Form:
Then navigate to the Frontend app registration -> API Permissions -> Add a permission -> Add Access to API exposed in step 2.
Then select "Grant Admin Consent"
Onto the Frontend code:
On the Frontend we are using Microsoft's MSAL library. We have it configured as such:
// Frontend msal config
{
msalConfig: {
auth: {
clientId: "{frontend clientId}",
authority: "https://login.microsoftonline.com/{tenantId}/",
redirectUri: appUri,
postLogoutRedirectUri: appUri,
mainWindowRedirectUri: appUri
},
cache: {
cacheLocation: 'localStorage'
}
}
We send a login request:
// login.js
const request = {
scopes: [
"User.Read",
"api://{Backend Application Id From AzureAD App Registration}/access_as_user"
]
};
instance.loginPopup(request).catch(
(error) => {
console.log(error);
}
);
This works as expected and users are able to login and out of our frontend application.
We place our Azure AD provided token in our Authorization header in requests to our backend API (always returns a 401).
Backend code using Microsoft.Identity.Web (.NET 5):
// .Net appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "{backend clientId}",
"TenantId": "{tenantId}"
}
// Startup.cs
using Microsoft.Identity.Web;
ConfigureServices(IServiceCollection services)
{
//other services
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
//other services
}
Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// other config
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// other config
}
Any help on how to proceed would be greatly appreciated. We are testing locally right now, but once we get auth working we will be deploying the frontend & backend applications to Azure Web Apps.
UPDATE:
I can see in the 401 response headers that I am getting:
Bearer error="invalid_token", error_description="The signature is invalid"
Try to add "Audience" parameter into AzureAD section: "Audience": "api://{Client_Id}".
To receive more info about auth you can pass subscribeToJwtBearerMiddlewareDiagnosticsEvents: true into services.AddMicrosoftIdentityWebApiAuthentication and check your logs after unsuccessful request.
Also, you can decode(https://jwt.io) your token and check the payload section if there are required scope and audience.
It could be that token (provided to backend) is actually for Microsoft Graph(check appId here: shawntabrizi.com/aad/…). If "scp" is "openid profile User.Read email" and "aud" is "00000003-0000-0000-c000-000000000000" then you are using the wrong token for your backend. Ask Frontend to check the way how they are receiving a token.
Frontend guy put something like this into code, correct me if I am wrong:
public static msalInterceptorConfigFactory(): MsalInterceptorConfiguration {
const resourceMap = new Map<string, Array<string>>();
resourceMap.set(environment.apiUrl, [environment.scope]);
return {
interactionType: InteractionType.Redirect,
resourceMap
};
}
public static msalGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: [environment.scope]
}
};
}
# module.ts
providers: [
...
{
provide: MSAL_GUARD_CONFIG,
useFactory: msalGuardConfigFactory
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: msalInterceptorConfigFactory
},
...
],
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.
The browser app
I have a browser app (CRA, TypeScript) which is issuing, after successfully authenticating to Azure AD, a request to my API:
public async acquireAccessToken(): Promise<string | undefined> {
let res: AuthResponse | undefined = undefined;
const params: AuthenticationParameters = {
scopes: ["Users.Read"],
};
try {
res = await this.msal.acquireTokenSilent(params);
} catch (error) {
res = await this.msal.acquireTokenPopup(params);
}
return !res || !res.accessToken ? undefined : res.accessToken;
}
The one before is a utility method to get the access token to contact the API, the actual call is here:
const token = await acquireAccessToken();
const res = await fetch("/controller/test", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
});
console.log(res.text());
Where msal is the UserAgentApplication I am using as client to handle authentication and authorization in my browser app.
I have everything correctly set up in Azure where a registration app is used to represent the browser app, and another registration app is used to describe the API I need to contact.
The API
The API server is an ASP.NET Core 3.1 C# application whose Startup.cs is:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication();
app.UseAuthorization();
}
}
I have removed all the extra code and left the parts that concern auth.
The controller I am contacting is:
[ApiController]
[Route("controller")]
public class MyController : ControllerBase
{
[HttpGet("test/")]
[Authorize(Roles = "Admin")]
public async Task<string> Test()
{
return "Ok";
}
[HttpGet("test2/")]
[Authorize]
public async Task<string> Test2()
{
return "Ok";
}
[HttpGet("test3/")]
public async Task<string> Test3()
{
return "Ok";
}
}
Azure
The setup in Azure is simple: apart from the two app registrations for the browser app and the API, I have set in the browser app registration some custom roles and assigned them to some users.
I authenticate in the browser app using a user who has the Admin app role assigned to it.
The problem
When my client app tries to fetch data using these endpoints:
/controller/test3
/controller/test2
Everything is fine as one is unprotected and the other one uses a simple [Authorize].
However when trying to fetch from /controller/test, I get 403 (Forbidden).
Why can't I make the roles work?
More info
While debugging when fetching test2, I can see, in the controller, that this.User is present and there are several claims. Among those claims, I cannot see anything relating to the role. The access token I get has the following form:
{
"aud": "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"iss": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"iat": 1607034050,
"nbf": 1607034050,
"exp": 1607037950,
"acr": "1",
"aio": "AWQAm/8RAAAAH1j5tZzINJFi5fsMsgf99gcrnqQA+dOhWBpFmsgy3jsr0pFJ0AxvenqthiNLmRqKzqx6l+9SuLlRniAVCTOoqEE7MonnOetO3h7g1/Bm520rS0qiX/gpCCWYm/UwDlJ+",
"amr": [
"pwd"
],
"appid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"appidacr": "0",
"email": "xxx#xxx.xxx",
"idp": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"ipaddr": "xxx.xxx.xxx.xxx",
"name": "XXX",
"oid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"rh": "0.AAAASupzDdEU8EyBI3R6nFeJQHVORvhJZ2hDjJoEO5yPUcZ0AEU.",
"scp": "Users.Read",
"sub": "frR45l2dTAIyXZ-3Yn2mGNbBcBX9CrGisgJ4L8zOCd4",
"tid": "0d73ea4a-14d1-4cf0-8123-747a9c578940",
"unique_name": "xxx#xxx.xxx",
"uti": "39dk-rAAP0KiJN5dwhs4AA",
"ver": "1.0"
}
As you can see, no claim relating to roles.
But note that I can successfully get the role in the user token I get when authenticating. I need that claim to flow in the access token too when I use it to contact the API. How?
I found your problem:
You cannot set a custom role in the manifest of the browser application. You need to set a custom role in the manifest of the api application, and then assign the role to the user.
Then you need to use the auth code flow to get the access token, and the roles claims will be included in it.
In fact, the access token is created based on the intended recipient of your token (ie your api). If you want to access the api, you must have permissions or roles. In your question, this role is you Custom, when you grant the user a custom role of the api application, then the user has the role that can access the api application. (Note that this is not to assign the custom role of the client application to the user, because you need to access the api application, so the role of api application is required), then the user can log in to the client application and request a token from the api application. When you obtain the token and use the token to access the API, the API only needs to verify whether the user you log in has the role to access it.
This was a hard nut to crack and is not available in auth0 by default.
You can set roles in the Auth0 id token for OpenId, But you have to write a rule in auth0 dashboard:
Go To Rules section on your Auth0 dashboard and create a new rule:
and then use this code to add the user role claims to the id token, that will be returned in the JWT token claims:
function addRolesToAccessToken(user, context, callback) {
const namespace = 'http://schemas.microsoft.com/ws/2008/06/identity/claims';
const assignedRoles = (context.authorization || {}).roles;
let idTokenClaims = context.idToken || {};
let accessTokenClaims = context.accessToken || {};
idTokenClaims[`${namespace}/role`] = assignedRoles;
accessTokenClaims[`${namespace}/role`] = assignedRoles;
context.idToken = idTokenClaims;
context.accessToken = accessTokenClaims;
return callback(null, user, context);
}
Hope this helps! Cheers
Does anyone succeed in creating react native app with authorization in Azure Ad B2C?
I did it for the React web app and .Net Core API but, any library which I found for React Native does not work right know and I'm beginner in React Native. Please give me some hints tutorials or repos. I got stuck so bad.
I found an working React-Native library for Azure AD B2C. You can follow this article.
Here is the example:
import B2CAuthentication from "../auth-ad-js/ReactNativeADB2C";
import LoginView from "../auth-ad-js/LoginView";
const CLIENT_ID = "<provide your client id>";
export default class LoginScreen extends React.Component {
static navigationOptions = {
title: "Login"
};
render() {
const b2cLogin = new B2CAuthentication({
tenant: 'yourtenant.onmicrosoft.com',
client_id: CLIENT_ID,
client_secret: "<key set in application/key>",
user_flow_policy: "B2C_1_signupsignin",
reset_password_policy: 'B2C_1_password_reset',
token_uri: "https://saroujetmp.b2clogin.com/saroujetmp.onmicrosoft.com/oauth2/v2.0/token",
authority_host: "https://saroujetmp.b2clogin.com/saroujetmp.onmicrosoft.com/oauth2/v2.0/authorize",
redirect_uri: "https://functionapp120190131041619.azurewebsites.net/.auth/login/aad/callback",
prompt: "login",
scope: ["https://saroujetmp.onmicrosoft.com/api/offline_access", "offline_access"]
});
return (
<LoginView
context={b2cLogin}
onSuccess={this.onLoginSuccess.bind(this)}
/>
);
}
onLoginSuccess(credentials) {
console.log("onLoginSuccess");
console.log(credentials);
// use credentials.access_token..
}
}