Roles with permissions

*It’s very common to create roles, and assign permissions to roles. *
*Checking would be against permissions only. *
Now we can add a user to one or more roles, and also give ad-hoc permissions of necessary.

I’ve implemented this solution:

public class MyOrmLiteAuthRepository : OrmLiteAuthRepository
{
    public override ICollection<string> GetPermissions(string userAuthId) {
        // gets permissions for roles, using OrmLite, returning them
    }
}

This returns correct permissions, which I’ve verified in the debugger.

My custom Credentials provider:

public class LdapCredentialsAuthProvider : CredentialsAuthProvider
{
    //...
    public override Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo, CancellationToken token = default)
    {
        session.Permissions = authRepo.GetPermissions(userAuth).ToList();
        base.OnAuthenticatedAsync(authService, session, tokens, authInfo, token);
    }
}

I’ve verified it inserts all the permissions into the session, which I see with the debugger.

Problem:
Once inside a Service:

var session = GetSession();
session.Permissions // contains only the permissions inside the UserAuth table
// missing what was inserted during logon in OnAuthenticatedAsync()

Where did the permissions get lost? I’ve logged out and in, restarted the app many times etc. Is it stuck with JWT somehow?

This is my Configure.Auth:

            appHost.Plugins.Add(new AuthFeature(() =>
                new AuthUserSession(),
                new IAuthProvider[] {
                    new LdapCredentialsAuthProvider(AppSettings),
                    new JwtAuthProvider(AppSettings) {               
                        // generate key: Convert.ToBase64String(AesUtils.CreateKey())
                        AuthKeyBase64 = AppSettings.GetString("AuthKeyBase64"),
                        ExpireTokensIn = TimeSpan.FromHours(8),                 // default is 14 days
                        RequireSecureConnection=false,                          // SSL is terminated on the reverse proxy
                    },
                }
            )

Using this to logon: /auth/Credentials?UseTokenCookie=1

Chrome’s debugger shows this is returned from SS:

{"userId":"9","sessionId":"mPl8PJfvRJnaLrmYE3Dq","userName":"xxxx","displayName":"xxxx","profileUrl":"","roles":["Admin"],"permissions":["ImpersonateUser"]}

clearly missing the permissions added in OnAuthenticatedAsync.

The Roles/Permissions for IManageRoles Auth Providers like OrmLiteAuthRepository are populated using GetRolesAndPermissions/Async which is what also needs to be overridden to include all the Roles/Permissions a user have:

To see what Auth Info is contained in a JWT, copy its BearerToken from ss-tok cookie or BearerToken property if not using Token Cookies and paste it in:

Which will decode it.

Got it working finally, had to override every method having anything to do with permissions (not roles) in the custom MyOrmLiteAuthRepository:

public override ICollection<string> GetPermissions(string userAuthId) {}

public override Task<ICollection<string>> GetPermissionsAsync(string userAuthId, CancellationToken token = default) {}

public override void GetRolesAndPermissions(string userAuthId, out ICollection<string> roles, out ICollection<string> permissions) {}

public override Task<Tuple<ICollection<string>, ICollection<string>>> GetRolesAndPermissionsAsync(string userAuthId, CancellationToken token = default) {}

public override bool HasPermission(string userAuthId, string permission) {}

public override Task<bool> HasPermissionAsync(string userAuthId, string permission, CancellationToken token = default) {}

It seems to me like different parts of ServiceStack uses different methods here. For example the JWT returned the correct result when I had just implemented GetRolesAndPermissionsAsync but still the returned body was wrong (from the auth call), because it was using another one, which at the time was not overriden.

A couple of questions:

  1. Setting Permissions/Roles in OnAuthenticatedAsync() seems useless. Correct? Perhaps they should be read only?

  2. GetSession() causes GetPermissionsAsync to be called, which causes the DB to be hit. Is this implemented in a smarter way in OrmLiteAuthRepository? Are permissions/roles not supposed to be stored in a session (best practice or something)?

Only if the Auth Repository implements IManageRoles/Async which is then up to the Auth Repository to return the roles/permissions.

GetSession() is cached for that request, which only calls PreRequestFilters if the session hasn’t been retrieved on that request yet. What’s done in the PreRequestFilters depends on what Auth Providers are registered, you’ll need to look at the full StackTrace to find out where GetPermissionsAsync is being called.

Because OrmLiteAuthRepository implements IManageRoles/Async, it purposely calls the AuthRepository APIs to fetch the Live Roles/Permissions. You can override GetPermissionsAsync on AuthUserSession to stop it from querying the AuthRepository and to only return the stale permissions on the AuthUserSession.