How to delegate Identity from Web-Application to WebAPI - asp.net-core

I am trying to build a website, where the user logs in at the and can use an backend web-API.
Calls to the backend web-API will always be proxied by the frontend website, since the backend is not publicly available.
Back- and frontend are MVC 6 (or MVC Core?) projects based on ASP.net Core.
The frontend currently authenticates (successfully) by using OpenId-Connect.
The backend should use JwtBearerToken.
The authentication so far requests the response type is id_token code and the scope is openid profile.
After the roundtrip to the Auth-Server (ADFS 2016), I will end up in the AuthorizationCodeReceived-Event from ASP.NET, but I have no luck in exchanging the code for authorization token. I tried the following using ADAL:
public override async Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
await base.AuthorizationCodeReceived(context);
var clientCredential = new ClientCredential(context.Options.ClientId, context.Options.ClientSecret);
var oAuthContext = new AuthenticationContext(context.Options.Authority, false);
var oAuthResult = await oAuthContext.AcquireTokenByAuthorizationCodeAsync(context.Code, new Uri(context.RedirectUri), clientCredential);
}
I had to disable the authority validation (which I do not like) and I do not get results other than Http-Status 400.
I'd be happy for any advice how to move on.
Update
Further Investigation Shows, that the OpenIdConnect-Configuration allows to save auth and refresh Tokens into the Claims. Nevertheless I don't see the possibility to convert it in the first place.
I also tried exchanging the code by hand (PS: Invoke-WebRequest ...) but had no success. Perhaps this is a problem of ADFS TP4...

I've managed to get this scenario to work with TP4.
AuthorizationCodeReceived = async n =>
{
string code = n.Code;
AuthenticationContext ac = new AuthenticationContext(BaseAddress, false);
ClientCredential client = new ClientCredential("clientid", "secret");
string resourceId = "https://myservices/myapi";
AuthenticationResult ar = await ac.AcquireTokenByAuthorizationCodeAsync(code, new Uri("https://localhost:44300/"), client, resourceId);
}
You can then use the access token from a controller method like this:
AuthenticationContext ac = new AuthenticationContext(Startup.BaseAddress, false);
ClientCredential cred = new ClientCredential("clientid", "secret");
string resourceId = "https://myservices/myapi";
AuthenticationResult ar = ac.AcquireTokenSilent(resourceId, cred, UserIdentifier.AnyUser);
var client = new HttpClient();
client.SetBearerToken(ar.AccessToken);
var result = await client.GetStringAsync("http://localhost:2727/identity");

Related

C# HttpClient failing to make GET requests with Windows Authentication

I have a .NET Core 3.1 Api application with the following configuration of HttpClient. In Startup.cs
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddHttpClient("myapi", c =>
{
c.BaseAddress = new Uri(Configuration["endpoint"]);
c.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
IISDefaults.AuthenticationScheme, Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes($"{Configuration["username"]}:{Configuration["password"]}")));
});
I then try to make an HTTP call like this:
var client = clientFactory.CreateClient(clientName);
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
return await response.Content.ReadAsStringAsync();
however I always get an Unauthorized response when calling an internal api. Under Debug I have Windows authentication and Anonymous authentication both enabled.
With Postman my api calls go through, which verifies that I got the right credentials.
Can you suggest any alterations to make this work?
Instead of c.DefaultRequestHeaders.Authorization =, I'm having config like this
c.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
Credentials = new NetworkCredential("username", "password"),
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
PreAuthenticate = true
});
I guess this will not work as-is in your case, but I hope this can get you on track.

AddOpenIdConnect and Refresh Tokens in ASP.NET Core

I have added AddOpenIdConnect to the ConfigureServices method of my ASP.NET Core 3.1 Razor application. It works great until the token expires, then I get 401 responses from my IDP.
I have seen an example that shows a way to wire up refresh tokens manually.
But I am hesitant to do that. It seems super unlikely that the folks at Microsoft did not think about refresh tokens.
Does ASP.NET Core 3.1 have a way to have refresh tokens automatically update the access token?
Here is what I came up with. Since there are not very many examples that I could find on how to do refresh tokens in ASP.NET Core with cookies, I thought I would post this here. (The one I link to in the question has issues.)
This is just my attempt at getting this working. It has not been used in any production setting. This code goes in the ConfigureServices method.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Events = new CookieAuthenticationEvents
{
// After the auth cookie has been validated, this event is called.
// In it we see if the access token is close to expiring. If it is
// then we use the refresh token to get a new access token and save them.
// If the refresh token does not work for some reason then we redirect to
// the login screen.
OnValidatePrincipal = async cookieCtx =>
{
var now = DateTimeOffset.UtcNow;
var expiresAt = cookieCtx.Properties.GetTokenValue("expires_at");
var accessTokenExpiration = DateTimeOffset.Parse(expiresAt);
var timeRemaining = accessTokenExpiration.Subtract(now);
// TODO: Get this from configuration with a fall back value.
var refreshThresholdMinutes = 5;
var refreshThreshold = TimeSpan.FromMinutes(refreshThresholdMinutes);
if (timeRemaining < refreshThreshold)
{
var refreshToken = cookieCtx.Properties.GetTokenValue("refresh_token");
// TODO: Get this HttpClient from a factory
var response = await new HttpClient().RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = tokenUrl,
ClientId = clientId,
ClientSecret = clientSecret,
RefreshToken = refreshToken
});
if (!response.IsError)
{
var expiresInSeconds = response.ExpiresIn;
var updatedExpiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresInSeconds);
cookieCtx.Properties.UpdateTokenValue("expires_at", updatedExpiresAt.ToString());
cookieCtx.Properties.UpdateTokenValue("access_token", response.AccessToken);
cookieCtx.Properties.UpdateTokenValue("refresh_token", response.RefreshToken);
// Indicate to the cookie middleware that the cookie should be remade (since we have updated it)
cookieCtx.ShouldRenew = true;
}
else
{
cookieCtx.RejectPrincipal();
await cookieCtx.HttpContext.SignOutAsync();
}
}
}
};
})
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = oidcDiscoveryUrl;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.RequireHttpsMetadata = true;
options.ResponseType = OidcConstants.ResponseTypes.Code;
options.UsePkce = true;
// This scope allows us to get roles in the service.
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
// This aligns the life of the cookie with the life of the token.
// Note this is not the actual expiration of the cookie as seen by the browser.
// It is an internal value stored in "expires_at".
options.UseTokenLifetime = false;
options.SaveTokens = true;
});
This code has two parts:
AddOpenIdConnect: This part of the code sets up OIDC for the application. Key settings here are:
SignInScheme: This lets ASP.NET Core know you want to use cookies to store your authentication information.
*UseTokenLifetime: As I understand it, this sets an internal "expires_at" value in the cookie to be the lifespan of the access token. (Not the actual cookie expiration, which stays at the session level.)
*SaveTokens: As I understand it, this is what causes the tokens to be saved in the cookie.
OnValidatePrincipal: This section is called when the cookie has been validated. In this section we check to see if the access token is near or past expiration. If it is then it gets refreshed and the updated values are stored in the cookie. If the token cannot be refreshed then the user is redirected to the login screen.
The code uses these values that must come from your configuration file:
clientId: OAuth2 Client ID. Also called Client Key, Consumer Key, etc.
clientSecret: OAuth2 Client Secret. Also called Consumer Secret, etc.
oidcDiscoveryUrl: Base part of the URL to your IDP's Well Known Configuration document. If your Well Known Configuration document is at https://youridp.domain.com/oauth2/oidcdiscovery/.well-known/openid-configuration then this value would be https://youridp.domain.com/oauth2/oidcdiscovery.
tokenUrl: Url to your IDP's token endpoint. For example: https:/youridp.domain.com/oauth2/token
refreshThresholdMinutes: If you wait till the access token is very close to expiring, then you run the risk of failing calls that rely on the access token. (If it is 5 miliseconds from expiration then it could expire, and fail a call, before you get a chance to refresh it.) This setting is the number of minutes before expiration you want to consider an access token ready to be refreshed.
* I am new to ASP.NET Core. As such I am not 100% sure that those settings do what I think. This is just a bit of code that is working for me and I thought I would share it. It may or may not work for you.
As far as I know, there's nothing built-in in ASP.NET Core 3.1 to refresh access tokens automatically. But I've found this convenient library from the IdentityServer4 authors which stores access and refresh tokens in memory (this can be overriden) and refreshes access tokens automatically when you request them from the library.
How to use the library: https://identitymodel.readthedocs.io/en/latest/aspnetcore/web.html.
NuGet package: https://www.nuget.org/packages/IdentityModel.AspNetCore/.
Source code: https://github.com/IdentityModel/IdentityModel.AspNetCore.
I implemented token refresh in a .NET 7.0 sample recently. There has always been an option to refresh tokens and rewrite cookies, in many MS OIDC stacks, including older ones: Owin, .NET Core etc. It seems poorly documented though, and I had to dig around in the aspnet source code to figure out the cookie rewrite step. So I thought I'd add to this thread in case useful to future readers.
REFRESH TOKEN GRANT
First send a standards based refresh token grant request:
private async Task<JsonNode> RefreshTokens(HttpContext context)
{
var tokenEndpoint = "https://login.example.com/oauth/v2/token";
var clientId = "myclientid";
var clientSecret = "myclientsecret";
var refreshToken = await context.GetTokenAsync("refresh_token");
var requestData = new[]
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", refreshToken),
};
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("accept", "application/json");
var response = await client.PostAsync(tokenEndpoint, new FormUrlEncodedContent(requestData));
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonNode.Parse(json).AsObject();
}
}
REWRITE COOKIES
Then rewrite cookies, which is done by 'signing in' with a new set of tokens. A better method name might have been something like 'update authentication state'. If you then look at the HTTP response you will see an updated set-cookie header, with the new tokens.
Note that in a refresh token grant response, you may or may not receive a new refresh token and new ID token. If not, then supply the existing values.
private async Task RewriteCookies(JsonNode tokens, HttpContext context)
{
var accessToken = tokens["access_token"]?.ToString();
var refreshToken = tokens["refresh_token"]?.ToString();
var idToken = tokens["id_token"]?.ToString();
var newTokens = new List<AuthenticationToken>();
newTokens.Add(new AuthenticationToken{ Name = "access_token", Value = accessToken });
if (string.IsNullOrWhiteSpace(refreshToken))
{
refreshToken = await context.GetTokenAsync("refresh_token");
}
newTokens.Add(new AuthenticationToken{ Name = "refresh_token", Value = refreshToken });
if (string.IsNullOrWhiteSpace(idToken))
{
idToken = await context.GetTokenAsync("id_token");
}
newTokens.Add(new AuthenticationToken{ Name = "id_token", Value = idToken });
var properties = context.Features.Get<IAuthenticateResultFeature>().AuthenticateResult.Properties;
properties.StoreTokens(newTokens);
await context.SignInAsync(context.User, properties);
}
SUMMARY
Being able to refresh access tokens when you receive a 401 response from an API is an essential capability in any web app. Use short lived access tokens and then code similar to the above, to renew them with good usability.
Note that relying on an expiry time is not fully reliable. API token validation can fail due to infrastructure events in some cases. APIs then return 401 for access tokens that are not expired. The web app should handle this via a refresh, followed by a retry of the API request.
AddOpenIdConnect is used to configure the handler that performs the OpenID Connect protocol to get tokens from your identity provider. But it doesn't know where you want to save the tokens. It could be any of the following:
Cookie
Memory
Database
You could store the tokens in a cookie then check the token's expire time and refresh the tokens by intercepting the cookie's validation event (as the example shows).
But AddOpenIdConnect doesn't have the logic to control where the user want to store the tokens and automatically implement token refresh.
You can also try to wrap the middleware as the ADAL.NET/MSAL.NET to provide cache features and then you can acquire/refresh tokens silently.

Which is the correct flow to get current user's groups from Microsoft graph?

Hi I am implementing Groups based authorization to my web api. I have client application swagger. Through swagger I am logging in and calling web api. In web api I want to implement groups based authorization through Microsoft graph. When I logging through swagger I will get one token and I am passing to my webapi. If I am not wrong, Now I required one token to call Microsoft graph. So can I use same token to call microsoft graph? I confused my self and implemented client credential flow. Client credential flow will get token for the app(here user signed in token has nothing to do).
public static async Task<GraphServiceClient> GetGraphServiceClient()
{
// Get Access Token and Microsoft Graph Client using access token and microsoft graph v1.0 endpoint
var delegateAuthProvider = await GetAuthProvider();
// Initializing the GraphServiceClient
graphClient = new GraphServiceClient(graphAPIEndpoint, delegateAuthProvider);
return graphClient;
}
private static async Task<IAuthenticationProvider> GetAuthProvider()
{
AuthenticationContext authenticationContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
// ADAL includes an in memory cache, so this call will only send a message to the server if the cached token is expired.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResource, clientCred).ConfigureAwait(false);
var token = authenticationResult.AccessToken;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token.ToString());
return Task.FromResult(0);
});
return delegateAuthProvider;
}
Below code will return all the groups.
GraphServiceClient client = await MicrosoftGraphClient.GetGraphServiceClient();
var groupList = await client.Groups.Request().GetAsync();
but my requirement is to get current signed in users group. So can someone help me which flow I should use and In the above code only Is it possible to get current users group? Can someone help me in understanding these and implement correctly? Any help would be greatly appreciated. Thanks
As we have discussed before, you should call Microsoft Graph API from your webapi app.
So you should not use the same access token to call Microsoft Graph. You should specfy the Microsoft Graph endpoint (https://graph.microsoft.com) as the resource when you request a new access token to Microsoft Graph.
Secondly, client credential flow means app-only permission (without user). So if there is no signed in user, how could we get user's groups?
You should consider using AcquireTokenAsync(String, ClientAssertion, UserAssertion).
After that, using the following code to get the signed in user's groups.
GraphServiceClient client = await MicrosoftGraphClient.GetGraphServiceClient();
var memberOf = await graphClient.Me.MemberOf.Request().GetAsync();

Using httpClient.postasync for web api calls .netcore

I am new to .netcore, I am working on web api that are running on docker container and while using postman the web api's are working really fine outputting the results. I want to make a program in .netcore calling the webapi endpoints and getting the response and using that particular response in other endpoints with MVC.
The explanation is given below.
The default username and password for admin is default set for example username:admin , password: helloworld
. The first time admin login the api requires a new personal password as shown in the Postman figure below.
The login api is: localhost://..../v1/users/login
The first question is How to give the values in Authorization->BasicAuth using .netcore.
The body of the api looks like the figure below.
After setting the new_password the response of the api is a token as given below.
The particular token is then use in the Environment to create user. The image for more clear problem is given below.
Lastly, the token then used to make other API calls such as creating a user.
API: https://localhost/..../v1/users
The image is below.
As a newbie in .netcore language, I am really struggling to do this kind of API calls, as most of the tutorials I tried are generating their own token from API, but here I just want to take the response token and save it and then use it in other API calls.
The StackOverflow community's support was always really handy for me.
The Code I'm trying is given below.
**Controller**
public class Login_AdminController : ControllerBase
{
[Route("/loginAdmin")]
[HttpPost]
public async Task<string> LoginAdminAsync([FromBody] dynamic content)
{
LoginAdmin L = new LoginAdmin();
var client = new HttpClient();
client.BaseAddress = new Uri("https://localhost:9090");
var request = new HttpRequestMessage(HttpMethod.Post, "/v1/users/login");
var byteArray = new UTF8Encoding().GetBytes($"<{L.username}:{L.df_Password}>");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("new_password", "helloWorld123!"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
Console.WriteLine(response);
return content;
}
}
}
***Model***
public class LoginAdmin
{
public string username = "admin";
public string df_Password = "secret";
public string new_Password { get; set; }
}
Thank you.
Do you want to get token from response? If yes. Try this:
var client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:12345/Api");
var request = new HttpRequestMessage(HttpMethod.Post, "/token");
var keyValues = new List<KeyValuePair<string, string>>();
keyValues.Add(new KeyValuePair<string, string>("username", "yourusername"));
keyValues.Add(new KeyValuePair<string, string>("password", "yourpassword"));
request.Content = new FormUrlEncodedContent(keyValues);
var response = client.SendAsync(request).Result;
return response.Content.ReadAsStringAsync().Result;
Authorization is handled via the Authorization request header, which will include a token of some sort, prefixed by the scheme. What you're talking about here isn't really basic auth. With that, you literally pass the username and pass in the Authorization header with each request. What you're doing is just authenticating once to get an auth token, and then using that auth token to authorize further requests. In that scenario, you should really be posting the username and pass in the request body. Then, you'd do bearer auth with the token for the other requests, using the Authorization header. Still, to cover both bases:
Basic Auth
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
request.Headers.Add("Authorization", $"Basic {token}");
Bearer Auth
request.Headers.Add("Authorization", $"Bearer {token}");
// where `token` is what was returned from your auth endpoint
FWIW, List<KeyValuePair<string, string>> is just Dictionary<string, string>. It's better to use the real type. Then, you can just do formData.Add("new_password", "helloWorld123!") instead of formData.Add(new KeyValuePair<string, string>("new_password", "helloWorld123!"))

How to fetch email in web api 2 which is secured by WAAD

I am using MVC 5 client which is secured by "UseOpenIdConnectAuthentication" and getting all user details in Claims object, this client is calling WAAD secured Web Api by "Bearer" authentication token.
I need to fetch username or email in the web api. I tried different options but nothing worked.
I am getting null in Identity.Name, other properties I am getting like nameidentifier, objectidentifier, tenanted etc.
Please advise.
Thanks
Below code I am using for access token in Web Client.
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
ClientCredential clientcred = new ClientCredential(Startup.clientId, Startup.appKey);
AuthenticationContext authenticationContext = new AuthenticationContext(Startup.aadInstance + Startup.tenantId, new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(apiResourceId, clientcred);
return authenticationResult.AccessToken;
Start up Code
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
}
Below are the Token Details:
You can get the upn of current user by :
var upn = ClaimsPrincipal.Current.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn").Value;
Other way is getting user's basic information using Microsoft Graph api , please refer to On-Behalf-Of scenario .The OAuth 2.0 On-Behalf-Of flow serves the use case where an application invokes a service/web API, which in turn needs to call another service/web API. Please refer to protocol explanation and code sample .
Update :
Looking into your codes , you are using client credential flow to acquire token for your web api :
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(apiResourceId, clientcred);
The OAuth 2.0 Client Credentials Grant Flow permits a web service (confidential client) to use its own credentials instead of impersonating a user, to authenticate when calling another web service. That's why you can't get upn information which associated with a user .
You can use authorization code flow with user's identity , Please refer to code sample :
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = await authContext.AcquireTokenSilentAsync(todoListResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));