I am trying to fetch AD groups from the Graph API using the groups:src1 endpoint value that I receive from _claims_sources in the JWT access token.
My client is using Client Credentials and can fetch info for all users and groups in the AD.
This is how I set it up:
private async Task<IList<Group>> GetUserGroupsAsync(string endpoint)
{
// Endpoint is from the claims in a previous OIDC request
// Setup a client if it does not exist
PrepareGraphApiClient();
// I can fetch my groups using the methods in the client
// var groupPage = await graphServiceClient.Me.MemberOf.Request().GetAsync();
// This is where I would like to use the resource URL received from the
// claims I receive in a previous OIDC request
var groupPageFromUrl = ???
...
}
private void PrepareGraphApiClient()
{
if (graphServiceClient != null) return;
try
{
AuthenticationContext authority = new AuthenticationContext(oidcOptions.Authority);
ClientCredential clientCredential = new ClientCredential(oidcOptions.ClientId, oidcOptions.ClientSecret);
var graphApiResource = "https://graph.microsoft.com";
AuthenticationResult authenticationResult = authority.AcquireTokenAsync(graphApiResource, clientCredential).Result;
graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Add token to outgoing requests
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
}));
}
catch (Exception ex)
{
logger.LogDebug($"Could not create the graph client {ex}");
throw;
}
}
Can I use the resource URL from the claims with the GraphServiceClient or do I have to set up an HttpClient to make the request?
The endpoint identified in _claim_sources.src1 is for Azure AD Graph, so the token you use needs to be for the Azure AD Graph API (https://graph.windows.net), not for Microsoft Graph (https://graph.microsoft.com). That also means you can't use the Microsoft Graph SDK, as the API requests and responses are fundamentally different.
You have two options:
(Recommended) Use the fact that an endpoint is given only as an indication that you have to do the lookup, and make an equivalent call to Microsoft Graph using the Microsoft Graph SDK:
var memberOfIds = await graphServiceClient
.Users[userObjectId] # from the 'oid' in access token
.GetMemberObjects(securityEnabledOnly: true) # true if groupMembershipClaims is "SecurityGroup", false if it's "All"
.Request()
.PostAsync();
Use the endpoint given and build your own requests to Microsoft Graph using (for example) HttpClient. A quick'n'dirty example:
using (var client = new HttpClient())
{
# Always get the token right before you use it. ADAL will take care of getting a new one
# if needed, or using an existing cached token if not.
authenticationResult =
authority.AcquireTokenAsync("https://graph.windows.net", clientCredential)
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
# Make the API request to Azure AD Graph. Note we have to include the api-version and
# the request body.
HttpResponseMessage response = await client.PostAsync(
$"{src}?api-version=1.6", new StringContent(
"{'securityEnabledOnly': true}", # true if groupMembershipClaims is "SecurityGroup", false if it's "All"
UnicodeEncoding.UTF8,
"application/json"));
if (response.IsSuccessStatusCode)
{
# Note we're deserializing as if it were a Microsoft Graph response to getMemberObjects,
# rather than an Azure AD Graph response. Though it works, this is somewhat risky, and
# it would be more correct to explicitly define the form of an Azure AD Graph response.
var memberOfIds = JsonConvert.DeserializeObject<DirectoryObjectGetMemberObjectsCollectionResponse>(
await response.Content.ReadAsStringAsync()).Value;
}
}
As a side note, I notice that you have the call to acquire a token outside of the DelegateAuthenticationProvider you've given to the Microsoft Graph library. You should put the AcquireTokenAsync call inside, so that it retrieves a fresh token for each and every Microsoft Graph request. The library (ADAL) will take care of using a cached token (if one is available), or making a new token request (if none are available or if those available are expired). Something like this:
graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Get fresh token
AuthenticationResult authenticationResult =
await authority.AcquireTokenAsync(graphApiResource, clientCredential);
// Add token to outgoing requests
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
}));
Related
Recently I came up with an issue that I have a .NET Web API which needs to connect to SharePoint Online. In the Azure AD, I have provided all permission to the AppId "AllSites.Manage.All", AllSites.Read.All etc. I used CSOM library to pass the token to the sharepoint. But once I am trying to execute query on the clientcontext received, It is throwing 401 UnAuthorized error
private async Task<ClientContext> GetClientContextWithAccessToken1(string targetUrl)
{
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] {"Files.ReadWrite.All", "Sites.Manage.All", "AllSites.Read"});
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await httpClient.GetAsync($"https://graph.microsoft.com/beta/me");
if(response.StatusCode == HttpStatusCode.OK)
{
var content = await response.Content.ReadAsStringAsync();
}
using(ClientContext clientContext = new ClientContext(targetUrl))
{
clientContext.ExecutingWebRequest +=
delegate (object oSender, WebRequestEventArgs webRequestEventArgs)
{
webRequestEventArgs.WebRequestExecutor.RequestHeaders["Authorization"] =
"Bearer " + accessToken;
};
return clientContext;
}
}
For the request to query https://graph.microsoft.com/beta/me , one of the below permissions is required to be granted consent from admin. like Directory.Read.All , User.Read.All ...
Also please make sure to add Sites.Read.All or Sites.ReadWrite.All Application permission in your registrated AAD Application and do admin consent for it before you getting token to access sharepoint sites.
If you're using v2 endpoint, please go to below URL in your internet browser to do admin grant:
https://login.microsoftonline.com/{yourtenant}/adminconsent?client_id={ applicationid /clientId }&state=123&redirect_uri={redirect uri of your app}
and sign in with Global administrator account and accept this permission.
Reference:
azure-app-cannot-access-sharepoint-online-sites
If you are calling Microsoft Graph API endpoints you should avoid using csom.
AllSites.Manage.All, AllSites.Read.All etc. permissions are related to SharePoint and CSOM and they will not work for Graph API endpoints.
For Graph API you need to acquire different token or better option is to use Microsoft Graph Client Library for .NET in your case.
I've scoured stackoverflow looking for ways to make synchronous API calls in Blazor WASM, and come up empty. The rest is a fairly length explanation of why I think I want to achieve this, but since Blazor WASM runs single-threaded, all of the ways I can find to achieve this are out of scope. If I've missed something or someone spots a better approach, I sincerely appreciate the effort to read through the rest of this...
I'm working on a Blazor WASM application that targets a GraphQL endpoint. Access to the GraphQL endpoint is granted by passing an appropriate Authorization JWT which has to be refreshed at least every 30 minutes from a login API. I'm using a 3rd party GraphQL library (strawberry-shake) which utilizes the singleton pattern to wrap an HttpClient that is used to make all of the calls to the GraphQL endpoint. I can configure the HttpClient using code like this:
builder.Services
.AddFxClient() // strawberry-shake client
.ConfigureHttpClient((sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql"); // GraphQL endpoint
var token = "[api token]"; // token retrieved from login API
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
The trick now is getting the API token from the login API at least every 30 minutes. To accomplish this, I created a service that tracks the age of the token and gets a new token from the login API when necessary. Pared down, the essential bits of the code to get a token look like this:
public async Task<string> GetAccessToken()
{
if ((_expirationDateTime ?? DateTime.Now).AddSeconds(-300) < DateTime.Now)
{
try
{
var jwt = new
{
token =
"[custom JWT for login API validation]"
};
var payload = JsonSerializer.Serialize(jwt);
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var postResponse = await _httpClient.PostAsync("https://[login API url]/login", content);
var responseString = await postResponse.Content.ReadAsStringAsync();
_accessToken = JsonSerializer.Deserialize<AuthenticationResponse>(responseString).access_token;
_expirationDateTime = DateTime.Now.AddSeconds(1800);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
return _accessToken;
}
So, now I need to wire this up to the code which configures the HttpClient used by the GraphQL service. This is where I'm running into trouble. I started with code that looks like this:
// Add login service
builder.Services.AddSingleton<FxAuthClient>();
// Wire up GraphQL client
builder.Services
.AddFxClient()
.ConfigureHttpClient(async (sp, client) =>
{
client.BaseAddress =
new Uri(
"https://[application url]/graphql");
var token = await sp.GetRequiredService<FxAuthClient>().GetAccessToken();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
});
This "works" when the application is loaded [somewhat surprisingly, since notice I'm not "await"ing the GetAccessToken()]. But the behavior if I let the 30 minute timer run out is that the first attempt I make to access the GraphQL endpoint uses the expired token and not the new token. I can see that GetAccessToken() refreshes expired token properly, and is getting called every time I utilize the FxClient, but except for the first usage of FxClient, the GetAccessToken() code actually runs after the GraphQL request. So in essence, it always uses the previous token.
I can't seem to find anyway to ensure that GetAccessToken() happens first, since in Blazor WASM you are confined to a single thread, so all of the normal ways of enforcing synchronous behavior fails, and there isn't an asynchronous way to configure the FxClient's HttpClient.
Can anyone see a way to get this to work? I'm thinking I may need to resort to writing a wrapper around the strawberry FxClient, or perhaps an asynchronous extension method that wraps the ConfigureHttpClient() function, but so far I've tried to avoid this [mostly because I kept feeling like there must be an "easier" way to do this]. I'm wondering if anyone knows away to force synchronous behavior of the call to the login API in Blazor WASM, sees another approach that would work, or can offer any other suggestion?
Lastly, it occurs to me that it might be useful to see a little more detail of the ConfigureHttpClient method. It is autogenerated, so I can't really change it, but here it is:
public static IClientBuilder<T> ConfigureHttpClient<T>(
this IClientBuilder<T> clientBuilder,
Action<IServiceProvider, HttpClient> configureClient,
Action<IHttpClientBuilder>? configureClientBuilder = null)
where T : IStoreAccessor
{
if (clientBuilder == null)
{
throw new ArgumentNullException(nameof(clientBuilder));
}
if (configureClient == null)
{
throw new ArgumentNullException(nameof(configureClient));
}
IHttpClientBuilder builder = clientBuilder.Services
.AddHttpClient(clientBuilder.ClientName, (sp, client) =>
{
client.DefaultRequestHeaders.UserAgent.Add(
new ProductInfoHeaderValue(
new ProductHeaderValue(
_userAgentName,
_userAgentVersion)));
configureClient(sp, client);
});
configureClientBuilder?.Invoke(builder);
return clientBuilder;
}
I'm working on a project where i need to get the list of users from a restful server. However, My code isn't working as I'm not getting the intended result. The rest server uses a JWT token which means i need to be authorized before i can make a request. Please how do i do this in xamarin forms.
below is my code:
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", accessToken);
var json = await client.GetStringAsync(ConstantsValue.BaseAddress + "Users");
var users = JsonConvert.DeserializeObject<List<AddedUsers>>(json);
return users;
}```
I have a bit of a head-scratcher for updating a refresh tokens in a certain situation with a single page application making multiple api calls at the same time. I have an SPA which has a stack that consists of the following.
Html/JS SPA -> MVC Application -> WebAPI
I make use of the Hybrid flow, when a user logs onto the page I store the id_token the access_token and the refresh_token in the session cookie.
I use a HttpClient which has two DelegatingHandlers to talk to the web API. One of the delegating handlers simply adds the access token to the Authorization header. The other one runs before this and checks the lifetime left on the access token. If the access token has a limited amount of time left the refresh_token is used to get new credentials and save them back to my session.
Here is the code for the OidcTokenRefreshHandler.
public class OidcTokenRefreshHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly OidcTokenRefreshHandlerParams _handlerParams;
public OidcTokenRefreshHandler(IHttpContextAccessor httpContextAccessor, OidcTokenRefreshHandlerParams handlerParams)
{
_httpContextAccessor = httpContextAccessor;
_handlerParams = handlerParams;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
var handler = new JwtSecurityTokenHandler();
var accessTokenObj = handler.ReadJwtToken(accessToken);
var expiry = accessTokenObj.ValidTo;
if (expiry - TimeSpan.FromMinutes(_handlerParams.AccessTokenThresholdTimeInMinutes) < DateTime.UtcNow )
{
await RefreshTokenAsync(cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private async Task RefreshTokenAsync(CancellationToken cancellationToken)
{
var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(_handlerParams.OidcAuthorityUrl, cancellationToken);
if (discoveryResponse.IsError)
{
throw new Exception(discoveryResponse.Error);
}
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = _handlerParams.OidcClientId,
ClientSecret = _handlerParams.OidcClientSecret,
RefreshToken = refreshToken
}, cancellationToken);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResponse.IdentityToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResponse.AccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResponse.RefreshToken
}
};
// Sign in the user with a new refresh_token and new access_token.
var info = await _httpContextAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.StoreTokens(tokens);
await _httpContextAccessor.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
The problem is that many calls hit this at roughly the same time. All of these calls will then hit the refresh endpoint at the same time. They will all retrieve new valid access tokens and the application will continue to work. However if 3 requests happen at the same time, three new refresh tokens will be created and only one of these will be valid. Due to the asynchronous nature of the application I have no guarantee that the refresh token stored in my session is actually the latest refresh token. The next time I need to refresh the refresh token may be invalid (and often is).
My thoughts on possible solutions so far.
Lock at the point of checking the access token with a Mutex or similar. However this has the potential to block when it is being used by a different user with a different session (to the best of my knowledge). It also doesn't work if my MVC app is across multiple instances.
Change so the refresh tokens remain valid after use. So it doesn't matter which one of the three gets used.
Any thoughts on which of the above is better or has anyone got a really clever alternative.
Many Thanks!
When all your requests come from the same SPA, the best should be to sync them in the browser and get rid of the problem serverside. Each time your client code requires a token, return a promise. The same promise instance to all requests, so they all get resolved with the only request to the server.
Unfortunately if you proxy all the requests through your local API and never pass your bearer to the SPA, my idea wouldn't work.
But if you keep your refresh token absolutely secure (never send it to the front), I can't see any problem to make it reusable. In that case you can switch on sliding option as excellently described here to perform less renewal requests.
I'm having problems in retrieving access token of an authenticated user. below is my configuration
ASP.NET MVC 5 Client:
OpenIdConnect
IdentityServer3 libraries
ResponseType = "code id_token"
ASP.NET Core Identity Server:
IdentityServer4 libraries
Client Config: AllowedGrantTypes =
GrantTypes.HybridAndClientCredentials,
I'm trying to get the access token in my client using this:
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(TokenEndpoint, "clientid", "secret");
var response = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
},
I used this reference for above implementation - https://github.com/IdentityServer/IdentityServer3/issues/2457
but the properties in the response has null values. I need the access token so that the user logged in the client can access the api. Below is another way that i'm trying to retrieve the access token:
public async Task<ActionResult> CallApiUsingUserAccessToken()
{
var user = User as ClaimsPrincipal;
var accessToken = user.FindFirst("access_token").Value;
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await client.GetStringAsync("http://localhost:6001/api/values");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
however, user.FindFirst("access_token").Value; is null. I'm thinking of migrating my MVC client to Core because I've tried the IdentityServer4 version in an asp.net core but that seems to be a big migration to my part. Thank you.
[updated]
It never occured to me that the endpoints in the IdentityServer3 differs from IDS4. I did have to change var tokenClient = new TokenClient(TokenEndpoint, "client", "secret"); to var tokenClient = new TokenClient("http://localhost:9000/connect/token", "client", "secret") since TokenEndpoint in IDS3 is http://localhost:9000/core/connect/token which the endpoint "core" does not exist in IDS4. I'm able to get the access token in this line var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri); but after authorization, i'm still getting nullreference exception to this var accessToken = user.FindFirst("access_token").Value; line of code.
Given the IdentityServer 4 documentation on
Switching to Hybrid Flow and adding API Access back
and an example client from IdentityServer3.Samples
MVC OWIN Client (Hybrid)
you should be able to setup a working environment.
To support debugging you should always do proper response handling as shown in example below and copied from example client. Add any response errors to your question.
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
Constants.TokenEndpoint,
"mvc.owin.hybrid",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
Finally I recommend to add code for all important parts of an IdentityServer3/4 based setup - because the truth is usually burried in the details.
According to these posts, https://github.com/IdentityServer/IdentityServer3/issues/2457 & https://github.com/IdentityServer/IdentityServer3/issues/2015#issuecomment-172623173, it is a good practice to not include the access token in the claims. Hence, I followed his example, https://github.com/Mich-b/IdentityServerTMLClient/blob/master/IdentityServerTMLClient/Startup.cs, in which the access token is added in the Http Session storage.