I am having a C# MVC web application through which I'm trying to read the user's group using Microsoft Graph API. But when I'm trying to do so through code using HttpClient I'm getting "403 Forbidden" error.
I have all the required permissions but still getting the error, can't get the reason for the error or any solution for it. I even tried to google it but couldn't find anything.
If anyone can help.
try
{
using (var httpClient = new HttpClient(HttpClientHelper.GetWinHttpHandler()))
{
var json = #"{ 'securityEnabledOnly': true }";
var stringContent = new StringContent(json);
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + graphapitoken);
httpClient.BaseAddress = new Uri("https://graph.microsoft.com/");
var response = Task.Run(() => httpClient.PostAsync($"v1.0/users/" + UsermailId + "/getMemberGroups", new StringContent(json, Encoding.UTF8, "application/json")));
response.Wait();
if (response.Result.IsSuccessStatusCode)
{
string strResponse = await response.Result.Content.ReadAsStringAsync();
object dec = JsonConvert.DeserializeObject(strResponse);
JObject obj = JObject.Parse(dec.ToString());
List<JToken> obj1 = obj["value"].ToList();
listAssociatedGroups = obj1.Values<string>().ToList();
}
}
}
Getting Token
public class Token
{
public static string GetToken()
{
return GraphToken(ConfigurationManager.AppSettings["ida:Tenant"],ConfigurationManager.AppSettings["ida:ClientId"], ConfigurationManager.AppSettings["ida:ClientSecret"]);
}
private static string GraphToken(string tenantId, string clientId, string clientSecret)
{
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = authContext.AcquireTokenAsync("https://graph.microsoft.com", credential).GetAwaiter().GetResult();
return result.AccessToken;
}
public static string TokenAsync(string tenantId, string clientId, string clientSecret, string resourceURI)
{
try
{
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}");
ClientCredential credential = new ClientCredential(clientId, clientSecret);
var authenticationResult = authenticationContext.AcquireTokenAsync(resourceURI, credential).GetAwaiter().GetResult();
return authenticationResult.AccessToken;
}
catch (Exception ex)
{
throw new Exception("Failed to retrive AAD token");
}
}
}
API Permissions I have
First, you could test this API with Graph Explorer directly.
POST https://graph.microsoft.com/v1.0/me/getMemberGroups
{
"securityEnabledOnly": true
}
I'm not sure which kind of flows that you used to get access token in your code. If you use client credentials flow, you need to add one of the application permissions. Delegated permissions can be used for the other flows.
Just a stab in the dark:
Some of those permissions require admin consent.
Does your application have admin consent for those permissions?
Related
I have my below code which output the master branch stats in JSON format from Azure DevOps repository and I am capturing the required output. This works when I use the personal access token the authentication works and gets back with the results from the API.
But when I try to generate Access token using the registered app in AAD(has delegated user impersonation enabled for Azure DevOps under API permissions), I am able to generate the access token and then passing it while calling the API, but it returns back with
StatusCode: 203, ReasonPhrase: 'Non-Authoritative Information', Version: 1.1, Content: System.Net.Http.StreamContent
public static async Task GetBuilds()
{
string url = "Azure Dev-Ops API";
var personalaccesstoken = "personalaccesscode";
//var personalaccesstoken = token.GetYourTokenWithClientCredentialsFlow().Result;
string value = null;
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalaccesstoken))));
using (HttpResponseMessage response = await client.GetAsync(url))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
dynamic jsonObject = JsonConvert.DeserializeObject(responseBody);
value = jsonObject;
}
}
if (value != null)
{
Console.WriteLine(value);
}
}
public static async Task<string> GetYourTokenWithClientCredentialsFlow()
{
string tokenUrl = $"https://login.microsoftonline.com/{tenant ID}/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "client ID",
["client_secret"] = "client secret",
["resource"] = "https://graph.microsoft.com/"
});
dynamic json;
dynamic token;
string accessToken;
HttpClient client = new HttpClient();
var tokenResponse = client.SendAsync(tokenRequest).Result;
json = await tokenResponse.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject(json);
accessToken = token.access_token;
return accessToken;
}
Tried to test using postman using the access token generated using above code and get as below screenshot.
what I am doing wrong here and how can I fix the problem?
The azure ad access token is a bearer token. You do not need to use it as basic auth.
Try with the following code:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetYourTokenWithClientCredentialsFlow().Result);
Update:
Register a new app
Set the app as a public client by default
Add permission to DevOps API
Create a new project, install Microsoft.IdentityModel.Clients.ActiveDirectory package
Code sample
class Program
{
static string azureDevOpsOrganizationUrl = "https://dev.azure.com/jack0503/"; //change to the URL of your Azure DevOps account; NOTE: This must use HTTPS
static string clientId = "0a1f****-****-****-****-a2a4****7f69"; //change to your app registration's Application ID
static string replyUri = "https://localhost/"; //change to your app registration's reply URI
static string azureDevOpsResourceId = "499b84ac-1321-427f-aa17-267ca6975798"; //Constant value to target Azure DevOps. Do not change
static string tenant = "hanxia.onmicrosoft.com"; //your tenant ID or Name
static String GetTokenInteractively()
{
AuthenticationContext ctx = new AuthenticationContext("https://login.microsoftonline.com/" + tenant); ;
IPlatformParameters promptBehavior = new PlatformParameters(PromptBehavior.Auto | PromptBehavior.SelectAccount);
AuthenticationResult result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, new Uri(replyUri), promptBehavior).Result;
return result.AccessToken;
}
static String GetToken()
{
AuthenticationContext ctx = new AuthenticationContext("https://login.microsoftonline.com/" + tenant); ;
UserPasswordCredential upc = new UserPasswordCredential("jack#hanxia.onmicrosoft.com", "yourpassword");
AuthenticationResult result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, upc).Result;
return result.AccessToken;
}
static void Main(string[] args)
{
//string token = GetTokenInteractively();
string token = GetToken();
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(azureDevOpsOrganizationUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = client.GetAsync("_apis/projects").Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine("\tSuccesful REST call");
var result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new UnauthorizedAccessException();
}
else
{
Console.WriteLine("{0}:{1}", response.StatusCode, response.ReasonPhrase);
}
Console.ReadLine();
}
}
}
For an integration test I have an authorized .NET Core 2.2 Controller that is calling another authorized controller (different project) or external api (like Microsoft Graph).
Both apis are authenticated against the Azure AD. In all the controller actions we need the authenticated user.
We can get in the first api by getting a token based on the username and password (grant_type=password). When the call continues to the second api, it breaks because of an interactive login prompt (We use ADAL).
Normally, the user authenticates with open id connect, we then have the authentication code and get the accesstoken + refresh token with the authentication code. With the refresh token we can get an access token for the second api.
We created a small sample project with default Values Controllers to explain our problem.
Get access token before calling the first api with native app registration:
public static async Task<string> AcquireTokenAsync(string username, string password)
{
var aadInstance = "https://login.windows.net/{0}";
var tenantId = "put id here";
var authority = string.Format(aadInstance, tenantId);
var clientId = "clientid here";
var resource = "put resource here";
var client = new HttpClient();
var tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={username}&password={password}";
var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = await client.PostAsync(tokenEndpoint, stringContent).ContinueWith((response) =>
{
return response.Result.Content.ReadAsStringAsync().Result;
});
JObject jobject = JObject.Parse(result);
var token = jobject["access_token"].Value<string>();
return token;
}
First API:
[Authorize]
[HttpGet]
public async Task<IActionResult> Get()
{
string name = User.Identity.Name;
var result = await AcquireTokenSilentWithImpersonationAsync();
string BaseUrl = "https://localhost:44356/";
var client = new HttpClient
{
BaseAddress = new Uri(BaseUrl)
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var url = "api/values";
HttpResponseMessage response = await client.GetAsync(url);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
int x = 1;
break;
default:
throw new HttpRequestException($"Error - {response.StatusCode} in response with message '{response.RequestMessage}'");
}
return Ok();
}
private const string BackendResource = "Second api resource here";
/// <summary>
/// For more information: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-devhowto-adal-error-handling
/// </summary>
/// <returns></returns>
public async Task<AuthenticationResult> AcquireTokenSilentWithImpersonationAsync()
{
const string ClientId = "client id of first api here";
const string ClientSecret = "secret of first api here";
ClientCredential credential = new ClientCredential(ClientId, ClientSecret);
string userObjectId = _httpContextAccessor.HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
var authContext = GetAuthenticationContext(userObjectId);
AuthenticationResult authResult = null;
try
{
authResult = await authContext.AcquireTokenSilentAsync(BackendResource, credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (AdalSilentTokenAcquisitionException ex)
{
// Exception: AdalSilentTokenAcquisitionException
// Caused when there are no tokens in the cache or a required refresh failed.
// Action: Case 1, resolvable with an interactive request.
try
{
authResult = await authContext.AcquireTokenAsync(BackendResource, ClientId, new Uri("https://backurl.org"), new PlatformParameters(), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
}
catch (Exception exs)
{
throw;
}
}
catch (AdalException e)
{
// Exception: AdalException
// Represents a library exception generated by ADAL .NET.
// e.ErrorCode contains the error code.
// Action: Case 2, not resolvable with an interactive request.
// Attempt retry after a timed interval or user action.
// Example Error: network_not_available, default case.
throw;
}
return authResult;
}
Second api:
[Authorize]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
string name = User.Identity.Name;
return new string[] { "value1", "value2" };
}
You need to use the On-behalf-of flow in your Web API (not the interactive token acquisition, need)
If you want to use ADAL.NET, a sample is there: https://github.com/azure-samples/active-directory-dotnet-webapi-onbehalfof
but I would now recommend you use MSAL.NET. the sample is: active-directory-dotnet-native-aspnetcore-v2/2. Web API now calls Microsoft Graph, and the documentation: https://aka.ms/msal-net-on-behalf-of
Also note that for Web APIs, we don't use OIDC (this is to sign-in users), but rather a JWT bearer middleware
I am new to Azure and i am working on a WebApi app which can create/delete/update and authenticate a user on Azure AD B2C tenant using
Graph API. I am stuck with the authenticate user on Azure AD B2C. I was successfully able to create user by following code.
private async Task<string> CreateUserRequest(string api, string json)
{
AuthenticationResult result = authContext.AcquireToken(Globals.aadGraphResourceId, credential);
HttpClient http = new HttpClient();
string url = Globals.aadGraphEndpoint + tenant + api + "?" + Globals.aadGraphVersion;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
return await response.Content.ReadAsStringAsync();
}
and now i need to use the same way for Authenticate user and get token as the for following code
and now i need to log in/sign in using api call by passing username and password as parameters slimier to above way.
public async Task<string> B2CAuthenticateUser(string userName, string password)
{
return await SendGraphValidateUser("/users/" + userName, null);
}
public async Task<string> SendGraphValidateUser(string api, string query)
{
AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", credential);
HttpClient http = new HttpClient();
**Here I need your help**
> // string url = "https://graph.windows.net/" + tenant + api + "?" +
> Globals.aadGraphVersion;
> // if (!string.IsNullOrEmpty(query))
> // {
> // url += "&" + query;
> // }
>
>
> //HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string error = await response.Content.ReadAsStringAsync();
object formatted = JsonConvert.DeserializeObject(error);
throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented));
}
return await response.Content.ReadAsStringAsync();
}
I hope there should be a way to do this. appreciate if you can suggest a way to do it
PS : I have gone through with following related questions and wasn't help much]
Authenticate a user on Azure AD B2C using Graph API
It's best if you authenticate users using a resource owner policy that enables your own application to validate credentials for users.
I wanted to send a mail using SMTP server with Azure access token.
I have an application hosted in azure and user login with tenant active directory. I found a code which does the same for google account here.
Can someone help me?
I tried with the below code, generated azure access token and tried to send it to SMTP as below. But something is missing here and going in other way as this code is not sending mail.
public async Task SendmailusingOAuthToken()
{
try
{
string accessToken = await new AppConfig().GetTokenForApplication();
System.Net.Mail.MailMessage message = new System.Net.Mail.MailMessage(
"Anita.Sure#mindtree.com",
"Anita.Sure#mindtree.com",
"NETWORKNET-33499 - " + Guid.NewGuid().ToString(),
"Access to SMTP servers using OAuth");
using (SmtpClient client = new SmtpClient("smtp.office365.com", 587))//, "user1#testaccount1913.narod2.ru", accessToken, true))
{
client.Timeout = 400000;
//client.SecurityMode = SmtpSslSecurityMode.Implicit;
client.EnableSsl = true;
client.SendAsync(message, accessToken);
}
}
catch (Exception ex)
{
throw;
}
}
Token generation code:
public async Task<string> GetTokenForApplication()
{
try
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
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;
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(clientId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's token filecache
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new FileCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
return authenticationResult.AccessToken;
}
catch (Exception ex)
{
throw;
}
}
I get a 403 Forbidden response from Azure AD when trying to create an application using the Graph API:
private static void CreateApplicationViaPost(string tenantId, string clientId, string clientSecret)
{
var authContext = new AuthenticationContext(
string.Format("https://login.windows.net/{0}",
tenantId));
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = authContext.AcquireToken(
"https://graph.windows.net",
clientCred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
const string json = #"{ displayName: ""My test app"", logoutUrl: ""http://logout.net"", identifierUris: [ ""http://identifier1.com"" ], replyUrls: [ ""http://replyUrl.net"" ] }";
HttpResponseMessage response = client.PostAsync(
string.Format("https://graph.windows.net/{0}/applications?api-version=1.6", tenantId),
new StringContent(json, Encoding.UTF8, "application/json")).Result;
Console.WriteLine(response.ToString());
}
The client registered in Azure AD has all the permissions:
What am I missing?
EDIT:
I registered a native client in Azure AD and gave it permissions to write to Windows Azure Active Directory. This code create an application in Azure AD:
private static void CreateApplicationViaPost(string tenantId, string clientId, string redirectUri)
{
var authContext = new AuthenticationContext(
string.Format("https://login.windows.net/{0}",
tenantId));
AuthenticationResult result = authContext.AcquireToken("https://graph.windows.net", clientId, new Uri(redirectUri), PromptBehavior.Auto);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
const string json = #"{ displayName: ""My test app1"", homepage: ""http://homepage.com"", logoutUrl: ""http://logout1.net"", identifierUris: [ ""http://identifier11.com"" ], replyUrls: [ ""http://replyUrl1.net"" ] }";
HttpResponseMessage response = client.PostAsync(
string.Format("https://graph.windows.net/{0}/applications?api-version=1.6", tenantId),
new StringContent(json, Encoding.UTF8, "application/json")).Result;
Console.WriteLine(response.ToString());
}
Modifying the directory requires consent from an admin user. So you'll need to acquire an access token from an user, e.g. through OAuth, instead of a token for the client.
There are quite a few of samples at GitHub that show the authorisation flow, e.g. https://github.com/AzureADSamples/WebApp-GraphAPI-DotNet.
Adding to #MrBrink's answer - you need to make sure the person adding the permissions in the Azure Active Directory UI is actually an administrator. If you have access to Azure Active Directory and are not an administrator it WILL still let you assign permissions - however they will only apply at a user scope.
An alternative would be to use the ActiveDirectoryClient from the Microsoft.Azure.ActiveDirectory.GraphClient NuGet package.
private static async Task CreateApplication(string tenantId, string clientId,
string redirectUri)
{
var graphUri = new Uri("https://graph.windows.net");
var serviceRoot = new Uri(graphUri, tenantId);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
async () => AcquireTokenAsyncForUser("https://login.microsoftonline.com/" + tenantId,
clientId, redirectUri));
var app = new Application
{
Homepage = "https://localhost",
DisplayName = "My Application",
LogoutUrl = "https://localhost",
IdentifierUris = new List<string> { "https://tenant.onmicrosoft.com/MyApp" },
ReplyUrls = new List<string> { "https://localhost" }
};
await activeDirectoryClient.Applications.AddApplicationAsync(app);
Console.WriteLine(app.ObjectId);
}
private static string AcquireTokenAsyncForUser(string authority, string clientId,
string redirectUri)
{
var authContext = new AuthenticationContext(authority, false);
var result = authContext.AcquireToken("https://graph.windows.net",
clientId, new Uri(redirectUri), PromptBehavior.Auto);
return result.AccessToken;
}