I have a platform with two API's. Let's call them API A and API B. I have a case, that i have to call API B from API A and both need a singned user. So i added there Policies and JWT token.
Of course, Policies are the same in both APIs.
So, using swagger i am calling Method A in API A, with authorized Token. Method is working correctly and calling Method B from API B, but HTTP client hasn't got token, because is not filling anywhere..
I am trying to fill in a Startup.cs in API A, a httpClient to using the token, but i dont know, how to get HttpContext with the token.
This is how i am trying to fill the token in the HttpClient, but i dont exactly now, how i should get the token from the context.
services.AddHttpClient("Api_B_Client", x =>
{
x.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "");
});
In the context token exists, because i am parsing like this:
services.AddAuthentication().AddJwtBearer(cfg =>
{
SetupJwtBearerWithDecryption(cfg);
});
In the SetupJwtBearerWithDecryption method i have an OnMessageReceived event and there i am parsing the token
My solution for the problem is to get from Configuration HttpContextAccessor and the object has authorization token which i was looking for
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddHttpClient("client", x =>
{
var context = Configuration.Get<HttpContextAccessor>();
x.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", context.HttpContext.Request.Headers["Authorization"]);
});
Related
Well, I have a WebApi using Asp Net Core 2.1 that have some security endpoints, so I have the following:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = configuration["Authorization:Domain"];
options.Audience = configuration["Authorization:ApiIdentifier"];
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateLifetime = configuration["Authorization:ValidateLifetime"] == null ? true :
Boolean.Parse(configuration["Authorization:ValidateLifetime"])
};
options.RequireHttpsMetadata = configuration["Authorization:RequireHttpsMetadata"] == null ?
true : Boolean.Parse(configuration["Authorization:RequireHttpsMetadata"]);
});
This works very well when someone calls my API, the validation is OK. I'm using http://auth0.com/ as an authorization provider.
Now I need to call other APIs that have security endpoints too, using Authorization Bearer Token (JWT). The flow that I should use in this case is Client Credentials. So, I have the steps:
Someone calls my API
My API validates Jwt Token using auth0.com as authorization provider
I need to call other API, so I'm using Refit Library
My Refit interface:
public interface IUserInfoApi
{
[Get("/api/v2/users/{userId}")]
[Headers("Authorization: Bearer")]
Task<UserInfoDto> GetUserInfoAsync(string userId);
}
And I created I handler to add Bearer token to my request:
//refit apis
services.AddRefitClient<IUserInfoApi>()
.AddHttpMessageHandler<AuthorizationMessageHandler>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(configuration["Api:UserInfo"]));
And my handler:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancelToken)
{
HttpRequestHeaders headers = request.Headers;
AuthenticationHeaderValue authHeader = headers.Authorization;
if (authHeader != null)
headers.Authorization = new AuthenticationHeaderValue(authHeader.Scheme, JWT_TOKEN);
return await base.SendAsync(request, cancelToken);
}
This works, but I think is too much manually and error-prone:
There is a lot of OAuth2 flows to implement manually, to generate a token (client credentials, implicit flow, authorization code flow and others).
I have to implement refresh token logic.
I have to implement some logic to reuse token if the expiration time is valid yet, instead generate a new token every time (hit /token endpoint every time without needed).
I worked a lot with Spring Security framework, and I can just say: "Spring, I'm using OAuth here, so insert Bearer token in every HTTP requests, please". Spring, intercepts all requests that I set in configuration and OAuth flow is respected (Client Credentials, Authorization COde Flow, and others), it's transparent for me, I don't need wast my time with it.
There is any way to do it in Asp Net Core 2.1 or I need to implement manually the token generate flow?
I've successfully integrated my .net Core WebAPI with IdentityServer4 where a client is able to request a token and send back via auth. header when calling the controller and able to successfully authorize.
However, I do have a question around fined grained authorization.
When inserting a new ApiResource, I am also adding scopes (ex. app.api1.read, app.api2.read, etc..) and the client has access to the scopes (app.api1.read and app.api2.read). When the client calls app.api1, they can pass either scopes app.ap1.read or app.api2.read and both would be authorized successfully. Ideally, I would want the client to pass scope for api1 (ex. app.api1.read) and if they pass scope for api2 it should not be authorized.
Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// base-address of your identityserver
options.Authority = "https://localhost:44393";
options.RequireHttpsMetadata = false;
});
TestController.cs
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class TestController : Controller
{
Your assistance is much appreciated.
I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.
Now, how to authorize uses before some action or inside action in my api/app controller?
I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.
But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.
I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).
Question how?
Or What I have to looking for?
So there's a few possible solutions here:
Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
Lower the cookie lifetime to refresh user info automatically more often
Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
Have IdentityServer force single sign out when user profile data is changed
In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)
Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.
The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:
On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal
Code Samples
Get updated UserInfo from endpoint on each call
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
Hope this helps!
I'm using JWT tokens and OpenIdConnectServer. All works very well, but i can't add custom properties in token response... Here are the result:
resource": "resource_server_1",
"token_type": "bearer",
"access_token": "eyJhb....LSk5PQldEVVFaTllNU",
"expires_in": "3600"
I want to add some properties like username or role...
I'm trying to add through AuthenticationProperties, but it is doesn't work. Here my code:
public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsContext context)
{
ClaimsIdentity identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, "test", "token id_token");
identity.AddClaim(ClaimTypes.Role, "test", "token id_token");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(new Dictionary<string, string>
{
{"username", "test" }
}),
context.Options.AuthenticationScheme);
ticket.SetResources(new[] { "resource_server_1" });
context.Validated(ticket);
return Task.FromResult<object>(null);
}
To add custom properties to token responses, you can take a look at this other SO question: Overriding TokenEndPoint in AspNet.Security.OpenIdConnect.Server
That said, this is not the approach I'd recommend. Instead, you should use the new id_token concept offered by OpenID Connect, which is also supported for the password flow in ASOS and that allows sharing user details between the authorization server and the client apps.
For that, add scope=openid to your token request and the OIDC server middleware will start returning a JSON Web Token you'll be able to read to extract user details like a username. Note that only claims specifying the id_token destination will be included in the identity token. Read this SO post for more info: https://stackoverflow.com/a/35041102/542757
(on a related note, you're not adding a ClaimTypes.NameIdentifier claim to your authentication ticket: this is not a legal operation as the OIDC server middleware needs a unique id to identify the user)
Setup
Client: mobile app built on Cordova
Backend: ASP.net WebAPI2 (based on the standard template) configured with facebook login provider.
Problem
Having authenticated the user in the mobile app and received a Facebook access token, I pass this in subsequent requests as an HTTP header ("Authorization: Bearer "). This returns status 401 Unauthorized.
Questions
What am i missing here? How can i access the WebAPI controller actions based on the Facebook access token obtained on the mobile device?
On a high level, what i'm trying to achieve is this:
User opens mobile app and authenticates with Facebook
If user is not registered as a local user, he must choose a username to complete the registration
User is registered and can access API
I was facing the same problem and I found a really good solution here: http://codetrixstudio.com/mvc-web-api-facebook-sdk/
The WebApi web site can't understand the access token provided by Facebook. I guess it's because it hasn't been issued by itself (LOCAL AUTHORITY) but by an external provider. The approach explained in the link above is based on validating the token given by Facebook using it's API and recreating the access token.
So, you'll need some additional steps to achieve your goal.
The external providers have API so you can get information. For example, the https://graph.facebook.com/me?access_token={0} can be used to check if the token is valid. On the server side, you'll need to make a https web request to this URL passing the token (and the secret app as a proof, if the app is configured to ask it in Facebook).
Given the token is ok, you'll create an identity (ClaimsIdentity) using the information you've got at the API (id and username, for example). This identity will be needed to make an instance of the AuthenticationTicket class so you'll be able to issue a new access token for your Cordova app. Use this new bearer access token in the Authorization header of your https calls and your WebApi will recognized it as valid calls.
Ps. The good thing here is that you can set the token's expiration.
Since API's are stateless, there are multiple ways to secure it. In this case the mobile app has authenticated the user, but the API has not.
You can register the user's email and facebook ID into the database using a anonymous route. this can serve as both the login and register technically. (you could secure it with a clientid via OAuth if you don't want it fully open) along with thier current token. You verify the user against the facebook API on the server before registering of course just in case.
Create a custom route handler to secure the account controller or any other routes. The custom route handler would check the database for the current FB token and fb ID combo as well as token expire time, since you don't want to keep authenticating if it's expired.
Facebook has two types of tokens.
A Short lived token, initially created when you login and a long term token that lasts up to 60 days vs 1-2 hours.
for the api side, i suggest sticking to the short lived token and re-authenticate once expired but thats up to you.
You should grab a facebook SDK of your choice to verify and pull account info.
Example Secured Route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "routename",
routeTemplate: "account",
constraints: null,
handler: new CustomFBHandler()
{
InnerHandler = new HttpControllerDispatcher(config)
},
defaults: new {controller = "default"}
);
}
Custom Handler: (note that you should pass any needed dependancies in the construtor)
public class CustomHandler : DelegatingHandler
{
public CustomHandler()
{
}
protected async Task<bool> IsAuthenticated(HttpRequestMessage requestMessage)
{
//Authenticate FB User Info HERE Against the Registered/logged in user....
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
bool isAuthenticated = false;
try
{
isAuthenticated = await IsAuthenticated(request);
}
catch (Exception e)
{
var response = request
.CreateResponse(HttpStatusCode.InternalServerError, new { status = new { code = 333, error = true, message = e.Message } }, new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
if (!isAuthenticated)
{
var response = request
.CreateResponse(HttpStatusCode.Unauthorized,new {status=new{code=1,error=true,message="Authorization Failed"}},new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
return await base.SendAsync(request, cancellationToken);
}
}
You should send the FB Token and Facebook user id in the Headers. Once authenticated you can use the token/id to pull the user info you need from the database.