Keycloak Spring UMA denied - authorization

I am trying to implement authorization for this use case:
user can access only his own resources
admin can access everything
I am trying out Keycloak and it's resource server. For testing purposes and to understand these scopes and permissions and stuff, I have created test client weather-api and one resource Weather with url /weatherforecast.
Then I have scope weather:read and policy that every user with role weatherer can read that resource.
Now when I try to evaluate on a user with that role, I get PERMIT:
and another user without this role gets DENY.
so I guess my policies and permissions are set correctly.
When I try to use this from my service with user-managed-access disabled, I get permit too.
But when I enable user-managed-access, it fails.
I see in debug log that it gets permissions token:
{
...
"permissions": [
{
"scopes": [
"weather:read"
],
"rsid": "ae5ac493-b7dc-481e-9204-a664d1558a51"
}
],
...
}
but then the next message is
Policy enforcement result for path [http://192.168.0.9:5001/weatherforecast] is : DENIED
I tried to debug Keycloak library and found something I don't really understand.
In KeycloakAdapterPolicyEnforcer this part of code:
#Override
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
AccessToken original = accessToken;
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
return true;
}
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade, claims);
if (accessToken == null) {
return false;
}
...
}
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
return null;
}
...
}
so when UserManagedAccess is not null, requestAuthorizationToken returns null and then it acts like the user is unauthorized with HTTP 401.
What am I missing here? Why it works only without UMA?
I have looked at these (app-authz-uma-photoz, devconf2019-authz) examples and haven't noticed what I am missing.
Except they are actually creating some resources for users from the Java app, I'm not. But I guess it shouldn't matter if I'm protecting user created resources or single "pre-made" URL, right? It should depend only on correct permissions and since they evaluate to PERMIT so I don't see why this doesn't work.
And one more question. Isn't this UMA thing overkill for just "user can access his own, admin can access everything" case when there will never be any sharing between users? I was thinking about some simpler way that could work without creating user resources in Keycloak but I couldn't think of anything, I believe I still need to have connected user ID with some resource ID to make this working.

Related

Custom Authorizationhandler for token evaluation that is done externally

When the user submits his credentials to my api, I call an external api to authenticate the user. After that, a token gets generated on the external api and will be sent to me. For that I implemented the HandleAuthenticateAsync function from the AuthenticationHandler:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//before this: make call to external api to get the access token
var claims = new[] {
new Claim(ClaimTypes.Name, submittedToken),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
I have implemented a custom AuthorizationHandler which I want to check for the access token that you got when you successfully authenticate. Note that the actual authentication and authorization is done by an external api which is a custom implementation. Here is the function:
public class IsAuthorizedRequirement : AuthorizationHandler<IsAuthorizedRequirement>, IAuthorizationRequirement
{
public AuthenticateHandlerHelperFunctions AuthenticateHandlerHelper;
public IsAuthorizedRequirement()
{
AuthenticateHandlerHelper = new AuthenticateHandlerHelperFunctions();
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAuthorizedRequirement requirement)
{
if(!context.User.HasClaim(c => c.Type == ClaimTypes.Name))
{
context.Fail();
return;
}
var token = context.User.FindFirst(c => c.Type == ClaimTypes.Name).Value;
if (!string.IsNullOrEmpty(token))
{
context.Fail();
return;
}
var checkedToken = await AuthenticateHandlerHelper.CheckAccessToken(token);
if (checkedToken == null)
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}
The CheckAccessToken function makes a simple HTTP Post Request to the external Api where I get back if the token is still valid or not. Is this a valid implementation especially when multiple users are using this? Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request? Currently I have no way to test this so I just wanted to know if I am on the right track for this. Thanks
Is this a valid implementation especially when multiple users are using this?
I strongly stand against this approach. Implementation like this mean you would call external API for validate and generate token(or cookie or any form of authenticated certificate) on external server for each and any of your request(which require authentication).
It's could be consider acceptable if we have some special cases on just some endpoints. But for the whole API/Web server. Please don't use this approach.
Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request?
They'll create for each request. As I can see in the code there are no part for generate cookie or some form of retaining user information for the client to attach next request afterward.

IdentityServer4 Revoke all reference tokens for a client, except the current one

I have a single page application which is protected by IdentityServer4 reference tokens.
I expect users to login to from multiple computers/devices.
In the settings area of the app, the user can change their password. To do so, they must enter their current password, as well as the new password.
I also wish to give the user the option to "Logout all other devices and computers".
If the user ticks this option, I want to invalidate any other reference tokens that exist for this client and this user, but I do NOT want to invalidate the reference token the user is currently using.
I only want it to logout other devices and computers. The user should stay logged in on the computer they are using.
For the life of me, I cannot see a way to do this with IdentityServer4. I was thinking I could simply run a delete on the PersistedGrants table, however I have no way of knowing which of the persisted grants in this table is the one the user is currently using.
Please help!
I was finally able to solve this. Make sure you're using the latest version of IdentityServer, as it includes a session_id column on the PersistedGrants table. With that, the solution is clear.
When user changes password:
if (model.EndSessions)
{
var currentSessionId = User.FindFirst(JwtClaimTypes.SessionId).Value;
foreach (var grant in db.PersistedGrants.Where(pg => pg.ClientId == "the-client-name" && pg.SubjectId == user.Id.ToString() && pg.SessionId != currentSessionId).ToList())
{
db.PersistedGrants.Remove(grant);
}
db.SaveChanges();
await userManager.UpdateSecurityStampAsync(user);
}
The user's other tokens are now revoked.
However, the user (on their other computer/devices) will likely still have an authentication cookie, so if they were to go to the authorization endpoint they would be granted a new token without having to login again.
To prevent that, we intercept the request for a new token with a CustomProfileService, like so -
public override async Task IsActiveAsync(IsActiveContext context)
{
//only run check for cookie authentication
if (context.Subject.Identity.AuthenticationType == IdentityConstants.ApplicationScheme)
{
var validationResponse = await signInManager.ValidateSecurityStampAsync(context.Subject);
if (validationResponse == null)
{
context.IsActive = false;
return;
}
var user = await userManager.GetUserAsync(context.Subject);
context.IsActive = user.IsActive;
}
}

How to prevent multiple login in SAAS application?

What I need to do
I'm developing an application using ASP.NET CORE and I actually encountered a problem using the Identity implementation.
In the official doc infact there is no reference about the multiple session, and this is bad because I developed a SaaS application; in particular a user subscribe a paid plan to access to a specific set of features and him can give his credentials to other users so they can access for free, this is a really bad scenario and I'll lose a lot of money and time.
What I though
After searching a lot on the web I found many solutions for the older version of ASP.NET CORE, so I'm not able to test, but I understood that the usually the solution for this problem is related to store the user time stamp (which is a GUID generated on the login) inside the database, so each time the user access to a restricted page and there are more session (with different user timestamp) the old session will closed.
I don't like this solution because an user can easily copy the cookie of the browser and share it will other users.
I though to store the information of the logged in user session inside the database, but this will require a lot of connection too.. So my inexperience with ASP.NET CORE and the lack of resource on the web have sent me in confusion.
Someone could share a generic idea to implement a secure solution for prevent multiple user login?
I've created a github repo with the changes to the default .net core 2.1 template needed to only allow single sessions. https://github.com/xKloc/IdentityWithSession
Here is the gist.
First, override the default UserClaimsPrincipalFactory<IdentityUser> class with a custom one that will add your session to the user claims. Claims are just a key/value pair that will be stored in the user's cookie and also on the server under the AspNetUserClaims table.
Add this class anywhere in your project.
public class ApplicationClaimsPrincipalFactory : UserClaimsPrincipalFactory<IdentityUser>
{
private readonly UserManager<IdentityUser> _userManager;
public ApplicationClaimsPrincipalFactory(UserManager<IdentityUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor)
{
_userManager = userManager;
}
public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
// find old sessions and remove
var claims = await _userManager.GetClaimsAsync(user);
var session = claims.Where(e => e.Type == "session");
await _userManager.RemoveClaimsAsync(user, session);
// add new session claim
await _userManager.AddClaimAsync(user, new Claim("session", Guid.NewGuid().ToString()));
// create principal
var principal = await base.CreateAsync(user);
return principal;
}
}
Next we will create an authorization handler that will check that the session is valid on every request.
The handler will match the session claim from the user's cookie to the session claim stored in the database. If they match, the user is authorized to continue. If they don't match, the user will get a Access Denied message.
Add these two classes anywhere in your project.
public class ValidSessionRequirement : IAuthorizationRequirement
{
}
public class ValidSessionHandler : AuthorizationHandler<ValidSessionRequirement>
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public ValidSessionHandler(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidSessionRequirement requirement)
{
// if the user isn't authenticated then no need to check session
if (!context.User.Identity.IsAuthenticated)
return;
// get the user and session claim
var user = await _userManager.GetUserAsync(context.User);
var claims = await _userManager.GetClaimsAsync(user);
var serverSession = claims.First(e => e.Type == "session");
var clientSession = context.User.FindFirst("session");
// if the client session matches the server session then the user is authorized
if (serverSession?.Value == clientSession?.Value)
{
context.Succeed(requirement);
}
return;
}
}
Finally, just register these new classes in start up so they get called.
Add this code to your Startup class under the ConfigureServices method, right below services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
// build default authorization policy
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ValidSessionRequirement())
.Build();
// add authorization to the pipe
services.AddAuthorization(options =>
{
options.DefaultPolicy = defaultPolicy;
});
// register new claims factory
services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>, ApplicationClaimsPrincipalFactory>();
// register valid session handler
services.AddTransient<IAuthorizationHandler, ValidSessionHandler>();
You can use UpdateSecurityStamp to invalidate any existing authentication cookies. For example:
public async Task<IActionResult> Login(LoginViewModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid username/password.");
return View();
}
if (await _userManager.ValidatePasswordAsync(user, model.Password))
{
await _userManager.UpdateSecurityStampAsync(user);
var result = await _signInManager.SignInAsync(user, isPersistent: false);
// handle `SignInResult` cases
}
}
By updating the security stamp will cause all existing auth cookies to be invalid, basically logging out all other devices where the user is logged in. Then, you sign in the user on this current device.
Best way is to do something similar to what Google, Facebook and others do -- detect if user is logging in from a different device. For your case, I believe you would want to have a slight different behavior -- instead of asking access, you'll probably deny it. It's almost like you're creating a license "per device", or a "single tenant" license.
This Stack Overflow thread talks about this solution.
The most reliable way to detect a device change is to create a
fingerprint of the browser/device the browser is running on. This is a
complex topic to get 100% right, and there are commercial offerings
that are pretty darn good but not flawless.
Note: if you want to start simple, you could start with a Secure cookie, which is less likely to be exposed to cookie theft via eavesdropping. You could store a hashed fingerprint, for instance.
There are some access management solutions (ForgeRock, Oracle Access Management) that implement this Session Quota functionality. ForgeRock has a community version and its source code is available on Github, maybe you can take a look at how it is implemented there. There is also a blog post from them giving a broad view of the functionality (https://blogs.forgerock.org/petermajor/2013/01/session-quota-basics/)
If this is too complex for your use case, what I would do is combine the "shared memory" approach that you described with an identity function, similar to what Fabio pointed out in another answer.

How to Create login and logout using Vapor (Basic Authentication)

I want to create login and logout methods and routes. I've done already basic authentication but now I'm stuck how to continue. How should I do that, should I use sessions?
I'm using Vapor 3, Swift 4 and PostgreSQL and followed this tutorial https://medium.com/rocket-fuel/basic-authentication-with-vapor-3-c074376256c3. I'm total newbie so I appreciate a lot if you can help me!
my User model
struct User : Content, PostgreSQLModel, Parameters {
var id : Int?
private(set) var email: String
private(set) var password: String
}
extension User: BasicAuthenticatable {
static let usernameKey: WritableKeyPath<User, String> = \.email
static let passwordKey: WritableKeyPath<User, String> = \.password
}
UserController.swift, registering user.
private extension UserController {
func registerUser(_ request: Request, newUser: User) throws -> Future<HTTPResponseStatus> {
return try User.query(on: request).filter(\.email == newUser.email).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "a user with this email already exists" , identifier: nil)
}
let digest = try request.make(BCryptDigest.self)
let hashedPassword = try digest.hash(newUser.password)
let persistedUser = User(id: nil, email: newUser.email, password: hashedPassword)
return persistedUser.save(on: request).transform(to: .created)
}
}
}
So in Basic authentication there is no 'logout' per se as there's no login. With HTTP Basic Auth you transmit the user's credentials with each request and validate those credentials with each request.
You mention sessions, but first it's important to know what type of service you are providing? Are you providing an API or a website? They are different use cases and have different (usually) methods for authentication and login.
For an API you can use Basic Authentication and generally in your login function you exchange the credentials for some sort of token. Clients then provide that token with future requests to authenticate the user. To log out you simply destroy the token in the backend so it is no longer valid.
For a website, things are a little different since you can't manipulate the requests like you can with a normal client (such as setting the Authorization header in the request). HTTP Basic authentication is possible in a website, though rarely used these days. What traditionally happens is you submit the user's credentials through a web form, authenticate them and then save the authenticated user in a session and provide a session cookie back to the browser. This authenticates the user in future requests. To log a user out you just remove the user from the session.
Vapor's Auth package provides everything you need to do both of these scenarios. See https://github.com/raywenderlich/vapor-til for examples of both

Symfony: Why some user checks should be performed after authentication?

I don't get this. UserCheckerInterface has two methods: checkPreAuth and checkPostAuth. Now let's look at their implementation in the class UserChecker:
class UserChecker implements UserCheckerInterface
{
/**
* {#inheritdoc}
*/
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isAccountNonLocked()) {
$ex = new LockedException('User account is locked.');
$ex->setUser($user);
throw $ex;
}
if (!$user->isEnabled()) {
$ex = new DisabledException('User account is disabled.');
$ex->setUser($user);
throw $ex;
}
if (!$user->isAccountNonExpired()) {
$ex = new AccountExpiredException('User account has expired.');
$ex->setUser($user);
throw $ex;
}
}
/**
* {#inheritdoc}
*/
public function checkPostAuth(UserInterface $user)
{
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isCredentialsNonExpired()) {
$ex = new CredentialsExpiredException('User credentials have expired.');
$ex->setUser($user);
throw $ex;
}
}
}
Why should isCredentialsNonExpired() be done AFTER authentication? Shouldn't we just not allow the user with expired credentials to authenticate? And bonus question: Where should we really do this "post authentication" check? After setting the authentication token?
I believe the reason the methods are split is because when using session based authentication there are some things you don't want to check every time.
When using sessions, Symfony will serialize the token (and related user). When the next request comes in the PreAuthenticatedToken will contain the credentials you need for authorization.
Some examples of pre-authenticated tokens are: (stolen from docs)
authentication based on a "remember me" cookie.
authentication based on your session.
authentication using a HTTP basic or HTTP digest header
To improve performance if you have a token stored in the session you can remove some checks. The only example I have of the UserCheckerInterface is the one provided by Symfony. As you've seen, validation of the user's account is done inside preAuth and postAuth only checks if the credentials have expired.
In actual case looking at services that use these methods you can see that there isn't much distinction. The GuardAuthenticationProvider calls both sequentially.
Symfony's PreAuthenticatedAuthenticationProvider only calls postAuth so perhaps someone in Symfony decided that for session based authentication to shave a few milliseconds off the response time they could separate authentication checks that need to be done on first authentication from those that need to be done on every request.
In your case if you're creating a custom UserChecker I think you can decide from yourself if you need to use both. Find out if you have other bundles that have authentication providers calling either of these methods. Find all the places where they are called and you might find that you only need to implement one, or, if you have a lot of complex authentication logic, split it.