I am trying to get token from using Azure Resource Manager API but getting 401-Unauthorized in response.I have my code as below :
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", client_Id, client_secret))));
var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var response = client.PostAsync("https://login.windows.net/subscriptionId/oauth2/token", content);
According to your code, you could refer to the following code to retrieve your token:
var client = new HttpClient();
var content = new FormUrlEncodedContent(new KeyValuePair<string, string>[]{
new KeyValuePair<string, string>("resource", "https://management.core.windows.net/"),
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", "{ClientID}"),
new KeyValuePair<string, string>("client_secret", "{ClientSecret}")
});
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsync("https://login.windows.net/{TennantID}/oauth2/token", content);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Result:
For more details, you could refer to this blog about using the Azure ARM REST API – Get Access Token.
I dont fully understand your code but i know how to construct ARM API calls so i can help you out with the core facts.
What jumps out at me is the fact that your POST URL looks wrong:
you should be using https://login.microsoftonline.com/ - check out the following blogpost Simplifying our Azure AD Authentication Flows
There needs to be the tenantID in your POST uri, not the subscriptionID. Access to subscriptions is managed through assigning RBAC Roles to the serivceprincipal created for the AzureAD App
Here is an example call. I use Postman to check if my constructed calls use the correct values and parameters:
Request
POST /[YOURTENANTID]/oauth2/token?api-version=1.0 HTTP/1.1
Host: login.microsoftonline.com
Cache-Control: no-cache
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue
Postman-Token: [token]
grant_type=client_credentials&client_id=[YOURCLIENTID]&client_secret=[YOUR-URLENCODED-Secret]&resource=https://management.azure.com/
response:
{
"token_type": "Bearer",
"expires_in": "3599",
"ext_expires_in": "0",
"expires_on": "1485695000",
"not_before": "1485691100",
"resource": "https://management.azure.com/",
"access_token": "[TOKEN]"
}
Related
I have two APIs as registered applications on Azure with authentication.
I want one of the APIs to call the other as part of a workflow, without a user.
My understanding of this is to use a client secret, and I successfully get a bearer token with the following call:
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
_httpClient.DefaultRequestHeaders.Add("Accept", "*/*");
var Parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("scope", "api://APPID/.default"),
new KeyValuePair<string, string>("client_secret", APPSECRET),
new KeyValuePair<string, string>("client_id", "APPID
};
var Request = new HttpRequestMessage(HttpMethod.Get, "https://login.microsoftonline.com/TENANTID/oauth2/v2.0/token")
{
Content = new FormUrlEncodedContent(Parameters)
};
var result = await _httpClient.SendAsync(Request).Result.Content.ReadAsStringAsync();
But if I make an API call with the returned bearer token I get 403 Forbidden.
The app on Azure has SPA redirect URLs which makes it "public", is this the cause?
Would passing the bearer token from the front-end from the original API call to be used for the API-to-API call be bad practice?
I'm having an issue adding a user to Azure B2C via the Microsoft Graph Api with a custom claim. I have added a claim called "sample", and when I add a user via the registration that claim will be populated with the value that I enter.
However, I need to add users via code not with self-registration. I have the following code that will add a user
// create the connection
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var client = new GraphServiceClient(authProvider);
var user = new User
{
AccountEnabled = true,
DisplayName = "John Smith",
UserPrincipalName = $"john.smith#{tenantId}",
MailNickname = "john.smith",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = false,
Password = "P#ssword1"
},
};
var addedUser = await client.Users.Request().AddAsync(user);
This is the same, but using HTTP direct. Which again will add the users
POST /v1.0/users HTTP/1.1
Host: graph.microsoft.com
SdkVersion: postman-graph/v1.0
Content-Type: application/json
Authorization: Bearer [redacted]
User-Agent: PostmanRuntime/7.20.1
Accept: */*
Cache-Control: no-cache
Postman-Token: [redacted]
Host: graph.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 325
Connection: keep-alive
cache-control: no-cache
{
"accountEnabled": true,
"displayName": "Joe Blogs",
"mailNickname": "joe.blogs",
"userPrincipalName": "joe.blogs#[redacted].onmicrosoft.com",
"passwordProfile": {
"forceChangePasswordNextSignIn": true,
"password": "P#ssword1"
},
"passwordPolicies": "DisablePasswordExpiration"
}
As I say the above code will add a user, but I can't figure out how to add the custom claim at the same time. The first set of code is using the Microsoft.Graph nuget package. The second is taken from a direct http call in Postman.
I seem to be going round in circles when reading the documentation, can can't seem to see how to do it in the new B2C v2.
Anyone, any idea?
Cheers
Ok worked out how to do this. Need to add the user like above, then update it with the custom claim. They key is that claim has the name in the format of
extension_xxxxx_sample
Where the xxxxx has the application id of the b2c-extensions-app application, which is a in build application.
So once the user has been added then the following code will add the custom claim
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var client = new GraphServiceClient(authProvider);
var dictionary = new Dictionary<string, object>();
dictionary.Add("extension_xxxxx_sample", "abcd");
await client.Users[$"john.smith#{tenantId}"]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});
The key is that the b2c-extensions-app may have the application id of 7e4ff0cd-825a-47ba-a08c-b13a4244b4ce. However, you would add the claim with the following
extension_7e4ff0cd825a47baa08cb13a4244b4ce_sample
not with
extension_7e4ff0cd-825a-47ba-a08c-b13a4244b4ce_sample
I'm following this tutorial to setup a client / server using Azure B2C.
I think I've done everything correctly but I'm experiencing a couple of issues.
AccessToken is null, but IdToken is populated
When I try to access protected resources, the following code is executed after I sign in at https://login.microsoftonline.com. The following line fails because AccessToken is null:
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
TaskWebApp.Controllers.TaskController.cs (Client App):
public async Task<ActionResult> Index()
{
try
{
// Retrieve the token with the specified scopes
var scope = new string[] { Startup.ReadTasksScope };
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(Startup.ClientId, Startup.Authority, Startup.RedirectUri, new ClientCredential(Startup.ClientSecret), userTokenCache, null);
var user = cca.Users.FirstOrDefault();
if (user == null)
{
throw new Exception("The User is NULL. Please clear your cookies and try again. Specifically delete cookies for 'login.microsoftonline.com'. See this GitHub issue for more details: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/9");
}
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, user, Startup.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); //AccessToken null - crash
//request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.IdToken); //This does work however
}
...
}
Contents of result AquireTokenSilentAsync:
IdToken doesn't contain Scope permissions
If I use IdToken in place of AccessToken - I get a little further but I'm hitting a new stumbling block. It fails here:
TaskService.Controllers.TasksController.cs (WebAPI):
public const string scopeElement = "http://schemas.microsoft.com/identity/claims/scope";
private void HasRequiredScopes(String permission)
{
if (!ClaimsPrincipal.Current.FindFirst(scopeElement).Value.Contains(permission)) //Crashes here as token doesn't contain scopeElement
{
throw new HttpResponseException(new HttpResponseMessage
{
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = $"The Scope claim does not contain the {permission} permission."
});
}
}
And here is a screenshot of my ClaimsPrincipal.Current:
Any advice is appreciated.
Edit
Signin URL:
https://login.microsoftonline.com/te/turtlecorptesting.onmicrosoft.com/b2c_1_email/oauth2/v2.0/authorize?client_id=03ef2bd...&redirect_uri=https%3a%2f%2flocalhost%3a44316%2f&response_mode=form_post&response_type=code+id_token&scope=openid+profile+offline_access+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fread+https%3a%2f%2fturtlecorptesting.onmicrosoft.com%2fwrite&state=OpenIdConnect.AuthenticationProperties%3daDQntAuD0Vh=...&nonce=63655.....YWRmMWEwZDc.....
Under Azure AD B2C go to your application
Under API access click add, select your API and its scope(s)
You should now get AccessToken in your response.
What I am trying to do is to use the "Web API on-behalf-of flow" scenario Microsoft described in this article to create a web hook.
So I started with the Microsoft github example and made sure that I can successfully get the users profile via the Graph API.
Then I modified the code where it gets the users profile to create the web hook, so the code looks like this:
// Authentication and get the access token on behalf of a WPF desktop app.
// This part is unmodified from the sample project except for readability.
const string authority = "https://login.microsoftonline.com/mycompany.com";
const string resource = "https://graph.windows.net";
const string clientId = "my_client_id";
const string clientSecret = "my_client_secret";
const string assertionType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var user = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var authenticationContext = new AuthenticationContext(authority,new DbTokenCache(user));
var assertion = ((BootstrapContext) ClaimsPrincipal.Current.Identities.First().BootstrapContext).Token;
var userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null
? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value
: ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
var result = await authenticationContext.AcquireTokenAsync(resource,new ClientCredential(clientId,clientSecret),new UserAssertion(assertion,assertionType,userName));
var accessToken = result.AccessToken;
// After getting the access token on behalf of the desktop WPF app,
// subscribes to get notifications when the user receives an email.
// This is the part that I put in.
var subscription = new Subscription
{
Resource = "me/mailFolders('Inbox')/messages",
ChangeType = "created",
NotificationUrl = "https://mycompany.com/subscription/listen",
ClientState = Guid.NewGuid().ToString(),
ExpirationDateTime = DateTime.UtcNow + new TimeSpan(0, 0, 4230, 0)
};
const string subscriptionsEndpoint = "https://graph.microsoft.com/v1.0/subscriptions/";
var request = new HttpRequestMessage(HttpMethod.Post, subscriptionsEndpoint);
var contentString = JsonConvert.SerializeObject(subscription, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
request.Content = new StringContent(contentString, System.Text.Encoding.UTF8, "application/json");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await new HttpClient().SendAsync(request);
if (response.IsSuccessStatusCode)
{
// Parse the JSON response.
var stringResult = await response.Content.ReadAsStringAsync();
subscription = JsonConvert.DeserializeObject<Subscription>(stringResult);
}
The error I get from the response is:
{
"error":
{
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError":
{
"request-id": "f64537e7-6663-49e1-8256-6e054b5a3fc2",
"date": "2017-03-27T02:36:04"
}
}
}
The webhook creation code was taken straight from the ASP.NET webhook github sample project, which, I have also made sure that I can run successfully.
The same access token code works with the original user profile reading code:
// Call the Graph API and retrieve the user's profile.
const string requestUrl = "https://graph.windows.net/mycompany.com/me?api-version=2013-11-08";
request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await new HttpClient().SendAsync(request);
So I want to find out:
Is creating a webhook via the graph API using the on-behalf-of flow even supported? Not sure if this SO question is what I'm looking for here.
If it is supported, what am I missing here?
If it is not supported, is there an alternative to achieve it? E.g. is there anything from the existing Office 365 API that I can use?
"message": "Access token validation failure.",
The error means you got incorrect access token for the resource . According to your code ,you get the access token for resource :https://graph.windows.net( Azure AD Graph API) , But then you used that access token to access Microsoft Graph API(https://graph.microsoft.com) ,so access token validation failed .
I have download a MVC application from Git for AAD graph API. I ran this application but each time i am not getting expected result.
To find the error i run same api using postman and generated token below was the response.
{
"odata.error": {
"code": "Request_ResourceNotFound",
"message": {
"lang": "en",
"value": "Resource not found for the segment 'me'."
}
}
}
I am using below Get URL-
https://graph.windows.net/XXXXX/me?api-version=1.6
Also, to verify is it working with AAD Grapg api explorer. After log in everything is working fine.
Below is my code to call Grapg API-
// Get the access token from the cache
string userObjectID =
ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
.Value;
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
//AuthenticationContext authContext = new AuthenticationContext(authority,
// new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority, true);
result = await authContext.AcquireTokenAsync(graphResourceId.ToString(), credential);
var Token = result.AccessToken;
//// AcquireTokenSilentAsync
//result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
// new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// Call the Graph API manually and retrieve the user's profile.
string requestUrl = String.Format(
CultureInfo.InvariantCulture,
graphUserUrl,
HttpUtility.UrlEncode(tenantId));
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Return the user's profile in the view.
if (response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync();
profile = JsonConvert.DeserializeObject<UserProfile>(responseString);
}
Could you guys please tell me what is the problem with my code. Why it is working on AAD explorer not with Localhost.
To request the me endpoint of Azure AD Graph REST, we need to use the delegate token which represents the sign-in user.
The code above you acquire token using the Client Credential flow is request the access token which represents for the application which doesn't contain the info of sign-in user.
To achieve this in the MVC application, we need to acquire the token after you get the authorization code when users login. Next time, we can acquire the token from the token cache based on the login user.
Here is the code for your reference( code sample from here):
result = await authContext.AcquireTokenSilentAsync(graphResourceId, credential,
new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));