MSAL React not showing roles, but they are in the token - reactjs

I'm trying to implement App Roles in a single-tenant React + .NET Core app of ours. This app has successfully been authenticating users via MSAL, so this is just an incremental addition.
I have the app roles set up in Azure AD, and I have the Authorize attribute with role restrictions working in the .NET back-end, but for some reason I'm unable to get the roles via the react MSAL library, even though when I manually decode the token I see them in there.
I was referring to this MS sample for my code. In my index.js, I have the following:
export const msalInstance = new PublicClientApplication(msalConfig);
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
Then, in the test page I have, I'm trying to access the roles array in this way (just as a test to print them out):
const TestComponent = () => {
const { instance } = useMsal();
useEffect(() => {
const activeAccount = instance.getActiveAccount();
setTokenRoles(activeAccount?.idTokenClaims?.roles);
// I've also tried:
// setTokenRoles(activeAccount?.idTokenClaims['roles']);
}, [instance]);
return (
<div>
ROLES: {JSON.stringify(tokenRoles)}
</div>
);
};
Unfortunately, tokenRoles is null. When I inspect entire idTokenClaims object, I see all the other claims, but no roles. However, I do see them in the token itself:
{
...
"roles": [
"Packages.Manage"
],
...
}
I'm really hoping to avoid manually decoding the token. There has to be a way to get it out of MSAL.

Jason Nutter's comments provided the answer, and in case this helps others I figured I'd give it a write-up.
Per the MS docs, I put the app roles on the back-end app registration. This is why I am able to have the Authorize(Roles = "Role") attribute work on the back-end. The reason I can see the roles in the access token is that the token is retrieved with the scope for that back-end API. But because I don't have those roles mirrored on the front-end app registration, I don't see anything in the id token.
There would be two options if you wanted to use the Azure app roles:
Mirror the app roles in the front-end app registration. In this way you'd have access to the roles in the id token. This sounds not good because I could foresee a typo or mismatch causing weird issues. I'm sure there might be a way using the Azure API to have a process that would sync the roles, but that's not worth it in my opinion.
Manually decode the access token on the front-end. The cleanest way I could think of to do this would be to create a roles context that would pull an access token, decode it, and store the roles for child components to refer to.
Another alternative would be to manage roles in the app itself. For us, the application in question is single-tenant, so there's not much need to do that. However, we do have a multitenant app we are moving to MSAL, and in that case we will already need to do things like validate that the tenant is authorized, and we will need more granular permissions than what this internal app needs, so we will likely have role system and have the front-end retrieve role and profile data from the back-end upon authentication through MSAL.
EDIT: What I ultimately did...
I did indeed keep the roles in the back-end only, then created a user context object that the front-end would retrieve. This user context includes the app roles, as well as other convenience data points like nickname, and is used by a React context and provider that I wrap my app in.

Related

Do you need firebase admin sdk when creating admin web?

I'm currently working on a small project using firebase. My team member is working on IOS and android while I'm trying to build a custom admin page using React.
In the app, users can signup with their phone and send a request for permission by attaching few documents.
I have to build an admin page to approve or deny these documents. For that I need to get list of all user from User Collection and view all the documents that was submitted and be able update user field 'isApproved' to true or false.
I was thinking of simply creating a new admin account directly in firebase and use that account to signin to admin page and perform the following actions (manipulate normal user info field). But I found out about firebase admin SDK. Do I need to use this in my case?
I may need to send push notifications to all users signed up and create user, update user, delete user account later on.
Give the situation should I use firebase admin SDK?
Can someone give me advice on how to set up the overall structure?
First things first, you should not use the Admin SDK on frontend. The Admin SDK has privileged access to all Firebase resources and does not follow any security rules either. You should always use Admin SDK in a secure environment like Firebase Cloud Functions or your own server.
I am not entirely sure what actions you need to perform while accepting/rejecting the documents. If you need to read/write a specific part of a database (which only a admin can access) then you can use Firebase security rules. You would have to add a Custom Claim to the admin user or store their UID in a database.
But if you need to do multiple things (maybe sending an email to user, doing some actions using 3rd party API), I'll recommend using a Cloud Functions with the Admin SDK.
How will that work?
You will have to create a Cloud Functions to accept/reject the documents.
When the admin accepts/rejects a document, you can pass details of that user (userID, document info and if the docs were accepted to the
cloud function) to the cloud function and process it over there.
The callable function may look like:
exports.verifyDocs = functions.https.onCall((data, context) => {
const {uid, token} = context.auth
if (!uid) return "Unauthorized"
if (!token.admin) return "Forbidden"
//The user is an admin
//Do database updates
//Any third party APIs
});
If you use callable functions, Firebase will automatically add auth info of the user calling that function. In the example above, I've assumed the user will have an admin custom claim but if you want to keep things simple based on UIDs you can do so by:
const adminUIDs = ["uid1", "uid2"]
if (!adminUIDs.includes(context.auth.uid)) return "Forbidden"
To call the function from your React app:
const verifyDocs = firebase.functions().httpsCallable('verifyDocs');
verifyDocs({ userID: "userID", text: messageText })
.then((result) => {
// Read result of the Cloud Function.
});
Any thing you pass in the function above will be available in your cloud functions in the 'data' parameter.

Sign Up with google account to Firebase-based Web App

It seems that Firebase only provides the way to sign IN an app with Firebase as BaaS. I'm not sure if I missed something; if so, let me know what I can use to sign UP a new account with Google but it should be different the manner we sign IN to it.
If there isn't a solution, please let me know how I can customize a Sign-up function.
This is what I am using now:
const provider = new firebase.auth.GoogleAuthProvider();
const signInWithGoogle = () => {
auth.signInWithPopup(provider);
};
Firebase Authentication is only focussed on authenticating a user, so for them to prove who they are by providing their credentials. For OAuth providers (such as Google) the user already provided those credentials elsewhere, so there is no need for them to sign up with Firebase Authentication. All that is needed is the credentials from the OAuth provider, as in the code you've shown.
If you want the users to register with your app, that is definitely possible but is separate from their signing in with Firebase Authentication. You'll want to put up a form where you gather the registration information about the user, and then store that in a database of your choice for later look up.

Blazor WASM calling Azure AAD secured Functions API

I have an Azure Functions API which uses Azure Active Directory authentication. I can test locally and deployed using a browser and curl calls in a process of:
Get a code
Use the code to get a token
Pass the token to authenticate and get the function result.
I now want to call this API from my Blazor WASM app but I'm sure there must be a nice MSAL call to do all the authentication but I cannot find any documentation on what that might be.
Does anyone have a code snippet to illustrate what needs to happen?
Further Information
My Azure Functions App and Blazor WASM client are not part of the same project and are hosted on different sub-domains of Azure hypotheticalapi.azurewebsites.net and hypotheticalweb.azurewebsites.net.
The web client application registration has API Permissions for the API and the API has an application registration which exposes itself with the scope that the client app has permissions for.
Again, the API and Web app work individually. I just don't seem able to get them to talk.
I have been following the "ASP.NET Core Blazor WebAssembly additional security scenarios" documentation but after several attempts I keep coming back to the error:
Microsoft.JSInterop.JSException: invalid_grant: AADSTS65001:
The user or administrator has not consented to use the application with ID 'e40aabb0-8ed5-4833-b50d-ec7ca4e07996' named 'BallerinaBlazor5Wasm'.
Send an interactive authorization request for this user and resource.
Even though I have revoked/deleted the client's permissions on the API, it has never repeated asking for consent. Is there a way I should clear the consent I previously gave? No idea how I might do that.
This GitHub Issue appears to be relevant.
I was stuck for the last two weeks with the same error code in the same setting: Blazor WASM talking to an AAD secured Azure Functions app.
What appeared to be a problem in my case was the scopes that I was listing in the http request when contacting AAD identification provider endpoints. Almost all examples I came across use Microsoft Graph API. There, User.Read is the scope that is given as an example. My first though was that even when I am contacting my own API I have to include the User.Read scope in the request because I was reasoning that this scope is necessary to identify the user. However, this is not the case and the only scope that you have to list when you call the authorize and token endpoints is the one that you exposed under the "Expose an API blade" in your AAD app registration.
I am using the OAuth2 authorization code in my example and not the implicit grant. Make sure that in the manifest of your API registration you have set "accessTokenAcceptedVersion": 2 and not "accessTokenAcceptedVersion": null. The latter implies the use of implicit flow as far as I know.
The scope the I exposed in my API is Api.Read. You can expose more scopes if you need but the point is that you only ask for scopes that you exposed.
I also have both following options unticked (i.e. no implicit flow). However, I tried with selecting "ID token" and it still worked. Note that the "ID token" option is selected by default if you let the Azure Portal create your AAD app registration from your function app Authentication blade.
Blazor code
Program.cs
This code has to be added.
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("{NAME}",
client => client.BaseAddress = new Uri("https://your-azure-functions-url.net"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{NAME}"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
// NOTE: no "api://" when providing the scope
options.ProviderOptions.DefaultAccessTokenScopes.Add("{you API application id}/{api exposed scope}");
});
appsetting.json
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{aad tenant id}",
"ClientId": "{application id of your blazor wasm app}",
"ValidateAuthority": true
}
GraphAPIAuthorizationMessageHandler.cs
Note that this class can have a different name. you'll then also reference a different name in Program.cs.
public class GraphAPIAuthorizationMessageHandler : AuthorizationMessageHandler
{
public GraphAPIAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://your-azure-functions-url.net" },
// NOTE: here with "api://"
scopes: new[] { "api://{you API application id}/{api exposed scope}" });
}
}
I hope this works. If not, let me know.
At least you need to get the access token, then use the token to call the function api. In this case, if you want to get the token in only one step, you could use the client credential flow, MSAL sample here, follow every part on the left to complete the prerequisites.
The following are the approximate steps(for more details, you still need to follow the sample above):
1.Create a new App registration and add a client secret.
2.Instantiate the confidential client application with a client secret
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
3.Get the token
string[] scopes = new string[] { "<AppId URI of your function related AD App>/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
4.Call the function API
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

How does AAD API Access delegate permission work?

I'm having a little trouble following how API Access delegate permissions work with azure active directory. I feel like i'm probably misunderstanding a key aspect of how AAD works.
Here is my set up
I have a Web Application let’s call it WebApp. I have created
an AAD for the Web Application and registered with a AAD App ID. Let’s
call it App ID A
I have a Web Api let’s call it ApiService. I have also created an AAD for it and registered with a AAD App ID. Let’s all it App ID B.
In AAD App ID A, I have updated the clicked on the API Access ->
Required Permissions -> Add (App ID B ; Web API) permissions
I’ve updated the manaifest in the AAD App ID B, to give consent to
knownClientApplications to include the client ID of the Web App
I’ve also enable oauth2AllowImplicitFlow to be true for both App’s
manifest.
What I’m trying to do is, A user signs into the web application sign. When it signs in, the user is able to acquire a token for the specific Web App App ID A. The user should be able to use that token and have access the Api Service with App ID B. I thought by configuring the whole API Access -> Required Permissions within the Web Application it would give me delegate permission with the logged in user to communicate with the Api Service WebApi.
When I examine the JWT token, I notice that there is a claim for Microsoft Graph, but not for the ApiService. Shouldn’t I be seeing a claim?
When I try to use the token, it reacts with a 404 authentication error.
Any advice appreciated,
Thanks,
Derek
UPDATE
In response to #joonasw
I actually looked at the example you wrote when i started.
https://joonasw.net/view/aspnet-core-2-azure-ad-authentication
In the example, the web application is initialized with:
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("OpenIdConnect").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = ctx =>
{
return Task.CompletedTask;
}
};
});
In the HomeController, there is code to retrieve the token for the graph api
private async Task<string> GetAccessTokenAsync()
{
string authority = _authOptions.Authority;
string userId = User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
var cache = new AdalDistributedTokenCache(_cache, _dataProtectionProvider, userId);
var authContext = new AuthenticationContext(authority, cache);
//App's credentials may be needed if access tokens need to be refreshed with a refresh token
string clientId = _authOptions.ClientId;
string clientSecret = _authOptions.ClientSecret;
var credential = new ClientCredential(clientId, clientSecret);
var result = await authContext.AcquireTokenSilentAsync(
"https://graph.microsoft.com",
credential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
From my understanding, when the user initially login to the web application it will trigger the OnAuthorizationCodeReceived() method where it will be using the clientId/clientSecret/resource of the web applicaiton. The token is stored in the distributed token cache under the key resource/client id.
In the example, GetAccessTokenAsync() is used to grab the token to access the graph API.
In my case, I was hoping to update that method to retrieve the token for the WebApi which has a different clientId/clientSecret/resoruce. In my case, it will AcquireTokenSilentAsync will throw an AdalTokenAcquisitionExceptionFilter because the token needed is not stored in the cache and in the AdalTokenAcquisitionExceptionFilter it will call try to reauthenticate
context.Result = new ChallengeResult();
which will redirect to the authentication page and then hits the AddOpenIdConnect() method. However, the openIdConnect is configured with the web app clientID/ClientSecret/Resource and will not store the new token properly. It will try to call GetAccessTokenAsync() again and the whole process will go in an infinite loop.
In the example, if you were to comment out the "Anthentication:resource" in app.settings, you will experience the same issue with the infinite loop. What happens is that you initially authenticate correctly with no resource specified. Then when you click on you try to get the token for microsoft graph which is a new resource, it can't find it in the cache and then tries to reauthenticate over and over again.
I also notice that the acquireAsyncAuthentication only returns a AuthenticationResult with a bearer tokentype. How would you get the refresh token in this case?
Any advice?
Thanks,
Derek
UPDATE (Solution)
Thanks to #jaanus. All you have to do is update the resource to the clientid of the web api and pass that into AcquireTokenSilentAsync. The web api id uri that you can get from the azure portal did not work.
Okay, so it seems there are multiple questions here. I'll try to make some sense of this stuff to you.
Adding the "Web App"'s client id to the "ApiService" knownClientApplications is a good idea.
It allows for consent to be done for both apps at the same time. This really only matters for multi-tenant scenarios though.
Now, your Web App will be acquiring access tokens at some point.
When it does, it must specify a resource parameter.
This parameter says to AAD which API you wish to call.
In the case of the "ApiService", you should use either its client id or Application ID URI (this is more common).
Depending on the type of your Web App, the access token is acquired a bit differently.
For "traditional" back-end apps, the Authorization Code Grant flow is usually used.
In this flow your back-end gets an authorization code after the user logs in, and your Web App can then exchange that code for the access token.
In the case of a front-end JavaScript app, you would use the Implicit Grant flow, which you have allowed (no need to enable it in the API by the way).
This one allows you to get access tokens directly from the authorization endpoint (/oauth2/authorize) without talking to the token endpoint as you usually have to.
You can actually get the access token right away after login in the fragment of the URL if you wish.
ADAL.JS makes this quite a lot easier for you if you are going in this route.
The reason you get the authentication error is because the access token is probably meant for Microsoft Graph API. You need to request an access token for your API.
An access token is always only valid for one API.

User Authentication on a mobile AngularJS App

I'd like to ask a question, which will likely have more than one solution but at this stage I don't know how to solve this problem.
I'm currently building a mobile application built in Angular/Ionic which is accessing the Woocommerce API, for which the app needs to pass a consumer key as well as secret in order to obtain product information as well as create products.
I assume that I do not have direct access to a database on my phone where I can store theses details to authenticate my app and point it at the right woocommerce store.
Even if I store these in a server based app, then my mobile app still needs to authenticate to the server based app in order to access the correct woocommerce store.
Could somebody point me into the right directions as to how developers go about this problem?
Usually, mobile authentication in Phonegap/Ionic looks like that:
You send Authentication request with Login/Pass or ApiKey.
Server response some Token.
You store Token in localStorage.
Send token with every ApiRequest.
Here is example how pass token to every API request if you already have some token.
angular.module('app').config(AppConfig);
AppConfig.$inject = ['$httpProvider'];
function AppConfig($httpProvider, $sceProvider) {
var token = simpleStorage.get('access_token'); // simpleStorage here is a js-plugin for using LocalStorage
if(token){
$httpProvider.defaults.headers.common['access-token'] = token;
}
}
here is a good article for angular-js authentication
https://medium.com/opinionated-angularjs/techniques-for-authentication-in-angularjs-applications-7bbf0346acec
If you need of high level security then you can use token based login
like: http://devcenter.kinvey.com/angular/tutorials/how-to-implement-safe-signin-via-oauth
or
you can use http://jwt.io

Resources