I am receiving StatusCode: 403, ReasonPhrase: 'Forbidden' for an HTTP Post:
URL: $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions/{functionName}/listkeys?api-version=2022-03-01";
StatusCode: 403, ReasonPhrase: 'Forbidden'
Client:
The following is my client code:
let tenantId = "<some_tenant_id>"
let clientId = "<some_client_id>"
let secret = "<some_secret>"
let scope = "<some_scope>"
let token = BearerToken.Create(tenantId, clientId, secret, scope).Result
let tokenRequestBody = Dictionary<string, string>()
tokenRequestBody.Add("grant_type" , "client_credentials")
tokenRequestBody.Add("client_id" , clientId)
tokenRequestBody.Add("client_secret", secret)
tokenRequestBody.Add("scope" , scope)
let content = new FormUrlEncodedContent(tokenRequestBody);
let httpKeysClient = new HttpClient();
httpKeysClient.DefaultRequestHeaders.Authorization <- new AuthenticationHeaderValue("Bearer", token);
httpKeysClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
let subscriptionId = "<some_scubscription_id>"
let resourceGroupName = "<some_resource_group_name>"
let appName = "<some_function_app_name>"
let functionName = "<some_function_name>"
let apiKeyUrl = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{appName}/functions/{functionName}/listkeys?api-version=2022-03-01";
let response = httpKeysClient.PostAsync(apiKeyUrl, content).Result;
response.IsSuccessStatusCode |> should equal true // ** StatusCode: 403, ReasonPhrase: 'Forbidden' **
Appendix:
The code for creating an authorization token works:
public static class BearerToken
{
public async static Task<string> Create(string tenantId, string clientId, string clientSecret, string scope)
{
var tokenRequestBody = new Dictionary<string, string> {
{ "grant_type" , "client_credentials" },
{ "client_id" , clientId },
{ "client_secret", clientSecret },
{ "scope" , scope }
};
var url = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token";
var client = new HttpClient() { BaseAddress = new Uri(url) };
var content = new FormUrlEncodedContent(tokenRequestBody);
var response = await client.PostAsync("", content);
if (response.IsSuccessStatusCode)
{
var tokenResponse = await response.Content.ReadAsStringAsync();
var valueFor = JsonConvert.DeserializeObject<JsonSupport.AccessToken.Root>(tokenResponse);
return valueFor.access_token;
}
throw new Exception(response.ReasonPhrase);
}
}
I tried to reproduce the same in my environment via Postman and got below results
I have one service principal having Reader role under my subscription like below:
Now I generated access token using client credentials flow with below parameters:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://management.azure.com/.default
Response:
When I used the above token to list keys, I got 403 Forbidden error like below:
POST https://management.azure.com/subscriptions/<subID>/resourceGroups/Sri/providers/Microsoft.Web/sites/<name>/functions/<functionname>/listkeys?api-version=2022-03-01
Authorization: Bearer <token>
Response:
To resolve the error, I assigned Contributor role to that service principal like below:
Go to Azure Portal -> Subscriptions -> Your Subscription -> Access control (IAM) -> Add role assignment
After assigning that role, I generated access token again and got the results successfully when I used that in below query:
POST https://management.azure.com/subscriptions/<subID>/resourceGroups/Sri/providers/Microsoft.Web/sites/<name>/functions/<functionname>/listkeys?api-version=2022-03-01
Authorization: Bearer <token>
Response:
In your case, make sure to assign Contributor role to your service principal that resolves the issue.
Related
I have the following code:
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
HttpClient client = new();
var discoveryResponse = await client.GetDiscoveryDocumentAsync("https://localhost:44334");
var response = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = "...",
ClientSecret = "...",
RefreshToken = refreshToken
});
return response;
}
And it always returns 400 Bad Request with invalid_client message. When I'm refreshing token in Postman it works well. Where is the problem?
The purpose of the refresh-token is: the user does not need to re-authenticate with the credentials (username/password) in the application every time the session expires. So your application needs to connect to the endpoint identity and consume a new refresh token before the token or refresh token times out. In asp dotnet core Identity and JwtToken always have a default timeout value; whatever: you need to capture the refresh token before this timeout expires, otherwise your identity understands the user who does not have the browser open or is not online. This may imply developing a routine that stays in Roudin-Robin always refreshing the application with the new Token while the browser is open.
I changed my code to this:
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
HttpClient client = new();
var discoveryResponse = await client.GetDiscoveryDocumentAsync("https://localhost:44334");
var tokenClient = new TokenClient(client, new TokenClientOptions
{
Address = discoveryResponse.TokenEndpoint,
ClientId = "...",
ClientSecret = "...",
});
var response = await tokenClient.RequestRefreshTokenAsync(refreshToken);
response.HttpResponse.EnsureSuccessStatusCode();
return response;
}
And now it works as expected.
Because Microsoft ends the support for Basic Authentication access for IMAP in Office 365 I try to update our application to use OAuth 2.0. We use MailKit in a MVC .Net web-application to access an IMAP mailbox, but I get an error saying Authentication failed. However, as a test, I can get it to work in a c# console-application.
The strange thing is:
If I copy the access-token I acquired using the console-application and use it in my web-application I can successfully authenticate and read emails. So that part works.
The authentication itself seems to be successful in the web-application. Our webapp redirects to the Microsoft login-page, MFA works, I see successful audits in Azure A/D and I do get a token in the callback. However, this token gives the Authentication failed error by Mailkit.
In Azure A/D I see some of these errors between the successful audits, but I'm not sure whether they are related or not: Error AADSTS16000 SelectUserAccount - This is an interrupt thrown by Azure AD, which results in UI that allows the user to select from among multiple valid SSO sessions. This error is fairly common and may be returned to the application if prompt=none is specified.
I already verified that the scope for which I acquire a token is the same for both console and web.
The main difference is that I use pca.AcquireTokenInteractive(scopes) in the console application to acquire the token, but I use a webclient call with a call-back in the MVC-controller.
Here is my code (MVC):
public ActionResult Index()
{
string clientID = "[client-id here]";
string clientSecret = "[client-secret here]";
string redirectUri = "[redirectUri here]";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize"),
TokenEndpoint = new Uri("https://login.microsoftonline.com/organizations/oauth2/v2.0/token"),
ProtocolVersion = ProtocolVersion.V20,
};
List<string> scopes = new List<string>
{
"email",
"offline_access",
"https://outlook.office365.com/IMAP.AccessAsUser.All"
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scopes, new Uri(redirectUri));
return response.AsActionResultMvc5();
}
public async Task<ActionResult> Authorized(string code, string state, string session_state)
{
List<string> scopes = new List<string>
{
"IMAP.AccessAsUser.All",
"User.Read",
"offline_access"
};
HttpClient httpClient = new HttpClient();
var values = new Dictionary<string, string>
{
{ "Host", "https://login.microsoftonline.com" },
{ "Content-Type", "application/x-www-form-urlencoded" },
{ "client_id", "[client-id here]" },
{ "scope", string.Join(" ",scopes) },
{ "code", code },
{ "redirect_uri", [redirectUri here] },
{ "grant_type", "authorization_code" },
{ "client_secret", "[client-secret here]" },
{ "state", state },
};
var content = new FormUrlEncodedContent(values);
var response = await httpClient.PostAsync("https://login.microsoftonline.com/organizations/oauth2/v2.0/token", content);
var jsonString = await response.Content.ReadAsStringAsync();
var oathToken = JsonConvert.DeserializeObject<OathToken>(jsonString);
var oauth2 = new SaslMechanismOAuth2("[Email here]", oathToken.access_token);
var stringBuilder = new StringBuilder();
using (var client = new ImapClient())
{
try
{
await client.ConnectAsync("outlook.office365.com", 993, SecureSocketOptions.Auto);
await client.AuthenticateAsync(oauth2);
var inbox = client.Inbox;
inbox.Open(FolderAccess.ReadOnly);
for (int i = 0; i < inbox.Count; i++)
{
var message = inbox.GetMessage(i);
stringBuilder.AppendLine($"Subject: {message.Subject}");
}
await client.DisconnectAsync(true);
return Content(stringBuilder.ToString());
}
catch (Exception e)
{
return Content(e.Message);
}
}
}
The error Authentication failed occurs at the line
await client.AuthenticateAsync(oauth2);
The problem was the scope "email".
We had to remove that. Exactly why, I don't know. It was no problem when used in the console app. Maybe it had to do with the fact we used pca.AcquireTokenInteractive(scopes) in that.
I'm migrating from .NET Core 1.1 to 2.0, and now I have to update my Authentication too.
I'm using OAuth and OpenIddict to .NET Core 2.0
When I'm sending the request to my connect/token I'm getting this:
OpenIddict.Server.OpenIddictServerHandler[0] The token response was
successfully returned: {
"error": "unsupported_grant_type",
"error_description": "The specified 'grant_type' parameter is not
supported."
}.
This is my request method:
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, $"{url}/connect/token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret,
["pessoaid"] = pessoaId,
["usuarioid"] = usuarioId,
["conta"] = conta,
["cpfcnpj"] = userDoubleCpf,
["fonteDados"] = fonteDados,
["userIdsLogged"] = userIdsLogged
});
var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
var result = JObject.Parse(await response.Content.ReadAsStringAsync());
if (result["error"] != null)
{
throw new InvalidOperationException("An error occurred while retrieving an access token.");
}
return result;
}
My OpenIddictApplications is generated when an application is linked to the user account, so the ClientId and Secret is generated, when a login request is send to my API and retrieve the respective values.
I have folowed the oppeniddict documentation and I have included everything in my Startup.cs
This is my AuthorizationController:
[HttpPost("~/connect/token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsClientCredentialsGrantType())
{
// Note: the client credentials are automatically validated by OpenIddict:
// if client_id or client_secret are invalid, this action won't be invoked.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);
if (application == null)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidClient,
ErrorDescription = "The client application was not found in the database."
});
}
// Create a new authentication ticket.
var ticket = CreateTicket(request, application);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
I'm generating the AuthenticationTicket and returning this.
Any idea about what might be causing this kind of badrequest when I try to send the request to take my token?
This happens because you do not configure the client credentials flow on you Startup.cs.
See the example: https://github.com/openiddict/openiddict-samples/blob/dev/samples/ClientCredentialsFlow/AuthorizationServer/Startup.cs
Attention for line 52:
// Enable the client credentials flow.
options.AllowClientCredentialsFlow();
I'm following the instructions from this page. I've created myself a windows service and I'm stuck at requesting access token from Azure AD.
I managed to get an authorization code but I get the redirect_uri error when I POST. This is what my code looks like:
var dictionary = new Dictionary<string, string>
{
{ "resource", "https%3A%2F%2Foutlook.office365.com"},
{"client_id","Application ID from azure AD portal" }, //-is this ok?
{"client_secret","Object ID from azure AD portal" }, //-is this ok?
{"grant_type","authorization_code" },
{"redirect_uri",HttpUtility.UrlEncode("https://haw.trustteam.be/") },
{ "code","AQABAAIAAAAB..1AiAA"}
};
var content = new FormUrlEncodedContent(dictionary);
string requestUrl = "https://login.windows.net/common/oauth2/token"; // also tried with login.microsoftonline.com
using (HttpClient client = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Content = content;
using (HttpResponseMessage response = await client.SendAsync(request))
{
string responseString = await response.Content.ReadAsStringAsync();
return response.Content.ToString();
}
}
What am I doing wrong?
FormUrlEncodedContent function also help posting data in the HttpMessage body as url-encoded key/value pairs. So just remove the HttpUtility.UrlEncode function:
var dictionary = new Dictionary<string, string>
{
{ "resource", "https://outlook.office365.com"},
{"client_id","Application ID from azure AD portal" },
{"client_secret","Application key from azure portal" },
{"grant_type","authorization_code" },
{"redirect_uri","https://haw.trustteam.be/" },
{ "code","AQABAAIAAAAB..1AiAA"}
};
var content = new FormUrlEncodedContent(dictionary);
In addition , you can add client secret in Keys blade of your azure ad application . Please refer to this document .
I have implemented Multi tenant application using Azure Active Directory in Angular 4.After user logged into my application i'm able get user info.But user photo is not getting from the Active directory for that i have implemented Graph API like below snippet.
public Task<UserDto> getPhoto(TenantDto tenantDto)
{
var client = new HttpClient();
client.BaseAddress = new Uri(String.Format("https://graph.windows.net/{0}/users/{1}/thumbnailPhoto?api-version=1.6", tenantDto.tenantKey, tenantDto.email));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/jpeg"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tenantDto.token);
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
return null;
//Status status = response.Content.ReadAsAsync<Status>().Result;
//if (status.Code == 200)
// InBoundResponse = JsonConvert.DeserializeObject<InBoundCallResponse>(status.Data.ToString());
//return InBoundResponse;
}
else
{
return null;
}
}
Here tenantDto.token is nothing but a logged in user "token" While calling this Graph API i'm getting 401 (Unauthorized) error. I have tried all but no use.
I have changed Graph API setting s in Active Directory APP also like below attachment
Also i have tried like below code it's working only for single tenant
[Route("AdUserImage"), HttpGet]
public async Task<HttpResponseMessage> userImage()
{
var authContext = new AuthenticationContext("https://login.windows.net/sampletest.onmicrosoft.com/oauth2/token");
var credential = new ClientCredential(clientID, clientSecret);
ActiveDirectoryClient directoryClient = new ActiveDirectoryClient(serviceRoot, async () =>
{
var result = await authContext.AcquireTokenAsync("https://graph.windows.net/", credential);
return result.AccessToken;
});
var user = await directoryClient.Users.Where(x => x.UserPrincipalName == "balu#sampletest.onmicrosoft.com").ExecuteSingleAsync();
DataServiceStreamResponse photo = await user.ThumbnailPhoto.DownloadAsync();
using (MemoryStream s = new MemoryStream())
{
photo.Stream.CopyTo(s);
var encodedImage = Convert.ToBase64String(s.ToArray());
}
//string token = await HttpAppAuthenticationAsync();
Status status = new Status("OK");
status = new Status("Found", null, "User exists.");
return Request.CreateResponse(HttpStatusCode.OK, status, _jsonMediaTypeFormatter);
}
but i need to implement for Multi tenant app.
Any Answer Appreciated.
Thanks in Advance........!
Delegate-user token:
1 .Acquire the token via the implict flow:
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
2 .Call the Azure AD Graph
GET: https://graph.windows.net/{tenant}/me/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
Application token:
1 .Acquire the token via the client credentials flow
POST:https://login.microsoftonline.com/{tenant}/oauth2/token
grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}&resource=https%3A%2F%2Fgraph.windows.net
2 .Call the Azure AD Graph
GET:https://graph.windows.net/{tenant}/users/{upn}/thumbnailPhoto?api-version=1.6
Content-Type: image/jpeg
If you only to get the thumbnail photo of sign-in user for the multiple tenant, you should login-in with Azure AD first and acquire the access token for the delegate user and used that token to call Azure AD Graph REST. Difference between these two kinds of token, you can refer the links below:
Get access on behalf of a user
Get access without a user
I'm using Delegate-user token as per your explnation using below url
https://login.microsoftonline.com/{tenant}/oauth2/authorize?response_type=token&client_id={clientId}&redirect_uri={redirect_uri}&resource=https%3A%2F%2Fgraph.windows.net&nonce={nonce}
But still not able receiving but i'm able getting 200 status but token is not return.i have implemented like below
var client = new HttpClient();
client.BaseAddress = new Uri("https://login.microsoftonline.com/{TenantID}/oauth2/authorize?response_type=token&client_id={ClientID}&redirect_uri={ApplicationUrl}&resource=https%3A%2F%2Fgraph.windows.net&nonce=a9d7730c-79f3-4092-803a-07f346de2cdf");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
}
else
{
//return null;
}
It's not return the token.it is returning html content in success block