How do I get accounts from Azure AD? - c#

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...

Related

How to authenticate with OAuth to access EWS APIs

My web service is currently doing basic username/password authentication in order to subscribe the exchange user for receiving the events (like new mail event etc) like below:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new NetworkCredential(username, password)
};
var subscription = service.SubscribeToPushNotifications(
new[] { inboxFolderFoldeID },
new Uri("some post back url"),
15,
null,
EventType.NewMail,
EventType.Created,
EventType.Deleted,
EventType.Modified,
EventType.Moved,
EventType.Copied);
Now, I am supposed to replace the authentication mechanism to use OAuth protocol. I saw some examples but all of them seem to be talking about authenticating the client (https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396) but nowhere I was able to find an example of how to authenticate an exchange user with OAuth protocol. Any code sample will help a lot. Thanks.
It's not clear what you mean with 'web service' and how you currently get the username and password. If that is some kind of website where the user needs to login or pass credentials, then you'll have to start an OAuth2 grant from the browser as in redirecting the clients browser to the authorize endpoint to start implicit grant or code grant. The user will be presented a login screen on the OAuth2 server (and not in your application), once the user logs in a code or access token (depending on the grant) will be returned to your application which you can use in the ExchangeService constructor.
If that 'web' service is some service that runs on the users computer you can use one of the methods described below.
Get AccessToken using AuthenticationContext
The example seems to be based on an older version of the AuthenticationContext class.
The other version seems to be newer, also the AcquireToken is now renamed to AcquireTokenAsync / AcquireTokenSilentAsync.
No matter which version you're using, you will not be able to pass username and password like you're doing in your current code. However, you can let the AcquireToken[Async] method prompt for credentials to the user. Which, let's be honest, is more secure then letting your application deal with those user secrets directly. Before you know, you'll be storing plain text passwords in a database (hope you aren't already).
In both versions, those methods have a lot of overloads all with different parameters and slightly different functionality. For your use-case I think these are interesting:
New: AcquireTokenAsync(string, string, Uri, IPlatformParameters) where IPlatformParameters could be new PlatformParameters(PromptBehavior.Auto)
Old: AcquireToken(string, string, Uri, PromptBehavior where prompt behavior could be PromptBehavior.Auto
Prompt behavior auto, in both vesions, means: the user will be asked for credentials when they're not already cached. Both AuthenticationContext constructors allow you to pass a token-cache which is something you can implement yourself f.e. to cache tokens in memory, file or database (see this article for an example file cache implementation).
Get AccessToken manually
If you really want to pass in the user credentials from code without prompting the user, there is always a way around. In this case you'll have to implement the Resource Owner Password Credentials grant as outlined in OAuth2 specificatioin / RFC6749.
Coincidence or not, I have an open-source library called oauth2-client-handler that implements this for use with HttpClient, but anyway, if you want to go this route you can dig into that code, especially starting from this method.
Use Access Token
Once you have an access token, you can proceed with the samples on this MSDN page, f.e.:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new OAuthCredentials(authenticationResult.AccessToken))
};
In case someone is still struggling to get it to work. We need to upload a certificate manifest on azure portal for the application and then use the same certificate to authenticate the client for getting the access token. For more details please see: https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/
Using the example code in this Microsoft Document as the starting point and these libraries:
Microsoft Identity Client 4.27
EWS Managed API v2.2
I am able to successfully authenticate and connect with Exchange on Office 365.
public void Connect_OAuth()
{
var cca = ConfidentialClientApplicationBuilder
.Create ( ConfigurationManager.AppSettings[ "appId" ] )
.WithClientSecret( ConfigurationManager.AppSettings[ "clientSecret" ] )
.WithTenantId ( ConfigurationManager.AppSettings[ "tenantId" ] )
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
AuthenticationResult authResult = null;
try
{
authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
try
{
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri( "https://outlook.office365.com/EWS/Exchange.asmx" );
ewsClient.Credentials = new OAuthCredentials( authResult.AccessToken );
ewsClient.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.SmtpAddress, "ccc#pppsystems.co.uk" );
ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "ccc#pppsystems.co.uk" );
var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );
foreach( var folder in folders )
{
Console.WriteLine( "" + folder.DisplayName );
}
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
}
The Microsoft example code did not work - the async call to AcquireTokenForClient never returned.
By calling AcquireTokenForClient in a separate try catch block catching a general Exception, removing the await and using .Result, this now works - nothing else was changed.
I realise that this is not best practice but, both with and without the debugger, the async call in the original code never returned.
In the Azure set-up:
A client secret text string was used - a x509 certificate was not necessary
The configuration was 'app-only authentication'
Hope this helps someone avoid hours of frustration.

multiple_matching_tokens_detected exception in authContext.AcquireTokenAsync

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.

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.

Accessing older GData APIs (Spreadsheet API) using OAuth 2 and a service account

The short question is whether is this possible and if so, how?
Outline
I have a .NET application which currently uses a service account to access information across a Google Apps domain using the Google Drive API. This works fine using the google-api-dotnet-client library and code along the same lines as shown in the samples here - which are currently a very good basic example of what I'm doing.
What I want to do now is extend it so as well as using those APIs provided by the "new" google-api-dotnet-client library, it uses the older "GData" libraries, as provided for via the
older google-gdata library, specifically the Spreadsheets API (and perhaps more to come).
The Problem
This is where the difficulty arises. The former library does exactly what I want, as evidenced by the second link in the first paragraph above - and the fact I have it doing it myself. HOWEVER... although the second library has been updated to support OAuth 2.0 in addition to OAuth 1.0 and the other older auth techniques, it does not - as far as I can tell from extensive Googling and trail-and-error - allow the "service account on behalf of all my users" operation which I need.
My question is whether I'm missing something (possibly a hard to find or undocumented something) which would allow me to do what I want. Failing that, is there any way I could force this behaviour and make these two libraries operate side by side?
The ideal solution
Ideally I would love some way of having the Google.GData.Spreadsheets.SpreadsheetsService instance be able to take advantage of the Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> instance I'm already using... somehow. Is such witchcraft possible? I'm I missing the obvious?
Failing that, I'm happy to do the whole OAuth2 "assertion flow client" dance again if I have to, in some way that the older library can handle.
Help?
Other Thoughts
I have considered - and rejected for the time being - the option of starting from scratch and writing my own library to make this happen. This is for two reasons:
The gdata library already exists, and has been developed by many people likely cleverer than myself. I'm not so arrogant that I believe I can do better.
I'm not certain the OAuth2 with service account approach is even supported/allowed on these older APIs.
An alternate approach which I've been hoping to avoid but may have to fall back to depending on the answers here will be to use 2-legged OAuth 1.0 for portions of this. I'd prefer not to, as having parts of the app rely on one old auth method whilst other parts do it the nice new way just feels wrong to me. And there's that much more to go wrong...
Updates
I have considered the possibility of subclassing GDataRequestFactory and GDataRequest so I can make my own request factory and have that take the instance of Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> (well, an instance of Google.Apis.Authentication.IAuthenticator anyway) which could step in to authenticate the request just before it's called. However... the constructor for GDataRequest is internal, which has stopped me.
It's really looking like this isn't meant to be.
For the sake of other folks coming across this question (now that the solution linked to in the accepted answer uses deprecated code), here's how I solved it:
First, start in "new API" land (use the Google.Apis.Auth nuget package) by setting up a ServiceAccountCredential following Google's Service Account example:
//In the old api, this accessed the main api accounts' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "12345697-abcdefghijklmnop#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
}.FromCertificate(certificate));
Tell the credential to request an Access Token:
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
Now it's time to switch back to "old API" land (use the Google.GData.Spreadsheets nuget package). Start by constructing the SpreadsheetsService (similar to Google's example):
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
To use Service Account authentication, we'll create an instance of the GDataRequestFactory and set a custom Authorization header:
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
Finally, set the SpreadsheetsService's RequestFactory property to this new factory:
service.RequestFactory = requestFactory;
And go ahead and use the SpreadsheetsService as you would had you authenticated using any other technique. (Tip: share spreadsheets with the Service Account by inviting the serviceAccountEmail address to the sheet)
I managed to solve this by subclassing GDataRequestFactory and creating my own implementation of the interfaces implemented by GDataRequest. This implementation wraps an instance of GDataRequest instantiated via reflection, and adds in the necessary code to perform authentication using an instance of IAuthenticator (in my case, Auth2Authenticator).
I wrote a blog post on it and added an example as a Gist:
Blog: Using Google's Spreadsheet API using .NET, OAuth 2.0 and a Service Account
Gist 4244834
Feel free to use this if it helps you (BSD licence).
Hey just stumbled accross the same problem and produced a different solution:
Has anybody ever concidered of writing the parameters from the credentials-object directly to an OAuth2Parameters-Object?
I did this and it worked nicely:
public class OAuthTest
{
OAuth2Parameters param = new OAuth2Parameters();
public OAuthTest()
{
Debug.WriteLine("Calling: AuthGoogleDataInterface()");
bool init = AuthGoogleDataInterface();
if (init)
{
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
//requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
var service = new SpreadsheetsService("MyService");
service.RequestFactory = requestFactory;
SpreadsheetQuery query = new SpreadsheetQuery();
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.Query(query);
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
// Print the title of this spreadsheet to the screen
Debug.WriteLine(entry.Title.Text);
}
}
Debug.WriteLine(m_Init);
}
private bool AuthGoogleDataInterface()
{
bool b_success;
try
{
Console.WriteLine("New User Credential");
// New User Credential
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GCSecrets.Secrets,
ArrScope,
"user", CancellationToken.None,
new FileDataStore("My.cal")).Result;
// put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
this.param.Scope = ArrScope.ToString();
this.param.AccessToken = credential.Token.AccessToken;
this.param.RefreshToken = credential.Token.RefreshToken;
}
Debug.WriteLine("AuthGoogleDataInterface: Success");
b_success = true;
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
b_success = false;
}
return b_success;
}
}

Categories

Resources