I need to connect to an external API to validate user credentials and get claims for user from within my custom UserService in IdSrvr, but using Client Credentials as if IdentityServer were a client to connect to another service.
What should be the approach?
First thing to come to my mind was to just make an HttpClient instance within UserService to connect to IdentityServer itself and make the request... But I don't know if there's a better/cleaner way.
The OwinEnviroment extensionmethods let you issue tokens.
public MyCustomUserService(OwinEnvironmentService owin)
{
_owin = owin;
}
public async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var token = await _owin.Environment.IssueClientToken(
clientId: "Banana",
scope: "resource1",
lifetime: 3600);
// call protected API with token
}
Link to GitHub issue with same question
There is a grant for this called the ResourceOwner Grant. Please read the spec accordingly.
The credentials should only be used when there is a high degree of
trust between the resource owner and the client (e.g., the client
is part of the device operating system or a highly privileged
application)
Most people would highly recommend that you do not use this grant as its an antipattern that requires the application to pass out user credentials which goes against the whole idea of OIDC. This grant is mostly here and used for legacy purposes.
Related
I have an ASP.NET Core (3.1) web API with a couple of client UI apps. Authentication is via Azure AD, everything is working, using:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
I also want to allow machine to machine API access using the Client Credentials flow. I can also get this working using the same app registration.
However, I need a way to validate which flow the request is using, as I want to expose functionality using Client Credentials API to API requests that I don't want interactive users to have access to.
What is the best way to make this work?
I have created a separate app registration in AAD that the Client Secret for the Client Credentials grant is on, and I have it adding permissions (as roles) to the token. And in the app registration for the API, I have granted permission to the Client Credentials app registration. But if I obtain a token with this flow, I can't authenticate. I have found that changing the scope in the token request to match the scope on the API app registration gives me a token that allows me to access the API, but then it is missing the app roles.
One the interactive token there are some user specific claims. So one workaround would be to check for the presence of these claims and disallow the functionality I want to restrict if they are present, but this seems a little hacky.
What else can I do? Is there a way to make both login flows work? Or another option that I've missed?
In case anyone else needs to get this working, I got it working by switching from:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
to:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.MetadataAddress = $"https://login.microsoftonline.com/mydomain.onmicrosoft.com/.well-known/openid-configuration";
options.TokenValidationParameters.ValidateAudience = false;
});
There are some additional steps too, as mentioned in the question. I created a separate app registration in AAD, and in the app registration for the API granted permission to the new app registration. In the new app registration I had to edit the manifest to get the scope I wanted included as a role (scopes are only assigned to user tokens, not tokens obtained with the client credentials grant).
With the token working that has the role data, for requests to my restricted endpoint I can just check that it's there:
public bool ValidateScope(string scopeName)
{
return _httpContextAccessor.HttpContext.User.IsInRole(scopeName);
}
bool authorised = _clientCredentialsService.ValidateScope("restricted");
if (!authorised)
{
throw new UnauthorizedAccessException("Attempt to access restricted functionality as a regular user");
}
(I have a filter that picks up this exception and bubbles it up to the consumer as a 403).
If anyone else is doing this you can see I've set ValidateAudience to false, so you probably want to add some policies if you do this.
I am having an application where Authentication is done using IdentityServer4 with Azure AD as an OpenID provider. IdentityServer4 is hosted in Azure App service. After successful authentication, I am able to get access token in Angular application. Access token is passed to .Net Core based RESTful API which is hosted in Azure Function 3.x.
In my Azure function I would like to get user info and other claims without hitting the end point "/connect/userinfo" of IdentityServer4.
Something similar to following for getting Claims would be helpful
[FunctionName("MyFunctionName")]
public static HttpResponseMessage Run(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"get", "post",
Route = "MyFunctionName")]HttpRequestMessage req,
ILogger log,
ClaimsPrincipal claimsPrincipal)
{
// My function code here...
}
How do I get I user info and other claims in Azure function where Authentication is done by IdentityServer4 with Azure AD as OpenID provider
If you have a valid access token, then you can make a request on your own to the UserInfo endpoint to retrieve the remaining user details.
Read more about it here
The only option if you don't want to access the userinfo endpoint is to include the required data in the tokens directly. Here you need to do a trade-of between token size vs convenience. Then you get a really stateless system.
If you don't want to hit user info end point of Identity Server to get the user info and other claims, here is what needs to be done.
Add the claim info to the authorization token
Parse the authorization token and extract the user info and other claim information.
The downside of this approach is that the token size is increased but advantage is that you don't need hit userinfo end point which saves your http request(s). So there are trade offs between each approach.
Here is how you can add claims info while configuring your api in Identity Server. Typically this information resides in Config.cs if you have used Identity Server template
public static IEnumerable<ApiResource> GetApis()
{
var apiResourceList = new List<ApiResource>
{
new ApiResource(IdentityServerConstants.LocalApi.ScopeName)
{
UserClaims =
{
JwtClaimTypes.Email,
JwtClaimTypes.PhoneNumber,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
JwtClaimTypes.PreferredUserName
},
}
};
return apiResourceList;
}
For parsing and validating the token please follow the blog Manual token validation in Azure Function
This StackOverflow thread is also very useful.
I'm working on constructing a system that is based on the microservice architecture. The microservices are created using Asp.Net Core 3.1.
A simplified high level diagram is shown below:
I would like two levels of security in the system:
Microservice authentication
When the ApiGateway receives a request, it receives a "microservice JWT" from the InternalAuthService and includes this JWT in the authorization header when sending requests over http to the other microservices. This JWT contains one or more claims, that are used by the microservices to decide if a request should be accepted or rejected.
The InternalAuthService is implemented using IdentityServer and it seems to work perfectly fine right now. This means CustomerService is rejecting requests if the ApiGateway does not provide a JWT with the required claim.
User authentication
Next, i want to add authentication of users, and this is where i need your help. A few endpoints in ApiGateway does not need user authentication (eg. registration of user and login of users). But lots of other endpoints will need user authentication. I'm pretty sure is can figure this out, as i have done this in other projects.
Because i have used IdentityFramework in the past when constructing small monolithic applications, i would like to use that in this system as well. This is why i have introduced the UserAuthService.
I would like to reject requests in the ApiGateway when a user does not provide a valid "user JWT". This JWT will also contains a number of claims, used to restrict access to the CustomerService endpoints depending on the roles the user have.
The problem
When sending a request from ApiGateway to CustomerService, the "microservice JWT" is placed in the authorization header. The JWT is authenticated in CustomerService's Starup.cs file like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:44308"; //url pointing to InternalAuthService
options.RequireHttpsMetadata = false;
options.Audience = "customer_service"; //the claim required by the CustomerService
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
I want to set up the authentication of the "user JWT" in a similar way, but i cannot figure out how to do it.
So the first issue is, where/how i can store the "user JWT" in the request to be able to authorize it when it is received by the CustomerService?
The next issue is, how i can set up authentication of the "user JWT"?
I'm open to replacing IdentityFrameWork with IdentityServer if it makes things easier (but i would prefer not to because, as i mentioned, i have experience with using IdentityFramework).
My opinion: this security architecture is complex. You can achieve the two levels of security you require by using a single user token and claims based authorisation.
If a call is made to the gateway with the user token, the gateway authenticates the call based on the user token, retrieves the "userId" claim, and injects it into the downstream call to the microservice. To make this work the userId needs to ideally be a component in the service operation path.
So, the URI exposed on the gateway for a given resource is:
GET /resources/{resourceId}
but is exposed on the actual service as:
GET /resources/{userId}/{resourceId}
This means that the downstream microservice can implement it's own measures to prevent access. For example, reject a request from anyone other than the owner of the requested resource from accessing the resource.
This is illustrated here:
If the service path modification is a difficult short term change then user claim injection can be done as a query, header, or even a body parameter.
I have two custom authentication method on Keycloak.
One of them is custom implemented user federation. I configured it for X realm. System uses this implementation for login with username / password method. This implementation calls my federation service and it validates sent user. It works successfully and authenticates federated users.
Second one is an identity broking (openid connect). I configured a custom openid provider to Y realm. It works successfully and validates provider's users.
I configured both of them to same realm. When i try to use login with custom identity provider, authentication flow works correctly. In the end of flow, configured user federation (custom implemented user federation) triggers with username which comes from identity broking (custom identity provider) login process and it calls my federation service again.
When i try to login with identity providers, i do not want the user federation (custom implemented user federation) to work. It must work only when i try to login with username / password login.
How can i block working of user federation on this scenario?
Please share your experience.
Thanks
I had the same issue of the identity provider and custom user federation not both working at the same time. My solution wasn't to block user federation but to change my custom user federation code. The implementation of the method that returns the user from the UserLookupProvider interface (getUserByEmail/Username/Id) must be coded to return a AbstractUserAdapterFederatedStorage. This implementation of UserModel provides implementation for methods needed by keycloak to internalize users from identity providers. Additionally I implemented UserQueryProvider interface along with UserStorageProvider, UserLookupProvider, CredentialInputValidator to be able to see these users from the keycloak admin console.
From their documentation:
Keycloak comes with a helper class org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage that will delegate every single UserModel method except get/set of username to user federated storage.
...interface UserQueryProvider. If you do not implement this interface, then users will not be viewable in the admin console. You’ll still be able to login though.
Ref: https://www.keycloak.org/docs/latest/server_development/#_user-storage-spi
Code:
note: A custom keycloak user storage should have at least two classes. One main one that does the heavy lifting that must implement at least UserStorageProvider. And another that is a factory that calls this class. More on this on their Server Development guide. The following code goes in the main class (not the factory):
#Override
public UserModel getUserByUsername(String username, RealmModel realm) {
[...]
userModel = createUserModel(username, realm);
[...]
return userModel;
}
protected UserModel createUserModel( String username, RealmModel realm) {
return new AbstractUserAdapterFederatedStorage(session, realm, model) {
#Override
public String getUsername() {
return username;
}
#Override
public void setUsername(String username) {
//retrieves user through repository and sets the keycloak user to its username. (Seems redundant but works!)
usersService.getUserDetails(username).setUsername(username);
}
};
}
I'm implementing both authentication and authorization mechanisms in Asp.Net Core Web Api application.
I use JWT for users authentication configured in:
ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(...).AddJwtBearer(...)
...
}
(similar to https://stackoverflow.com/a/45901894/1544054)
this service also populate HttpContext.User according to the JWT data.
For authorization I use a custom RBAC implementation, based on
class AccessControlFilter : IAuthorizationFilter
{
public AccessControlFilter(string permission) {...}
public void OnAuthorization (AuthorizationFilterContext context){...}
}
(similar to the great answer in https://stackoverflow.com/a/41348219)
I need to know for sure that my AccessControlFilter will run AFTER the JWT Authentication Service, so that the
context.HttpContext.User is already populated.
(I guess that the order is correct and filters will run after services, but I could not find the right documentation.)
From the ASP.NET Core Security Overview (emphasis mine):
Authentication vs. Authorization
Authentication is a process in which a user provides credentials that are then compared to those stored in an operating system, database, app or resource. If they match, users authenticate successfully, and can then perform actions that they're authorized for, during an authorization process. The authorization refers to the process that determines what a user is allowed to do.
Another way to think of authentication is to consider it as a way to enter a space, such as a server, database, app or resource, while authorization is which actions the user can perform to which objects inside that space (server, database, or app).
So to answer your question : authentication always occurs before the authorization pipeline. This makes sense, because you need to know who the user is before knowing what he's authorized to do.