Reputation: 796
Basically I'm implementing an SSO for an employee portal that I'm making for us, but I'd also like to be able to access the graph API (At the least, the AzureAD REST API items, like adding/removing/getting users information) without having to be signed in through an SSO.
This way, I can use what I'm thinking to be some kind of API key/secret sort of setup and schedule cron jobs that interact with the AD in some way. Technically I could signin and set my account to be the one running this, but that seems kind of hacky and unreliable (as in, if something happens to my account, password expires, changes, etc... then the refresh token will no longer be valid and I'll have to sign in again and the task could be temporarily broken.)
I could have sworn I've seen documentation for this somewhere when I was looking into implementing this a few months ago, but I can't for the life of me find it now.
Really hope this isn't a duplicate, I just can't think of wording to search for that doesn't keep coming up with SSO-based API information.
Update - Alright, it looks like I figured this out (with the help of Shaun Luttin's answer posted below: https://stackoverflow.com/a/32618417/3721165 [link for convenience])
So, all of the info Shaun brought together was really helpful. Initially the docs were fairly confusing due to how they word things, as well as the complexity of some of their examples. But once I got to digging through some of the examples, plus some of the info Shaun provided, and some experimenting/research on my own, I was able to come up with this (basic demo/concept):
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.ActiveDirectory.GraphClient;
namespace AzureADGraphApi
{
class Program
{
private static string tenant = "...tenant id...";
private static string clientid = "...client id...";
private static string appkey = "...app key...";
private static string aadinstance = "https://login.microsoftonline.com/{0}";
private static string graphResourceUrl = "https://graph.windows.net";
static void Main(string[] args)
{
Uri serviceRoot = new Uri(graphResourceUrl + "/" + tenant);
ActiveDirectoryClient adc = new ActiveDirectoryClient(serviceRoot, async () => await GetToken());
IPagedCollection<IUser> Users = adc.Users.ExecuteAsync().Result;
bool pagesLeft = false;
do
{
foreach (IUser user in Users.CurrentPage)
{
Console.WriteLine(user.DisplayName);
}
pagesLeft = Users.MorePagesAvailable;
Users = Users.GetNextPageAsync().Result;
Console.WriteLine("--- Page Break ---");
} while (pagesLeft);
Console.ReadLine();
}
private static async Task<string> GetToken()
{
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, aadinstance, tenant));
AuthenticationResult result = authContext.AcquireToken(graphResourceUrl, new ClientCredential(clientid, appkey));
return result.AccessToken;
}
}
}
I found through further research that in order to use the Graph API the way I'm intending, you have to provide the graph resource URL (https://graph.windows.net) to the AquireToken method, instead of your app ID/URL.
So, I'm accepting Shaun's answer, but I also wanted to give my working result of that answer.
Thanks for the help, guys!
Upvotes: 3
Views: 6416
Reputation: 141542
What you need is an access token without doing single-sign-on. The link that Rick Rainey provided will get you started.
You can obtain an access token programmatically by calling AcquireTokenAsync
. There are lots of ways to call AcquireTokenAsync
depending on the type of application you build and how you would like to authenticate. You will need to decide whether to create a Native Client Application or a Web Application.
Based on the application type, the way to authenticate differs a fair amount. It sounds like you would like to authenticate without a user prompt (i.e. not request to enter the username/password.) The following have worked for me, and do not involve a user prompt at all. Each of them use the following constants that you will need to grab from the manage.windowsazure.com portal. Let me know in the comments if you need help finding any of them.
private const string
DIRECTORY_TENANT_NAME = "mytenant.onmicrosoft.com",
DIRECTORY_TENANT_ID = "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
RESOURCE_URL = "https://graph.windows.net",
SUBSCRIPTION_ID = "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
AUTHORITY = "https://login.microsoftonline.com/" + DIRECTORY_TENANT_NAME,
// web application
CLIENT_ID_WEB = "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
CLIENT_SECRET_WEB = "xxxxxxxx/xxxxxxxxxxxx/xxxxxxxx/xxxxxxxx=",
// native client application
CLIENT_ID_NATIVE = "xxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx",
// adminstrator
USER_NAME = "[email protected]",
USER_PASSWORD = "xxxxxxxxxx";
Now the following three options open up to you (though there may well be others) for obtaining a token. I have tested each of the following and they all work so long as you have properly configured your Azure Active Directory tenant, users, and applications.
AcquireToken(string resource, ClientAssertionCertificate clientCertificate) works with Web Apps. The disadvantage of this approach is that it is harder to setup initially (PowerShell, certificate creation). The advantage is that once setup it is easy to use. With Native Client Apps it does not work and throws this error: AADSTS50012: Client is public so a 'client_assertion' should not be presented.
var authContext = new AuthenticationContext(AUTHORITY);
var store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
var certs = store
.Certificates
.Find(X509FindType.FindByIssuerName, "mvp2015", false);
var clientCertificate = new ClientAssertionCertificate(CLIENT_ID_WEB, certs[0]);
var result = authContext
.AcquireTokenAsync(RESOURCE, clientCertificate)
.Result;
AcquireToken(string resource, ClientCredential clientCredential) works with Web Apps. It is easier to setup and is easy to use once setup. Native Client Apps do not support this approach, because they lack a Client Secret.
var authContext = new AuthenticationContext(AUTHORITY);
var clientCredential = new ClientCredential(CLIENT_ID_WEB, CLIENT_SECRET_WEB);
var result = authContext
.AcquireTokenAsync(RESOURCE, clientCredential)
.Result;
AcquireToken(string resource, string clientId, UserCredential userCredential) works with Native Client Apps. Web Apps fail with this error: AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'.
One gotcha is that the user with whom you are logging in must be configured correctly in your Active Directory.
var authContext = new AuthenticationContext(AUTHORITY);
var userCredential = new UserCredential(USER_NAME, USER_PASSWORD);
var result = authContext
.AcquireTokenAsync(RESOURCE, CLIENT_ID_NATIVE, userCredential)
.Result;
Once you have a token using your preferred method, you can then use the Active Directory Graph like this. For the sake of readability it is in a Func
though you can put the accessTokenGetter
into its own method.
Func<Task<string>> accessTokenGetter = async () =>
{
var authContext = new AuthenticationContext(AUTHORITY, false);
var clientCredential = new ClientCredential(CLIENT_ID_WEB, CLIENT_SECRET_WEB);
var result = await authContext
.AcquireTokenAsync(RESOURCE_URL, clientCredential);
var token = result.AccessToken;
return token;
};
var uriRoot = new Uri(RESOURCE_URL);
var uriTenant = new Uri(uriRoot, DIRECTORY_TENANT_ID);
var client = new ActiveDirectoryClient(uriTenant, accessTokenGetter);
foreach (var u in client.Users.ExecuteAsync().Result.CurrentPage)
{
var n = u.DisplayName;
}
DIRECTORY_TENANT_ID
is in the address bar of the web browser when we are in our Azure Active Directory tenant's dashboard. manage.windowsazure.com > Active Directory > Some Tenant. Microsoft.Azure.ActiveDirectory.GraphClient
Version 2.1.0Microsoft.IdentityModel.Clients.ActiveDirectory
Version 2.19.208020213There is an example here: https://github.com/AzureADSamples/ConsoleApp-GraphAPI-DotNet
Upvotes: 16
Reputation: 11256
Azure AD Service Principals is what I believe you are after. Here is documentation on how to create one for your app.
Upvotes: 0