multiple_matching_tokens_detected exception in authContext.AcquireTokenAsync - c#

In mobile app, I have written ADAL authentication logic which is working for most of the users.
var authContext = new AuthenticationContext(authority);
var controller =
UIApplication.SharedApplication.KeyWindow.RootViewController;
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(controller);
var authResult = await authContext.AcquireTokenAsync(resource, clientId,
uri, platformParams);
Only 2-3 odd users are getting below exception.
{Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException: multiple_matching_tokens_detected: The cache contains multiple tokens satisfying the requirements. Call AcquireToken again providing more arguments (e.g. UserId) at Microsoft.IdentityModel.Client…}
What is the root cause of this issue ? Why it is coming for only few users? How to solve this?

This means that per a given tuple of authority/clientID/resource, ADAL's cache has more than one token matching those values. That typically happens when you acquire tokens using multiple accounts, which leads with multiple entries- all with the same authority/clientID/resource but different user identifiers.
If your app is meant to support multiple accounts at once, you need to call the overload of AcquireTokenAsync that requests a userID as well, so that you can eliminate the ambiguity.
If your app is meant to be single user, then you need to understand how you ended up with multiple users. I recommend revisiting the flow thoruhg which users enter account information in your app, and flag the steps where they can use different accounts. You can help users by passing your intended account identifier in AcquireTokenAsync- that will prepopulate the UX with the correct account name.

Related

How do I get accounts from Azure AD?

I have a nice Azure Active Directory set up with a dozen users. (All me!) So I have a Tenant ID, client ID and Client Secret.
I am also working on a simple console application that will function as a public client for this directory. This client also holds a list of usernames and passwords as this is just meant as a simple experiment. Not secure, I know. But I first need to understand how it works...
I do this:
IConfidentialClientApplication client = ConfidentialClientApplicationBuilder
.CreateWithApplicationOptions(options).Build();
And this creates my client app. Works fine.
I also get a token using "https://graph.microsoft.com/.default" and can use this to get all users as JSON:
string result = await GetHttpContentWithToken("https://graph.microsoft.com/v1.0/users",
token.AccessToken);
Although I might want it to be more user-friendly, JSON is fine for now.
How can I check if user is an authorized user?
And no, I don't want complex solutions that require various nuget packages. Just a plain and simple step-by-step explanation. I could probably Google this but I ended up with thousands of results and none were helpful... This should be easy, right?
[EDIT] I first wanted to get a list of users nut that failed because of a typo... (There's a dot before 'default'...)
It took some fooling around but it's not too difficult after all. There are a lot of libraries around Azure but it is all basically just a bunch of HTTP requests and responses. Even in a console application...
I started with making a PublicClientApplicationBuilder first:
var options = new PublicClientApplicationOptions()
{
ClientId = <**clientid**>,
TenantId = <**tenantid**>,
AzureCloudInstance = AzureCloudInstance.AzurePublic,
};
var client = PublicClientApplicationBuilder.CreateWithApplicationOptions(options).Build();
I can also create a ConfidentialClientApplication instead, but this allows me to log in interactively, if need be.
Next, set up the scopes:
var scopes = new List<string>() { "https://graph.microsoft.com/.default" };
As I wanted to log in using username and password, I have to use this:
var token = await client.AcquireTokenInteractive(scopes).ExecuteAsync();
But if I want to log in using code, I can also use this:
var password = new SecureString();
foreach (var c in <**password**>) { password.AppendChar(c); }
var token = await client.AcquireTokenByUsernamePassword(scopes, <**account**>, password).ExecuteAsync();
At this point, I'm authorized as the specified user. So, now all I need is to get whatever data I like, in JSON strings...
public static async Task<string> ExecCmd(string name, string url, string token)
{
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
string result = await GetHttpContentWithToken(url, token);
JObject json = JsonConvert.DeserializeObject(result) as JObject;
File.WriteAllText(name, json.ToString());
return result;
}
As I just want to read the data as text files, I just execute the action in using a specific and write it as formatted JSON to the file . So, using this simple method I can now use this:
await ExecCmd("Profile.txt", "https://graph.microsoft.com/v1.0/me/", token.AccessToken);
await ExecCmd("Groups.txt", "https://graph.microsoft.com/v1.0/groups", token.AccessToken);
await ExecCmd("Users.txt", "https://graph.microsoft.com/v1.0/users", token.AccessToken);
These will provide me with (1) the profile of the current user, (2) the AD groups and (3) the AD users. And probably a bit more...
I can use this ExecCmd to retrieve a lot more data, if I want to. But there's something else to keep in mind! For it all to work, you also need to configure the Azure application and make sure all access rights are assigned and approved!
So, in Azure AD you have to add an "App registration" and fiddle around with the settings... (The Azure experts are horribly shocked now, but when you want to learn, you'd just have to try and fail until you succeed...)
Also set "Default client type" to "public client" for the registered app.
In Azure, with the registered app, you also need to set the proper API permissions! Otherwise, you won't have access. And as I want access to Active Directory, I need to add permissions to "Azure Active Directory Graph". I can do this inside Azure or by using the scope when I call AcquireTokenInteractive(). For example, by using "https://graph.windows.net/Directory.Read.All" instead of "https://graph.windows.net/.default".
Once you've accessed a token interactively, you can also get more tokens using client.AcquireTokenSilent(). It gets a bit tricky from here, especially if you want to access a lot of different items. Fortunately, Active Directory is mostly the directory itself, groups, users and members.
Personally, I prefer to grant access from the Azure website but this is quite interesting.
Anyways, I wanted to authenticate users with Azure and now I know how to do this. It still leaves a lot more questions but this all basically answers my question...
I'll use this as answer, as others might find it useful...

How can I revoke Reference Tokens for blocking users?

I have an implementation of Identity Server 4 that uses Entity Framework Core for persistent storage and ASP.NET Core Identity for users management. Since this IDS will support public applications, we were asked to add a way of completely blocking users - which means not allowing them to sign in and remove their existing logins.
After long research, I've determined that IDS does not support anything like expiring Access Tokens, since that's not part of OpenID Connect. What strikes me as completely odd is that I switched a client to use Reference Tokens, which are correctly stored in the PersistedGrants table, but even clearing that table doesn't invalidate future requests, as the user is still authenticated both to the client application and to Identity Server itself.
Is there any store/service I can re-implement to block all access from a given logged in user?
You'll have to clear the cookies as well.
But you may want to investigate a different approach. Where IdentityServer is used as an authentication service and authorization is outsourced, like the PolicyServer.
That way you can opt-in authorization, making it less important that a user is still authenticated.
In the end, the problem was that someone had changed AccessTokenType from 1 back to 0 since another API didn't work, as it was configured for using Access Tokens rather than Reference Tokens. Thanks #VidmantasBlazevicius for pointing in the right direction of looking at logs for calls to the connect/introspect endpoint.
For reference, this is the code we ended up using (called by admin users, appropriately secured):
[HttpPut("{userId}/Block")]
public async Task<IActionResult> BlockUser(string userId)
{
var user = await _context.Users.FindAsync(userId);
if (user == null || !user.LockoutEnabled || user.LockoutEndDate > DateTime.Now)
{
return BadRequest();
}
var currentTokens = await _context.PersistedGrants
.Where(x => x.SubjectId == user.UserId)
.ToArrayAsync();
_context.PersistedGrants.RemoveRange(currentTokens);
var newLockOutEndDate = DateTime.Now + TimeSpan.FromMinutes(_options.LockOutInMinutes);
user.LockoutEndDate = newLockOutEndDate;
string updater = User.Identity.Name;
user.UpdateTime(updater);
await _context.SaveChangesAsync();
return NoContent();
}

IdentityServer4 - user permissions on the API

I'm currently setting up a new project with a Web API and a MVC UI (will eventually have a mobile UI as well which can talk to the same API).
So, I have the following plan:
User navigates to the MVC UI which takes them off to the IdentityServer4 server to log in or sign up
IdentityServer user is then added to the applications own user table in the database
Permissions can then be set on the user to limit their access
This means the identity server is just that, an identity server (and means I allow people to log in through Google, etc. without worrying about their roles and permissions).
So, to achieve the above, I need to check the user's permission on the API, NOT on the client (the client could be anything - web, phone app, JavaScript client, etc. so we can't rely on that to handle user permissions).
On the API, I have implemented a Permissionhandler and PermissionRequirement authorization policy. So, on the API controller or method, I can do something like: [Authorize(Policy = "CreateUser")]. It looks like I'll need to have a policy per system permission.
So in the authorisation handler I need to:
Get the current user's username
If they exist in the app database, check their permissions and auth or deny
If they don't exist in the app database, add them, then we can set their permissions later from the admin panel
This was going well up until I tried to request the user's username from the identity server. I understand I need to use the UserInfoClient to do that, but I can't figure out how to use the user's token/credentials to get their claims from the identity server or to at least get their User.Identity.Name.
Now I could just use the sub ID to add the user to the application database, but then whoever's managing the permissions would have no idea who that person is, so I need to use their email really.
Now in the MVC client, I can see User.Identity.Name without any problems, but in the API that value is null!?
So my question is: how on earth do I get the current user's Identity.Name, username or email from within the API?
Thanks.
Think I've cracked it now.
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Use the UserInfo endpoint to get the user's claims
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
var accessToken = await mvcContext.HttpContext.Authentication.GetTokenAsync("access_token");
var userInfoClient = new UserInfoClient(doc.UserInfoEndpoint);
var response = await userInfoClient.GetAsync(accessToken);
var claims = response.Claims;
}
This is detailed right at the bottom of https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies so according to Microsoft, it's the right way.
You did however spark this idea, thank you Win. I had been staring at this bloody thing for hours. All you need sometimes is someone else to say something, anything, and the curse is broken! :)
I am still open to a better way of achieving this, if anyone has one.
Cheers
I believe you already configure IdentityServerAuthentication in Web API similar like this -
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://UrlOfIentityServer",
RequireHttpsMetadata = false,
ApiName = "exampleapi"
});
...
}
When you make a web service call, you will need to pass the same token received from IdentityServer like this -
using (var httpClient = new HttpClient())
{
string accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
httpClient.BaseAddress = new Uri("http://UrlOfWebAPI");
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return await httpClient.GetStringAsync("api/clock/time");
}

Using Facebook API to access public group event data

I'm developing a public website and what I want to do is pretty straightforward, but I'm pulling my hair out trying to get everything working right.
I administer an open Facebook group and I want to display the public facebook events of this group on my website.
I can't seem to figure out how to setup my authentication so that I can access the event data. Here is my code for using my application to get an auth token:
var fb = new FacebookClientWrapper();
dynamic result = fb.Get("oauth/access_token", new
{
client_id = AppSettings.AppID,
client_secret = AppSettings.AppSecret,
grant_type = "client_credentials"
});
fb.AccessToken = result.access_token;
I know this works fine because I can access some information - for example, if I access a specific event by its ID, I can retrieve that information.
The problem occurs when I try to retrieve a list of events with fields within a date range:
[HttpGet]
public object GetEventDetails(string unixStartDateTime, string unixEndDateTime)
{
var parms = new Dictionary<string, object>();
parms.Add("fields", new[] { "id","name","description","start_time","venue" });
if (!String.IsNullOrEmpty(unixStartDateTime)) { parms.Add("since", unixStartDateTime); }
if (!String.IsNullOrEmpty(unixEndDateTime)) { parms.Add("until", unixEndDateTime); }
var eventsLink = String.Format(#"/{0}/events", AppSettings.GroupID);
return ObjectFactory.GetInstance<IFacebookClient>().Get(eventsLink,parms);
}
(I'm aware that even if this did succeed, the return value wouldn't be serializable - I'm not concerned about that quite yet).
This GET request returns the following message:
(OAuthException - #102) A user access token is required to request this resource.
So the message is quite clear: I need a user access token to get the data I've requested. The question is - what is the best way to do this? Can I give my application a certain permission to read this data? I've looked over all the permissions available to apps, but I don't see one that would do the trick.
I don't want to require people to log onto Facebook to look at public event data, and I love the idea of allowing people with no technical experience to essentially update the website content by posting Facebook events to the group. Right now, I have to duplicate anything they do.
I would think this kind of application would be very common, but no matter what I've read or tried, I can't quite find an example of the same thing that works.
From the docs at https://developers.facebook.com/docs/graph-api/reference/v2.0/group/events you need
A user access token for a member of the group with user_groups permission.
To avoid the hassle, you could create such an Access Token via the Graph Explorer and then store it in your application. Remember to exchange that Access Token to a long-lived one (https://developers.facebook.com/docs/facebook-login/access-tokens/#extending), and that you have to renew the Access Token every 60 days afterwards.

User Master Account to send SMS on behalf of a Sub Account

I'm developing a feature for our product that will allow users to send SMS messages via Twilio and we handle all of the account issues. We have our Master Account and all of our customers will be sub accounts under us.
In an effort to not have to keep track of 1000+ auth tokens, we decided to use our Master Account credentials to send the SMS message however we still want it to roll up under the sub account. According to Twilio, this shouldn't be an issue.
My problem is that there is very little (that I've found) documentation for the c# library they provide. I'm not sure if what I've done is the correct way to accomplish what I described above and since I'm on a trial account until the project is finished and can be rolled out to production I have no way of testing.
var twilio = new TwilioRestClient("Master SID", "Master Auth Token");
var response = twilio.SendSmsMessage(sender, recipient.ConvertToE164Format(), message, null, "Subaccount SID");
The comment on this overload isn't really clear on if passing the subaccounts SID here will send the message as if I had logged into their account and sent it.
// applicationSid:
// Twilio will POST SmsSid as well as SmsStatus=sent or SmsStatus=failed to
// the URL in the SmsStatusCallback property of this Application. If the StatusCallback
// parameter above is also passed, the Application's SmsStatusCallback parameter
// will take precedence.
The callback url will be the same across all accounts so I don't need/care about that value.
Short Version:
If I log in with my Master Account details but pass the subaccount SID in the SendSmsMessage() method, which account does the message come from?
Twilio support finally got back to me and confirmed my fears that this wouldn't work. I do have to pull the subaccount information down and reinstantiate the twilioRestClient with the new credentials which I was hoping I could get away with not doing. Just a few extra lines.
var twilio = new TwilioRestClient("Master SID", "Master Auth Token");
var subAccount = twilio.GetAccount("SubAccount SID");
var subAccountClient = new TwilioRestClient(subAccount.Sid, subAccount.AuthToken);
var response = subAccountClient.SendSmsMessage(sender, recipient.ConvertToE164Format(), message);
return response.Sid;

Categories

Resources