I am trying to write a .netcore API which gets a bearer token from third party Webapp. This .netcore API should access the Microsoft graph API and get the user group information back from Azure AD.
I was following the sample project https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore.
But unfortunately this uses AAD graph rather tha Microsoft graph API.
I tried to implement Graph API in the .netcore api project in the above sample.
Things I have tried
I have changed the AAD graph to Graph API in the AzureAdAuthenticationBuilderExtensions.cs(in the web app project)
options.Resource = "https://graph.microsoft.com";
Also I used the Microsoft.Graph nuget in the API project. And I am trying to create the GraphServiceClient using the code below
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var words = accessToken.Split(' ');
var token = words[1];
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}
And finally I am trying to access the user information using the code below,
public async Task<IEnumerable<Group>> GetGroupAsync(string accessToken)
{
var graphClient = GetClient(accessToken);
try
{
User me = await graphClient.Me.Request().GetAsync();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var user= await graphClient.Users["***"].Request().Expand("MemberOf").GetAsync();
var userEmail = "testemail#test.com";
var usergroup = await graphClient.Users[userEmail].GetMemberGroups(false).Request().PostAsync();
var groupList = new List<Group>();
foreach (var g in usergroup.CurrentPage)
{
var groupObject = await graphClient.Groups[g].Request().GetAsync();
groupList.Add(groupObject);
}
return groupList;
}
But when I try the code I am getting the error "Microsoft.Graph.ServiceException: Code: InvalidAuthenticationToken
Message: Access token validation failure.Inner error at Microsoft.Graph.HttpProvider."
Can somebody help me please?
Thanks in advance
The access token passed to GetGroupAsync is not correct , and i am confused why you need to split the token :
var words = accessToken.Split(' ');
var token = words[1];
But never mind , since you have modified options.Resource = "https://graph.microsoft.com"; ADAL will help you get access token for Microsoft Graph API in OnAuthorizationCodeReceived function , and save the tokens to cache .
To get the access token , you could use ADAL to get the token from cache :
AuthenticationResult result = null;
// Because we signed-in already in the WebApp, the userObjectId is know
string userObjectID = (User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
// Using ADAL.Net, get a bearer token to access the TodoListService
AuthenticationContext authContext = new AuthenticationContext(AzureAdOptions.Settings.Authority, new NaiveSessionCache(userObjectID, HttpContext.Session));
ClientCredential credential = new ClientCredential(AzureAdOptions.Settings.ClientId, AzureAdOptions.Settings.ClientSecret);
result = await authContext.AcquireTokenSilentAsync("https://graph.microsoft.com", credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Then you could pass that token to your function:
await GetGroupAsync(result.AccessToken);
Modify your GetClient function to delete the split part:
public GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
{
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.FromResult(0);
});
var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? new HttpProvider());
return graphClient;
}
Related
I have created a .net core console application to access the graph api. I created a authentication by using clientId and clientSecret of the Azure AD application
string tenantName = "MY.TENANT";
string authUrl = "https://login.microsoftonline.com/" + tenantName;
var clientId = "MYID";
var clientSecret = "MYSECRET";
AuthenticationContext authenticationContext = new AuthenticationContext(authUrl, false);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult;
authenticationResult = await authenticationContext.AcquireTokenAsync("https://graph.microsoft.com/", clientCred);
return authenticationResult.AccessToken;
After I get a valid token the call do a sharepoint list works fine and I get some data
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"{graphUrl}/sites/{siteId}/lists/MYLISTGUID/items?expand=fields");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
return responseString;
}
But if I call the Search API I get the following error: SearchRequest Invalid (Region is required when request with application permission.)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, $"{graphUrl}/search/query/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
}
}
}
};
request.Content = new StringContent(JsonConvert.SerializeObject(filter), Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
}
The same query by using the Graph Explorer works fine.
I found some posts around that tells something, that you can not call the search API by using the application credential but only by using delegation. In my case the api call is made by a service user and not by the user directly. I have to migrate a Sharepoint on Premise solution which access the search in that way.
Thanks for any input
You can get the region value by calling the following URL
https://yourtenant.sharepoint.com/_api/GeoTenantInstanceInformationCollection
Note: your tenant admin needs to call (copy&paste in the browser) this URL otherwise you will receive UnauthorizedAccessException with the message Current user is not a tenant administrator.
Then add region property with the value from the request above to your filter:
var filter = new
{
Requests = new[] {
new {
EntityTypes = new[] { "listItem" },
Query = new
{
QueryString = "Pio*"
},
Region = "guid"
}
}
};
Resources:
Search content with application permissions
I am working on an asp.net Web API and I have an web application that consumes this api.
Right now it is working perfectly since I don't have the [Authorize] part on my api controller.
So, if I want to secure this api, my web application will not be able anymore to fetch data from the API because it is not authorized.
So how can I send the token generated from my API to my web app and to allow it to fetch the needed data?
-I am using postman for testing my app;
-my api return jwt token;
-I am not really familiar with http headers.
My consuming web application controller :
public ActionResult Index()
{
IEnumerable<OperatorClass> OperatorObject = null;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://localhost:44304/api/");
var ApiOpController = client.GetAsync("data");
client.DefaultRequestHeaders.Add("Authorization", "Bearer"+"");
ApiOpController.Wait();
var resultDisplay = ApiOpController.Result;
if (resultDisplay.IsSuccessStatusCode)
{
var readTable = resultDisplay.Content.ReadAsAsync<IList<OperatorClass>>();
readTable.Wait();
OperatorObject = readTable.Result;
}
else
{
OperatorObject = Enumerable.Empty<OperatorClass>();
ModelState.AddModelError(String.Empty, "No records found");
}
return View(OperatorObject);
}
My web API controller
[Authorize]
[HttpGet]
public IHttpActionResult GetOperators()
{
SchoolEntity myEntity = new SchoolEntity ();
IList<OperatorClass> OperatorObject = myEntity.Operator.Include("Operator").Select(x => new OperatorClass()
{
name = x.name,
lastname = x.lastname,
mobile = x.mobile,
username = x.username,
password = x.password
}).ToList<OperatorClass>();
return Ok(OperatorObject);
}
string token = <Your token>
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
this should work for you.
To be able to use your MVC controller, you need to store the token when it returns.
One way to do it is to store it using Session.
Assuming you are using sign in to get the token, anytime you sign in successfully you can store the token using the session. See below.
//For brevity after successful login
string myToken = <token returned from api>
HttpContext.Session.SetString("token", myToken);
//other codes
then you can use this in all of your controllers.
public async Task<ActionResult> Index()
{
IEnumerable<OperatorClass> OperatorObject = null;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("https://localhost:44304/api/");
//note here
var token = HttpContext.Session.GetString("token");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
//ApiOpController.Wait();
var resultDisplay = await client.GetAsync("data");
if (resultDisplay.IsSuccessStatusCode)
{
var readTable = await resultDisplay.Content.ReadAsAsync<IList<OperatorClass>>();
//readTable.Wait();
OperatorObject = readTable;
}
else
{
OperatorObject = Enumerable.Empty<OperatorClass>();
ModelState.AddModelError(String.Empty, "No records found");
}
return View(OperatorObject);
}
xamarin forms with office 365 authentification
the authentification works fine but lately i receive this error message :
AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token.
with some research i find that i need to refresh the token. -->
https://support.workspace365.net/hc/en-us/articles/360010259114--RESOLVED-Technical-issue-Workspace-365
the question is how to refresh the token can anyone guide me.
Thank you
This my code :
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
AuthenticationResult authResult = null;
try
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var uri = new Uri(returnUri);
PlatformParameters platformParams = null;
android
platformParams = new PlatformParameters((Android.App.Activity)Forms.Context);
ios
Device.BeginInvokeOnMainThread(() =>
{
UIViewController controller = new UIViewController();
controller = UIApplication.SharedApplication.KeyWindow.RootViewController;
platformParams = new PlatformParameters(controller);
});
UserDialogs.Instance.HideLoading();
Authresult need to return the token so i can use it from office 365 authentification but instead i receive the message AADSTS54005: OAuth2 Authorization...
authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
authContext.TokenCache.Clear();
}
catch (Exception e)
{
Console.WriteLine("Execption : " + e.Message);
}
return authResult;
}
any help will be appreciated Thank you
I'm new with Xamarin.Forms, and I'm finding a way to store data in App like: token, username. Now, I'm trying to use SecureStorgae, but the function getValue doesn't work, and I have no idea why.
My code:
public async Task CheckLogin(string usernameString, string passwordString)
{
using (var client = new HttpClient())
{
string url = "myUrl";
var json = JsonConvert.SerializeObject(new { username = usernameString, password=passwordString });
HttpContent content = new StringContent(json,Encoding.UTF8, "application/json");
var response = await client.PostAsync(url,content);
if (response.IsSuccessStatusCode)
{
var rs = JsonConvert.DeserializeObject<LoginResult>(response.Content.ReadAsStringAsync().Result);
var token = rs.result.token;
CrossSecureStorage.Current.SetValue("SessionToken",token);
CrossSecureStorage.Current.SetValue("Username", rs.result.userName);
var token1 = CrossSecureStorage.Current.GetValue("SessionToken");
await Navigation.PushAsync(new Home());
}
}
}
When my app is running, I can't get the value of SessionToken.
in iOS
Open the Entitlements.plist file and make sure that "Enable Keychain Access Groups" is checked. Also ensure that in Project->Options->iOS Bundle Signing, the Entitlements.plist is selected in Custom Entitlements for iPhoneSimulator platform.
in Android
It is required that the password is set by the application prior to use.
SecureStorageImplementation.StoragePassword = "Your Password";
For more detail you can access here
I wrote an application which should read the groups of a user. I am using Asp.net core. I created an application inside the azure portal and granted all application permissions for GraphAPI and clicked on the Grant permission button. Then I used some code similar to WebApp-GroupClaims-DotNet to retrieve the users groups:
public async Task<IEnumerable<string>> GetGroupIdsFromGraphApiAsync(string userId)
{
var groupObjectIds = new List<string>();
// Acquire the Access Token
var credential = new ClientCredential(_configHelper.ClientId, _configHelper.AppKey);
var authContext = new AuthenticationContext(_configHelper.Authority);
var result = await authContext.AcquireTokenAsync(_configHelper.GraphResourceId, credential);
var accessToken = result.AccessToken;
var requestUrl =
$"{_configHelper.GraphResourceId}/{_configHelper.Domain}/users/{userId}/getMemberGroups?api-version=1.6";
// Prepare and Make the POST request
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = new StringContent("{\"securityEnabledOnly\":false}");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
var response = await client.SendAsync(request);
// Endpoint returns JSON with an array of Group ObjectIDs
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var groupsResult = (Json.Decode(responseContent)).value;
foreach (string groupObjectId in groupsResult)
groupObjectIds.Add(groupObjectId);
}
else
{
var responseContent = await response.Content.ReadAsStringAsync();
throw new WebException(responseContent);
}
return groupObjectIds;
}
Unfortunately I do always get the following response:
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
Is there no way for an application to query the AD for this information?
According to your code , you are making Azure ad graph api calls , Then you need to grant permission for Windows Azure Active Directory(azure ad graph api ) .
https://graph.windows.net is the endpoint for Azure AD Graph APi , in azure portal the name is Windows Azure Active Directory . https://graph.microsoft.com is the the endpoint for Microsoft Graph api , in azure portal the name is Microsoft Graph