MSAL TokenAcquisition GetAccessTokenOnBehalfOfUser always fails because getaccounts is always empty - .net-core

I have been trying to use Azure AD MSAL and ADAL and have NEVER been able to retrieve a token. I have tried the ALL of the samples and keep getting to the same issue, token is created, added to the EF cache DB but when the tokenAcquisition object tries to retrieve it, no account is found and fails to get token.
I have read through most (if not ALL) of the issues on GitHub and SO. this seems to be working for others but looks like numerous people have the same issue and I have yet to see an answer other then pointing me to the samples I have tried.
Simple question for the moment - how do I get accounts from the IConfidentialClientApplication?
I have NEVER been able to get a single account or a list of accounts.
Create app object:
var app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(_applicationOptions)
.WithRedirectUri(currentUri)
.WithAuthority(authority)
.Build();
In GetAccessTokenOnBehalfOfUser:
IAccount account = await application.GetAccountAsync(accountIdentifier);
returns NULL
and
var accounts = await application.GetAccountsAsync();
returns an empty lists/IEnumerable.
I would expect to retrieve an account from
application.GetAccountAsync(accountIdentifier)
and a list from
await application.GetAccountsAsync();

OK, Finally found my issue.
The issue comes in using ASPNet identity logging into AzureAD as an external authority but using the identity to signin and create the claims principle.
I was mssing the AzureAD ObjectIdentifier from my claims. so the solution seems to be adding the ObjectIdentifier to the identity. I did this by using a ClaimsTransofrmation and looking for the auth type. If it was NOT Identity.Application it is from AzureAD and check to see if the User has the UserClaim and add it if not. This claim is then picked up and put in the principle's claims and under the covers, now the account is found....
if (principal.HasClaim(c => c.Type == SecurityConstants.ClaimTypes.ObjectId))
{
string oId = principal.FindFirstValue(SecurityConstants.ScpcClaimTypes.ObjectId);
var user = _usrMgr.FindByNameAsync(usrNm).Result;
List<Claim> claims = new List<Claim>(_usrMgr.GetClaimsAsync(user).Result);
if (!claims.Exists(c => c.Type == SecurityConstants.ScpcClaimTypes.ObjectId))
{
_usrMgr.AddClaimAsync(user, new Claim(SecurityConstants.ScpcClaimTypes.ObjectId, oId));
}

Related

Why there is no Rules tab inside Firebase Authentication?

Before adding a new user to Firebase Authentication should the name be qualified first:
The name must not be null
The name must not be empty
The name must contain one D character at least
Examples:
"Frank van Puffelen" => It is unacceptable because there is no D character
"Doug Stevenson" => It is acceptable
"Alex Mamo" => It is unacceptable because there is no D character
"Renaud Tarnec" => It is acceptable
"" => It is unacceptable because it is empty value
NULL => It is unacceptable because it is a null value
On the client side before adding a new user I check if the name follows the above qualifiers or not but the problem is if someone modifies the code.
The client side is not safe and I should check again on the server side if the name follows the rules or not.
So the question is why there is no Rules tab inside Firebase Authentication?
Since you want to check that the user name (the displayName I guess) follows the set of constraints listed at the top of your question you can take advantage of the new blocking Cloud Functions that "let you execute custom code that modifies the result of a user signing in to your app".
For example:
exports.checkDisplayName = functions.auth.user().beforeCreate((user, context) => {
if (!user.displayName || !user.displayName.toUpperCase().includes('D')) {
throw new functions.auth.HttpsError(
'invalid-argument', `displayName is invalid`); // adapt as follows
}
});
More details in the specific section of the doc, and in particular on how to catch and handle the error in your front-end.
The security rules concept is used to prevent unauthorized access to your Firebase resources such as database and storage. The displayName property is optional irrespective of which authentication method you chose.
If you require users to have a displayName then you can:
Check if user has displayName set every time they login. If not, then redirect them to a screen where they can set a name.
Disable sign-ups directly from Firebase client SDKs and use Firebase Cloud Functions with the Admin SDK to create user. No one else can reverse engineer the functions code so the validation on server side will ensure a user has displayName.
exports.createUser = functions.https.onCall((data, context) => {
const { displayName, email, password } = data;
// check if displayName is valid
// if not return error
// create user using Admin SDK if all data provided is valid
return { message: "User created" };
});
Then you can login your user with the Client SDK using signInWithEmailAndPassword()
In case you are using any Auth providers e.g. Google, Facebook and the display name is unavailable for some reason, then you'll need some custom logic as explain in method 1 above.
Either of the solution does not prevent users from using updateProfile() APIs so make sure have some validation on client end as well and report such events somewhere in the database where you can monitor it.

MobileServiceUser returns wrong UserId

I have an existing user in my Xamarin Forms app whose Details are as follows:
First Name: Jim
Last Name: Smith
ProviderLoginId = jsmith#google.com
OAuth Provider: Google
When I first create this user, I use the following method to authenticate against google as follows:
result = await AuthenticationClient.AcquireTokenInteractive("https://***.onmicrosoft.com/profile.read/profile.read")
.ExecuteAsync();
JObject objToken = new JObject();
objToken.Add("access_token", result.IdToken);
MobileServiceUser user = await App.syncMgr.CurrentClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, objToken);
Now, I take the UserId property from the MobileServiceUser user variable and store it into my User Table as primary key.
Subsequently, I create another new user with the following details:
First Name: Jim
Last Name: Smith
ProviderLoginId = jsmith#hotmail.com
OAuthProvider: Microsoft
When I create the user using the same steps as above, I get the same UserId back from Azure. Obviously, I am not able to store it in my User table for the second user. This only happens when the first name and the last name are the same regardless of which ProviderLoginId was used (whether it was Google OAuth or Microsoft OAuth).
Should I not be getting a unique UserId in each case? Since the bug surfaces only when the first and last names are same, I am assuming it is some sort of a bug.
Any and all help is appreciated.
You are using the older client, which is no longer supported.
You should be submitting the access token to the service, not the IdToken. You haven't stated what the AuthenticationClient is, but hopefully it is ADAL (since the older service doesn't work with MSAL).
The newer client and service completely abstracts the authentication code, allowing you to use whatever authentication mechanism you like and just dealing with standard OAuth2/OIDC and bearer tokens (Authorization header) instead of the non-standard X-ZUMO-AUTH header that was used in the past. My recommendation is to upgrade the client and server.

ADFS 2016 On behalf of flow : cannot get any user informations

I'm trying to implement the "on behalf of" flow in an application using ADFS 2016 as STS. As a reference, I look at this Microsoft tutorial (https://learn.microsoft.com/en-ca/windows-server/identity/ad-fs/development/ad-fs-on-behalf-of-authentication-in-windows-server). It's working as it should, I can login into my web application and then use my original access token in UserAssertion to generate a new access token with the proper audience to call my API BUT I found absolutely no way to include any user informations (sub, name, email, upn etc.) into the access token for my API, even if I set claim rules into my ADFS configurations for the API.
I checked the communication between my app and adfs using Fiddler and everything looks like the informations in the tutorial. See the screen shot of the "on behalf of" request below :
Here's the resulting access token :
Finally, here's the code I use to generate my new access token :
private async Task<string> GetAccessToken(ClaimsPrincipal user, string originalAccessToken)
{
var authority = "[authority]";
var context = new AuthenticationContext(authority, false);
string userName = user.FindFirstValue("upn");
var userAssertion = new UserAssertion(originalAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer",userName);
var cc = new ClientCredential("https://localhost:44387/", "[client_secret]");
var result = await context.AcquireTokenAsync("https://localhost:44339/", cc, userAssertion);
return result.AccessToken;
}
Have you struggle with that scenario and if yes, did you find a way to fix this ?
Thanks
I've only used the Microsoft On Behalf Of flow with Azure AD and not ADFS, but it looks like you need to send a more detailed scope in your User Info request.
Maybe try sending 'openid profile email', to indicate that you want that type of detail, as in Section 17 of my blog post. Of course this assumes that this type of data has been registered for all users.
TROUBLESHOOTING
Looks like one of these will be the cause:
A suboptimal Microsoft library that does not allow you to send the required scope
Or ADFS 2016 perhaps lacks the scope features that work correctly in Azure AD
I would concentrate on making extra sure you are sending the correct form URL encoded request message, using a tool such as curl, Postman or a plain C# HttpClient. Here is the code I used to send the correct scope - using an open source library rather than a Microsoft one:
Sample NodeJS Code
If you can get the scope sent correctly then you should have a resolution either way:
Either you get the correct data and can update your code
Or the behaviour you want is not supported by ADFS
Good luck ...

Firebase work-around for missing tenantId in user().onCreate() response?

I'm trying to use multi-tenancy in firebase. All has gone well, except that after creating a user, the returned user missing the tenantId. I'm trying to create a custom claim with hasura so that I can tell the user's tenant:
exports.processSignUp = functions.auth.user().onCreate((user) => {
const customClaims = {
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": ["user"],
"x-hasura-user-id": user.uid,
"x-hasura-tenant-id": user.tenantId,
},
};
However, there seems to be a bug where the onCreate request does not properly return the tenantId, as acknowledged here: https://issuetracker.google.com/issues/160809045
Unfortunately I can't fetch the user again to try and grab the tenantId there because the user is scoped to the tenant. It won't find the user given the user's id because I haven't set the tenant.
Does anyone more familiar with firebase/google identity management know of a work-around until that bug is fixed?
I already meet familiar with this case. Using Google identity tenant and Hasura too
And I can check user.tenantId as normally. I see this post 1 year ago. But still want to give you comment with a familiar case

Twitterizer.TwitterUser.IsFollowing returns null

I am using Twitterizer to integrate Twitter into my ASP.NET app. After authenticating the user I am using following code to see if the authenticated user is following a specific user:
Twitterizer.TwitterUser followed = new Twitterizer.TwitterUser();
followed.ScreenName = tw_current_like;
bool? Following = followed.IsFollowing;
However Following.HasValue is always false. Am I missing something?
As I stated on the forums. Instantiating TwitterUser will not fetch any data from Twitter.
Also, the Twitter API is spotty in how/when it supplies the following indicator. You can use TwitterFriendship.Show(OAuthTokens, decimal/string) to query details about the relationship between the authenticated user and another user.

Resources