This seem to be a pretty easy one, but as I dont have exp with async too much, then I come to the experts.
I need to make a rest call, and for it I need an authorization token.(bearer token)
This is already done by this:
private static async Task<string> GetAppTokenAsync()
{
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string azureAdGraphApiEndPoint = ConfigurationManager.AppSettings["ida:AzureAdGraphApiEndPoint"];
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphResourceId"];
string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(Authority, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientId, appKey);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
However, from the caller method, which is a lambda expression with await, I dont know how to get a reference to that result and put in the httpheader
public ActionResult TestRestCall()
{
Uri serviceRoot = new Uri(azureAdGraphApiEndPoint);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
Application app = (Application)adClient.Applications.Where(
a => a.AppId == clientId).ExecuteSingleAsync().Result;
if (app == null)
{
throw new ApplicationException("Unable to get a reference to application in Azure AD.");
}
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", CODE HERE);
}
You should make your caller function Async like:
public async Task<ActionResult> TestRestCall()
then call your async method:
var token = await GetAppTokenAsync();
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot, token);
Related
Been working on an ASP.NET MVC 5 application that interacts with the MS Graph API.
The controller gets a graph client for the current user and then using that client sends an email.
public class EmailController : Controller
{
// GET: Email
public async Task<ActionResult> Index()
{
var client = await MSGraphServiceClient.GetGraphServiceClientAsync();
await InvitationHelper.SendEmail(client);
return Content("");
}
}
public static async Task<GraphServiceClient> GetGraphServiceClientAsync()
{
string appId = ConfigurationManager.AppSettings["ida:ClientId"];
string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
// Get Signed in user
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
// Get app and user id claims from Azure
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
// Specify Graph resource URL
string graphResourceID = "https://graph.microsoft.com";
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(appId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// use delegate to create auth provider using async auth result
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
// return the graph service client
return new GraphServiceClient(delegateAuthProvider);
}
public static async Task<bool> SendEmail(GraphServiceClient graphServiceClient)
{
var message = new Message()
{
Subject = "Test",
Body = new ItemBody()
{
ContentType = BodyType.Text,
Content = "This is a test"
},
ToRecipients = new List<Recipient>() { new Recipient() { EmailAddress = new EmailAddress() { Address = "jonathan.sweetland#gmail.com" } } }
};
var request = graphServiceClient.Me.SendMail(message, true).Request();
await request.PostAsync();
return true;
}
It is not a permissions error because on AAD I have granted all the permissions for this app. I have other graph calls working so I know it authenticates correctly using the same code.
Used fiddler as was suggested and the JSON response was the same as the error messages displayed.
After a lot of playing around, I found that if you try and use the SendMail on the Graph API whilst authenticated by a Azure Active Directory tenant that is not set up with an outlook account. Even if the account has an outlook account on a different tenant, then you will not be able to use the SendMail endpoint.
I am trying to write a local console application which will swap an Azure Web App slot using the Azure REST API. Using the following code I get a 401 (Unauthorized) response:
public async Task Swap(string subscription, string resourceGroup, string site, string slot)
{
var client = new HttpClient();
var url =
$"https://management.azure.com/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{site}/applySlotConfig?api-version=2016-08-01";
var data = new {preserveVnet = true, targetSlot = slot};
var message = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(message);
Console.WriteLine(response.StatusCode);
}
I know I need to put in some kind of credentials but what I have found seems to apply to apps using Azure AD for authentication. This will be a publicly accessible web app with anonymous authentication.
Generally speaking you need to attach a Authorization header to the request with the Auth token. There are numerous ways of getting it, see this link or this.
This is how I managed to do it (using the provided links):
private async Task<string> GetAccessToken(string tenantName, string clientId, string clientSecret)
{
var authString = "https://login.microsoftonline.com/" + tenantName;
var resourceUrl = "https://management.azure.com/";
var authenticationContext = new AuthenticationContext(authString, false);
var clientCred = new ClientCredential(clientId, clientSecret);
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientCred);
var token = authenticationResult.AccessToken;
return token;
}
And then in my previous method:
public async Task Swap(string subscription, string resourceGroup, string site, string slot)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetAccessToken("XXX", "XXX", "XXX"));
var url =
$"https://management.azure.com/subscriptions/{subscription}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{site}/applySlotConfig?api-version=2016-08-01";
var data = new {preserveVnet = true, targetSlot = slot};
var message = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(message);
Console.WriteLine(response.StatusCode);
}
I have implemented an Azure AD OAuth2 Daemon or Server to ASP.NET Web API.
However I only receive an access token which is the property on the AuthenticationResult. See implementation below.
public IHttpActionResult GetAccessToken(string clientId, string clientkey)
{
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential clientCredential = new ClientCredential(clientId, clientkey);
AuthenticationResult authenticationResult = authContext.AcquireTokenAsync(resourceUri, clientCredential).Result;
Authorisation authorisation = new Authorisation {access_token = authenticationResult.AccessToken,
token_type = authenticationResult.AccessTokenType,
expires_on = authenticationResult.ExpiresOn };
return Ok(authorisation);
}
This returns only access token. I would like an implementation, a Daemon or Server implementation that returns both access token and refresh token. Have your seen or done similar implementation. Any useful links to an example are welcome.
When I posted this question, this was the answer I was looking for, please see screen shot below for expected result and c# console solution.
Having found the solution, it is worth sharing it here, may be useful to someone some day
C# console app code to achieve expected result in the postman screen shot below
using System;
using System.Collections.Generic;
using System.Net.Http;
namespace AzureADTokenApp
{
class Program
{
static void Main(string[] args)
{
var client = new HttpClient();
var uri = "https://login.microsoftonline.com/<tenant-name>.onmicrosoft.com/oauth2/token?api-version=1.0";
var pairs = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("resource", "https://graph.microsoft.com"),
new KeyValuePair<string, string>("client_id", "<azure ad client id e.g. 9b864-a5e6-4f0d-b155-1f53a6c78>"),
new KeyValuePair<string, string>("client_secret", "<azure ad client secret e.g. MTMiXaO1P9HnhSawdXWmcnuQ="),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", "<azure ad user e.g. julius.depulla#example.com>"),
new KeyValuePair<string, string>("password", "<azure ad user password e.g. Pa$$word01>"),
new KeyValuePair<string, string>("scope", "openid")
};
var content = new FormUrlEncodedContent(pairs);
var response = client.PostAsync(uri, content).Result;
string result = string.Empty;
if (response.IsSuccessStatusCode)
{
result = response.Content.ReadAsStringAsync().Result;
}
Console.WriteLine(result);
Console.ReadLine();
}
}
}
Screenshot from Postman - Expected Result. You will have same result in console except is less readable
You are using the client credentials flow. In that flow, a refresh token should not be included https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3. Also it looks like you are using ADAL v3, which anyways doesn't return refresh tokens (by design), but it uses them automatically for you. More info here http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/
There is no need to use the refresh token to redeem to access token manually if you were developing with ADAL v3.
In this scenario, we should use the AcquireTokenSilentAsync and catch the exception. After the access_token is expired, we should re-initialize AuthenticationContext and call the AcquireTokenAsync again to send the request to acquire the access_token.
Here is a code sample for your reference:
class Program
{
static string authority = "https://login.microsoftonline.com/adfei.onmicrosoft.com";
static string resrouce = "https://graph.microsoft.com";
static string clientId = "";
static string secret = "";
static ClientCredential credential = new ClientCredential(clientId, secret);
static AuthenticationContext authContext = new AuthenticationContext(authority);
static void Main(string[] args)
{
while (true)
{
var client = new GraphServiceClient(new DelegateAuthenticationProvider(request =>
{
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", GetAccessToken());
return Task.FromResult(0);
}));
var users = client.Users.Request().GetAsync().Result.CurrentPage;
foreach (var user in users)
{
Console.WriteLine(user.DisplayName);
}
Thread.Sleep(1000 * 60 * 5);
}
Console.Read();
}
static string GetAccessToken()
{
try
{
var token = authContext.AcquireTokenAsync(resrouce, credential).Result.AccessToken;
return token;
}
catch (Exception ex)
{
authContext = new AuthenticationContext(authority);
return GetAccessToken();
}
}
I have the following code to get the context token.
public class UserProfileController : Controller
{
private static string azureAdGraphApiEndPoint = ConfigurationManager.AppSettings["ida:AzureAdGraphApiEndPoint"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
public async Task<ActionResult> GetPropertiesForUser()
{
Uri serviceRoot = new Uri(azureAdGraphApiEndPoint);
var token = await GetAppTokenAsync();
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
Microsoft.Azure.ActiveDirectory.GraphClient.Application app = (Microsoft.Azure.ActiveDirectory.GraphClient.Application)adClient.Applications.Where(
a => a.AppId == clientId).ExecuteSingleAsync().Result;
if (app == null)
{
throw new ApplicationException("Unable to get a reference to application in Azure AD.");
}
string requestUrl = string.Format("https://graph.windows.net/mysaasapp.onmicrosoft.com/users/{0}?api-version=1.5", token);
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", token);
HttpResponseMessage hrm = await hc.GetAsync(new Uri(requestUrl));
if (hrm.IsSuccessStatusCode)
{
string jsonresult = await hrm.Content.ReadAsStringAsync();
return View("TestRestCall", new SuccessViewModel
{
Name = "The Title",
Message = "The message",
JSON = jsonresult.ToJson()
});
}
else
{
return View();
}
}
private static async Task<string> GetAppTokenAsync()
{
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string azureAdGraphApiEndPoint = ConfigurationManager.AppSettings["ida:AzureAdGraphApiEndPoint"];
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphResourceId"];
string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(Authority, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientId, appKey);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
However I need to replace the token with the current user email, but havent found how.
First of all, forgive me if I've misunderstood the problem.
If you want to get the user's email from Claims, I think that can be somewhat different depending on your tenant. For me, I can find my email i Claims under both "upn" and "unique name".
So as an example;
string email= ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
This would give me my email, or null.
In my case, if I use, as you do, ""http://schemas.microsoft.com/identity/claims/objectidentifier"", that returns a GUID uniquely identifying me in my tenants Active Directory.
Have you inspected your Claims to see what is in there?
I'm trying to figure out how to delete an AppRoleAssignment from either an Group or a User using the Graph API for Azure Active Directory. I'm using the .NET SDK (Microsoft.Azure.ActiveDirectory.GraphClient).
I've tried using the standard DeleteAsync method that's on every IEntityBase, but it fails with an error. It's issuing an HTTP request that looks like this:
DELETE /{tenantId}/directoryObjects/{appRoleAssignment ObjectID}/Microsoft.DirectoryServices.AppRoleAssignment?api-version=1.5
which fails with a 400 Bad Request with the error "Direct queries to this resource type are not supported."
This isn't the correct way to delete AppRoleAssignments using the Graph API according to this Microsoft blog post which says you need to do an HTTP request that looks like:
DELETE /{tenantId}/users/{user object ID}/appRoleAssignments/{appRoleAs}?api-version=1.5
If I do a manual HTTP request using HttpClient using that URL format, it works, but I want to know how to do this within the bounds of the .NET library rather than doing manual HTTP requests myself.
How do I delete AppRoleAssignments via the .NET library?
While it is not fixed, you can make a manual HTTP-request, but still using Azure AD SDK to acqure the token. Something like this:
var tenantId = "<guid> tenant id";
var appId = "<guid> your Azure app id";
var appKey = "your app key";
var authority = "i.e. https://login.windows.net/mycompany.onmicrosoft.com";
var graphUrl = "https://graph.windows.net/";
public async Task RemoveRoleFromUser(Guid userId, string roleObjectId) {
var uri = string.Format("{0}/users/{1}/appRoleAssignments/{2}?api-version=1.5", tenantId, userId, roleObjectId);
await ExecuteRequest<object>(uri, HttpMethod.Delete);
}
private async Task<T> ExecuteRequest<T>(string uri, HttpMethod method = null, Object body = null) where T : class {
if (method == null) method = HttpMethod.Get;
T response;
var token = await AcquireTokenAsyncForApplication();
using (var httpClient = new HttpClient { BaseAddress = getServicePointUri() }) {
var request = new HttpRequestMessage(method, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
if (body != null) {
request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
}
var responseMessage = await httpClient.SendAsync(request).ConfigureAwait(false);
responseMessage.EnsureSuccessStatusCode();
response = await responseMessage.Content.ReadAsAsync<T>();
}
return response;
}
private async Task<string> AcquireTokenAsyncForApplication() {
ClientCredential clientCred = new ClientCredential(appId, appKey);
var authenticationContext = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphUrl, clientCred);
return authenticationResult.AccessToken;
}
private Uri getServicePointUri() {
Uri servicePointUri = new Uri(graphUrl);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
return serviceRoot;
}
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
user = (User) await client.Users.GetByObjectId(objectId).ExecuteAsync();
var roleId = "";
await user.AppRoleAssignments.Where(t=>t.ObjectId==roleId).FirstOrDefault().DeleteAsync();
The following websites might be helpful:
https://github.com/AzureADSamples/WebApp-RoleClaims-DotNet
https://github.com/AzureADSamples/WebApp-GraphAPI-DotNet