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
Related
I am adding CF Turnstile recaptcha to my asp.net core web api for our contact us form and I am curious what IP address I should be using for this verification process. My code is as follows:
var dictionary = new Dictionary<string, string>
{
{ "secret", reCaptchaKey },
{ "response", customerInquiry.Token }
};
var postContent = new FormUrlEncodedContent(dictionary);
HttpResponseMessage recaptchaResponse = null;
string stringContent = "";
// Call recaptcha api and validate the token
using (var http = new HttpClient())
{
recaptchaResponse = await http.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", postContent);
stringContent = await recaptchaResponse.Content.ReadAsStringAsync();
}
The example code on CF shows the following for their node.js ( I assume) implementation:
formData.append('secret', SECRET_KEY);
formData.append('response', token);
formData.append('remoteip', ip);
The following .NET Core method returns BadRequest error:invalid_grant
However not always, only in the middle of a session - not sure what else is needed. The request is made from a Blazor App:
private async Task<TokenResponse> RefreshAccessToken()
{
string authority = _configuration.GetValue("Authority", "url...");
using (HttpClient serverClient = _httpClientFactory.CreateClient())
{
var discoveryDocument = await serverClient.GetDiscoveryDocumentAsync(authority);
var refreshToken = _tokenProvider.RefreshToken;
using (HttpClient refreshTokenClient = _httpClientFactory.CreateClient())
{
TokenResponse tokenResponse = await refreshTokenClient.RequestRefreshTokenAsync(
new RefreshTokenRequest
{
Address = discoveryDocument.TokenEndpoint,
RefreshToken = refreshToken,
ClientId = "client id ...",
ClientSecret = "secret ..."
});
return tokenResponse;
}
}
}
This is the request message:
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;
}
I'm trying to convert my token string to jwt token using JwtSecurityTokenHandler. But it's getting error that saying
IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII is hidden]'.\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
How can I solve this issue?
Here is my token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwibmJmIjoxNTUwNjM3NzcxLCJleHAiOjE1NTA2Mzg5NzEsImlhdCI6MTU1MDYzNzc3MX0.tUcoyoHgkrX3rDKl0cRLd9FwLtRprQpgYepMoiekixY
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
Calling web api
using (HttpClient client = new HttpClient())
{
string path = "UserMaintenance/ValidateUserId?userid=" + txtUsername.Text.Trim().ToString();
client.BaseAddress = new Uri(GlobalData.BaseUri);
client.DefaultRequestHeaders.Add("Authorization", "Bearer" + GlobalData.Token);
HttpResponseMessage response = client.GetAsync(path).Result;
if (response.IsSuccessStatusCode)
{
var value = response.Content.ReadAsStringAsync().Result;
isValid = JsonConvert.DeserializeObject<bool>(value);
}
}
Here is my GetPrincipal method
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken securityToken;
var principal = handler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception ex)
{
return null;
}
}
This is how I do it and it works for me:
var token = new System.IdentityModel.Tokens.JwtSecurityToken(jwt);
The above line works for System.IdentityModel.Tokens.Jwt package version 4.0.0.
As #Nick commented, in the latest versions of the package, the JwtSecurityToken does not exist in the previous namespace anymore, instead it exists in System.IdentityModel.Tokens.Jwt so you need to write:
var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(jwt);
Unless your token is not well-formed. It would be better if you share the token too.
Update:
You also need to remove the word "Bearer " from the beginning of the token (If you haven't):
var jwt = context.Request.Headers["Authorization"].Replace("Bearer ", string.Empty);
at version 5.6.0.0 - currently is the latest version
can use similar code as in #thilim9's question.
var tokenId = identity.Claims.SingleOrDefault(c => c.Type == "id_token")?.Value;
var handler = new JwtSecurityTokenHandler();
JwtSecurityToken token = handler.ReadJwtToken(tokenId);
For .net framework 4.5.1 I remove my custom key while generating token and use default values in claims of JwtRegisteredClaimNames.
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.GivenName, Data.UserName),
new Claim(JwtRegisteredClaimNames.Prn,Data.Password),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
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