I'm following a sample provided at the Microsoft Graph repository on github. In it, it describes using the DelegateAuthenticationProvider which needs an access token.
Marc LaFleur's primer on auth flow specified a way to get the access token which I combined with guidance on making service-service authentication work.
Even though I receive what appears to be a valid access token, when I issue the command via the graph client, I get a ServiceException from Microsoft Graph stating,
"{
Code: InvalidAuthenticationToken
Message: Access token validation failure.
Inner error
}"
I haven't seen any other inner error.
What am I doing incorrectly?
This is the code I've come up:
var clientId = "[app-guid]";
var clientSecret = "[secret-from-app-dashboard]";
var resource = "[app-guid]";
var baseUrl = "https://login.microsoftonline.com";
var loginUrl = "/[tenant guid]/oauth2/token";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(baseUrl);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("resource", resource)
});
var result = client.PostAsync(loginUrl, content).Result;
string resultContent = result.Content.ReadAsStringAsync().Result;
var json = JObject.Parse(resultContent);
accessToken = (string)json["access_token"];
}
var header = new AuthenticationHeaderValue("Bearer", accessToken);
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = header;
return Task.FromResult(0);
}));
try
{
var users = await graphserviceClient.Users.Request().GetAsync();
var user = users.First();
}
catch (Microsoft.Graph.ServiceException servex)
{
Console.WriteLine(servex);
}
You set your resource to point to your website, but it should point to the external web resource you wish to access. Try this:
var resource = "https://graph.microsoft.com/";
Related
I have just started to use Asp.Net Core and I managed to create a mvc project. In This project I have created an API and it is secured with token based authorization.I have also used identity framework for user auhentication. Now I want to consume this API to perform CRUD operations with passing token but have no clear idea how to do that. After searching similar questions what I have tried is generate the token using user credentials (username, password) when user successfully logged in or registered and attach the generated token to header and as far as I know it will be passed through each subsequent request.
First I tried creating a method to call to generate the token after success login or registration. This includes in same controller which used for login and registration.
Token generate method
public string GenerateAuthToken(ApplicationUser applicationUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub, applicationUser.Id),
new Claim(type:JwtRegisteredClaimNames.Email, applicationUser.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
I call this after success user login and register,
public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = registerViewModel.Username,
Email = registerViewModel.Email};
var result = await _userManager.CreateAsync(user, registerViewModel.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
var token = GenerateAuthToken(user);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("bearer", token);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "User Registration Failed");
}
return View(registerViewModel);
}
When this executed, the token is successfully generated but does not attach the token. I do not know if I am doing any wrong here. But I found someone facing the same issue but has tried different way to achieve this. I think it is the correct way but not sure. Instead of generate the token on success login, have to generate it each api call. According to this solution I created another controller and action to generate the token.
public async Task<IActionResult> GetToken([FromBody] AuthViewModel authViewModel)
{
var user = _context.Users.FirstOrDefault(u => u.Email == authViewModel.Email);
if (user != null)
{
var signInResult = await _signInManager.CheckPasswordSignInAsync(user,
authViewModel.Password, false);
if (signInResult.Succeeded)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")
["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub,authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Email,
authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new
SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
return BadRequest("Invalid User");
}}
AuthViewModel
public class AuthViewModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
I added authViewModel to accept logged user credentials since I don't want add them manually, Then I have created another controller to perform the CRUD same as the above mentioned link Please note that I followed the solution mentioned below that page.
private async Task<string> CreateToken()
{
var user = await _userManager.GetUserAsync(User);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7015/Auth");
request.Content = JsonContent.Create(new AuthViewModel{
Email = user.Email, Password = user.PasswordHash
});
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request);
var token = await response.Content.ReadAsStringAsync();
HttpContext.Session.SetString("JwToken", token);
return token;
}
request.Content I added to match my solution since token should be generated using user credentials. But I have no idea how to pass the logged in user's credentials with the request. This does not work. It is not possible to access the user password.
This is how I called the token generate action to perform CRUD. And I use JQuery Ajax to call the GetAllSales endpoint.
public async Task<IActionResult> GetAllSales()
{
string token = null;
var strToken = HttpContext.Session.GetString("JwToken");
if (string.IsNullOrWhiteSpace(strToken))
{
token = await CreateToken();
}
else
{
token = strToken;
}
List<Sale> sales = new List<Sale>();
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get,
"http://localhost:7015/api/Sales");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiString = await response.Content.ReadAsStringAsync();
sales = JsonConvert.DeserializeObject<List<Sale>>(apiString);
}
Ok(sales);
}
This does not work. An exception throws
'System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)'
Please suggest me and show me how to achieve this correctly. I am expecting all of your help. Thank you.
System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'
This issue means you inject IHttpClientFactory in AccessApiController without registering the service in Program.cs.
Register IHttpClientFactory by calling AddHttpClient in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
I read a lot of questions and tested a lot of code but I couldn't solve my problem.
I want call rest API (that write with php code) in asp controller.
I tested my web service with postman app and get responds correctly from web service . But I can't get the right answer through asp request.
My problem: I get status code 200 for response(in asp controller) But the instructions on the web service are not executed(for example: don't register user). Where can I find the problem and get the data in the answer?
public async Task registerInShop()
{
try
{
ShopUserModel model = new ShopUserModel()
{
user_login = "ff",
user_pass = "v12315",
user_nicename = "",
user_url = "",
user_registered = "",
user_activation_key = "",
user_status = 0,
display_name = "ff",
};
JsonResult json = Json(model, JsonRequestBehavior.AllowGet);
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("domainurl/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
HttpResponseMessage response = await client.PostAsJsonAsync("shopktphp/register.php", json.Data);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{ // Get the URI of the created resource.
Uri returnUrl = response.Headers.Location;
var res = response.Content;
Console.WriteLine(returnUrl);
}
}
}
catch (Exception ex)
{
Elmah.ErrorLog.GetDefault(null).Log(new Elmah.Error(ex));
}
}
I successful finding answer of my question. I change only URI of base address and set by this: "domainurl/shopktphp/"
and set Post json async by "register.php"
client.BaseAddress = new Uri("domainurl/shopktphp/");
HttpResponseMessage response = await client.PostAsJsonAsync("register.php", json.Data);
I'm using following code to call Microsoft Graph API:
private static void GraphAPICallTest()
{
try
{
string authority = "https://login.microsoftonline.com/common/oauth2/token";
string resrouce = "https://graph.microsoft.com";
string clientId = "{clientid}";
string userName = "userid#test.com";
string password = "{userpassword}";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resrouce, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
return Task.FromResult(0);
}));
var a = graphserviceClient.Me.Request().GetAsync().Result;
Console.WriteLine("Success");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
}
Console.ReadLine();
}
When I use any user from Azure AD this code works just fine. but if i use personnel account it errors out with following error.
ADSTS65001: The user or administrator has not consented to use the application with ID 'c15fd791-7a49-424b-a938-2a9464476277' named 'OneDriveWebAppTest'. Send an interactive authorization request for this user and resource.
Trace ID: 5f31be27-dfdd-410b-af41-e769b2573d00
Correlation ID: 9c8c9f60-c56d-4845-a223-37f5232c6210
Timestamp: 2017-12-11 13:58:39Z
Yes, error is asking about consenting with that personnel user. But even after doing the consent with that user, the error still persist.
Not sure if i'm missing anything.
I have the "Server application accessing a web API" scenario.
The web site uses OIDC and authenticates no problem.
However, I have a use case for accessing some of the web API without a user context and for that, I use client_credentials.
The server app has a client ID and the secret key.
So assume the web API URL is:
https://my-pc/WebService/api/my-api/
The web API has the RP identifier:
https://my-pc/WebService/api/my-api/
Access control policy is:
Permit everyone
I have one claim rule:
c:[] => issue(claim = c);
Client permissions is set to:
"All clients" with scope of openid and user_impersonation.
The code is:
AuthenticationContext ac = new AuthenticationContext("https://my-adfs/adfs/", false);
// ClientCredential contains client_id and secret key
AuthenticationResult result = await ac.AcquireTokenAsync("https://my-pc/WebService/api/my-api/", clientCredential);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://my-pc/WebService/api/my-api/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("foo", "blah"), new KeyValuePair<string, string>("foo1", "blah1") });
request.Content = content;
HttpResponseMessage response = await client.SendAsync(request);
ADFS returns the access token no problem but when I call the web api, I keep getting a 401 - Unauthenticated.
Any ideas?
Finally figured this out and wrote it up.
The two pieces of code:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
ClientCredential clientCredential = new ClientCredential(clientId, secretKey);
AuthenticationContext ac = new AuthenticationContext("https://my-adfs/adfs/", false);
AuthenticationResult result = await ac.AcquireTokenAsync("https://my-pc/WebService",
clientCredential);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
"https://my-pc/WebService/api/my-api/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string,
string>("foo", "blah"), new KeyValuePair<string, string>("foo1", "blah1") });
request.Content = content;
HttpResponseMessage response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
// etc
And in Startup.Auth.cs
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters()
{
SaveSigninToken = true,
ValidAudience = "https://my-pc/WebService"
},
MetadataEndpoint = "https://my-adfs/FederationMetadata/2007-06/FederationMetadata.xml"
});
Took me a while!
Since you don't get the user principal in the access token, you have no user session attached to a principal. When the Web API looks for the principal in the cookie(or Authorization header if you're sending access token) it doesn't find the principal and you will still be an anonymous user.
My application shows dashboard of my power bi account for all users, I am authorizing the Azure Active Directory through a dialog to get an access token. Can I hard code my credentials and get access token without using the authorization dialog.
Code. It works but it is using the authorization dialog.
var #params = new NameValueCollection
{
{"response_type", "code"},
{"client_id", Properties.Settings.Default.ClientID},
{"resource", "https://analysis.windows.net/powerbi/api"},
{"redirect_uri", "http://localhost:13526/Redirect"}
};
var queryString = HttpUtility.ParseQueryString(string.Empty);
queryString.Add(#params);
string authorityUri = "https://login.windows.net/common/oauth2/authorize/";
var authUri = String.Format("{0}?{1}", authorityUri, queryString);
Response.Redirect(authUri);
Redirect.aspx
string redirectUri = "http://localhost:13526/Redirect";
string authorityUri = "https://login.windows.net/common/oauth2/authorize/";
string code = Request.Params.GetValues(0)[0];
TokenCache TC = new TokenCache();
AuthenticationContext AC = new AuthenticationContext(authorityUri, TC);
ClientCredential cc = new ClientCredential
(Properties.Settings.Default.ClientID,
Properties.Settings.Default.ClientSecret);
AuthenticationResult AR = AC.AcquireTokenByAuthorizationCode(code, new Uri(redirectUri), cc);
Session[_Default.authResultString] = AR;
Response.Redirect("/Default.aspx");
Default.aspx
string responseContent = string.Empty;
System.Net.WebRequest request = System.Net.WebRequest.Create(String.Format("{0}dashboards", baseUri)) as System.Net.HttpWebRequest;
request.Method = "GET";
request.ContentLength = 0;
request.Headers.Add("Authorization", String.Format("Bearer {0}", authResult.AccessToken));
using (var response = request.GetResponse() as System.Net.HttpWebResponse)
{
using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
{
responseContent = reader.ReadToEnd();
PBIDashboards PBIDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseContent);
}
}
I did this once without using ADAL. For Power BI as well, since they don't offer application permissions, only delegated.
Note: This method won't work if the user has MFA enabled, their password has expired etc.
In general you'll want to use interactive flows.
You can even have a bootstrapping process where the user logs in interactively and you store the refresh token received.
That refresh token can then be used in the background as long as it works.
What you need to is call the AAD token endpoint with grant_type=password. You will specify the username and password, as well as the client id, client secret and resource URI in form parameters.
Here is the function I wrote:
private async Task<string> GetAccessToken()
{
string tokenEndpointUri = Authority + "oauth2/token";
var content = new FormUrlEncodedContent(new []
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("password", Password),
new KeyValuePair<string, string>("client_id", ClientId),
new KeyValuePair<string, string>("client_secret", ClientSecret),
new KeyValuePair<string, string>("resource", PowerBiResourceUri)
}
);
using (var client = new HttpClient())
{
HttpResponseMessage res = await client.PostAsync(tokenEndpointUri, content);
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
return tokenRes.AccessToken;
}
}
Authority here is https://login.microsoftonline.com/tenant-id/. Here is the response class I'm using:
class AzureAdTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}
I hope using UserCreadential you have give username and password of azure subscription and you can get AccessToken and call your api. i hope it should helps you.
string ResourceUrl="https://analysis.windows.net/powerbi/api";
string ClientId=Properties.Settings.Default.ClientID;//as per your code
AuthenticationContext authenticationContext = new AuthenticationContext(Constants.AuthString, false);
UserCredential csr = new UserCredential("your-username", "password");
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(ResourceUrl,ClientId, usr);
string token = authenticationResult.AccessToken;