Authentication, Locode and Roles

We have a custom authentication provider that inherits from AuthProvider ServiceStack class. This auth provider sets roles, none of which are Admin.

We setup a service using AutoQuery CRUD and it works as expected in the API explorer with our authentication provider. When we switch to the Locode view, the right side panel is blank and the error console has no errors.

We tracked it down to a hard check in IsAdminAuth from the core.js. When we add the role Admin to our session object the Locode page works as expected.

Is there any way to remove or change the role required for Locode?

Hi @anthony.carl.cfc,

Are you already authenticated with your application? For example, if API Explorer is working fine, you should be able to authenticate and then navigate to the /locode UI. The isAdminAuth method in core.js you link to should still check other roles and Locode does not require admin access to use.

What restrictions are put on your services?

I’m not sure what right side panel you mean in Locode, could you post a screenshot? The menu on the left should contain AutoQueryCRUD tables that Locode can use. Do these all have Query endpoints? Eg, Request DTOs that inherit from QueryDb<MyTable>?

Any code examples of your DTOs or AppHost configuration would also help us try and replicate the issue, or if you can provide a minimal reproduction of the issue I can try help track down the problem.

Our custom session object inherits from AuthUserSession and the IsAuthenticated property returns true. We successfully authenticate. API explorer shows us the roles we have set.

Our auth provider basically reads Claims from the .net ClaimsPrincipal to populate the custom session object. We have a few custom claims we map to custom properties. A company specific library reads auth/session info from a custom JWT and/or cookie that is exchanged to get session info. Either method results in Claims being set on the .net ClaimsPrincipal. I don’t intend to share that code.

In our testing we individually attempted to add ValidateIsAuthenticated per the documentation, Authenticate attribute directly on the request dto, implement custom AutoQuery service class decorated with the Authenticate attribute, and setting a specific role the session had on the request DTO to resolve the issue. All resulted in the related functionality disappearing from the Locode page.

For example: if we make creating a group (see code below) require authentication, create group button disappears. The table still shows up in the navigation menu and we see results.

Everything works as expected as soon as I set a role called Admin on the custom session object:

GlobalRequestFilters.Add((request, _, __) =>
{
    var session = request.SessionAs<CustomAuthUserSession>();
    if (session is { IsAuthenticated: true })
    {
        session.Roles.Add("Admin");
    }
});

Reference Code:

public interface IAudit
{
    DateTime CreatedDate { get; set; }
    string CreatedBy { get; set; }
    string CreatedInfo { get; set; }
    DateTime ModifiedDate { get; set; }
    string ModifiedBy { get; set; }
    string ModifiedInfo { get; set; }
}

public abstract class AuditBase : IAudit
{
    public DateTime CreatedDate { get; set; }
    [Required]
    public string CreatedBy { get; set; }
    [Required]
    public string CreatedInfo { get; set; }
    public DateTime ModifiedDate { get; set; }
    public string ModifiedBy { get; set; }
    public string ModifiedInfo { get; set; }
    public ulong RowVersion { get; set; }
}

[Route("/group", "POST", Summary = "Creates a new group")]
// Authenticate attribute causes Locode to stop working
[Authenticate]
public class CreateGroup : AuditableRequest, ICreateDb<Group>, IReturn<GroupCreated>
{
    [ValidateNotEmpty(Message="Did This Work?")]
    public string Name { get; set; }
}

public class GroupCreated
{
    public Guid Id { get; set; }
    public ulong RowVersion { get; set; }
}

public class Group : AuditBase
{
    [AutoId]
    public Guid Id { get; set; }

    [Required]
    [Unique]
    [StringLength(50)]
    public string Name { get; set; }

    [Reference]
    public List<FeatureFlag> FeatureFlags { get; set; }

    public ulong RowVersion { get; set; }
}

[UniqueConstraint(nameof(GroupId), nameof(Name))]
public class FeatureFlag : AuditBase
{
    [AutoId]
    public Guid Id { get; set; }

    [ForeignKey(typeof(Group))]
    public Guid GroupId { get; set; }

    [Required]
    [StringLength(75)]
    public string Name { get; set; }

    public bool IsEnabled { get; set; }

    public ulong RowVersion { get; set; }
}

[Route("/group", "GET", Summary = "Retrieves groups")]
//This breaks Locode
//[ValidateIsAuthenticated]
public class GetGroups : QueryDb<Group>
{
}

[Route("/group/{Id}", "DELETE", Summary = "Deletes the specified group")]
//[ValidateIsAuthenticated]
public class DeleteGroup : IDeleteDb<Group>, IReturnVoid
{
    public Guid Id { get; set; }
}

[Route("/group/{Id}", "PATCH", Summary = "Patch an existing group for feature flag management")]
// [ValidateIsAuthenticated]
public class PatchGroup : AuditableRequest, IPatchDb<Group>, IReturn<GroupPatched>
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public ulong RowVersion { get; set; }
}

public class GroupPatched
{
    public Guid Id { get; set; }
    public ulong RowVersion { get; set; }
    public ResponseStatus ResponseStatus { get; set; }
}

[Route("/softwareFeature", "GET", Summary = "Retrieves feature flags for the specified group")]
// [ValidateIsAuthenticated]
public class GetFeatureFlags : QueryDb<FeatureFlag>
{
}

[Route("/softwareFeature/{Id}", "DELETE", Summary = "Deletes a new feature flag for the specified group")]
//[ValidateIsAuthenticated]
public class DeleteFeatureFlag : IDeleteDb<FeatureFlag>, IReturnVoid
{
    public Guid Id { get; set; }
}

[Route("/softwareFeature", "POST", Summary = "Creates a new feature flag setting for the specified group")]
//[ValidateIsAuthenticated]
public class CreateFeatureFlag : AuditableRequest, ICreateDb<FeatureFlag>, IReturn<FeatureFlagCreated>
{

    public Guid GroupId { get; set; }

    public string Name { get; set; }

    public bool IsEnabled { get; set; }
}

[Route("/softwareFeature/{Id}", "PATCH", Summary = "Patch an existing feature flag")]
public class PatchFeatureFlag : AuditableRequest, IPatchDb<FeatureFlag>, IReturn<FeatureFlagPatched>
{
    public Guid Id { get; set; }
    public bool IsEnabled { get; set; }
    public string Name { get; set; }
    public ulong RowVersion { get; set; }
}

public class FeatureFlagPatched
{
    public Guid Id { get; set; }
    public ulong RowVersion { get; set; }
}

//In AppHost
Plugins.Add(new AutoQueryFeature
{
    MaxLimit = 100,
    IncludeTotal = true
});

container.AddSingleton<ICrudEvents>(c =>
new OrmLiteCrudEvents(c.Resolve<OrmLiteConnectionFactory>())
{
});

Plugins.Add(new ValidationFeature());

Plugins.Add(
    new AuthFeature(
        () => new CustomAuthUserSession(),
        new IAuthProvider[]
        {
            new CustomNetCoreIdentityAuthProvider
            {
            }
        }));

Just to confirm, what version of ServiceStack are you running?

The “Admin” role allows use of all endpoints like a superuser. When AutoQueryCRUD endpoints require authentication, they are hidden from the Locode interface for unauthenticated users.

The AuthenticateAttribute and ValidateIsAuthenticatedAttribute both hit the AuthenticateAsync method in AuthenticateAttribute, could you step through to make sure the session is as expected when interacting with your custom auth provider?

The removal of functionality as authentication is required with the combination of the services not needing any additional roles sounds like something unexpected in the loadAuth response when the locode interface first loads, you should be able to see your session and user details. For example, this is what I see when logged into out demo TalentBlazor application when locode loads auth info


Let us know if you are seeing a populated user or something different?

The isAdmin check in core.js returns true since the Admin role is a superuser and can interact with all locode services, but it is only one part of the checks for if a user can see a locode endpoint which indirectly controls visibility of functionality.

Using ServiceStack v6.1.0 on .net 6.0.

I stepped through the js and I think I found a bug. We don’t require any permissions or roles. It seems this may be incorrect:

if (!requiresAnyRole.some(role => roles.indexOf(role) >= 0))
        return false

If I don’t require any roles, then I would not expect the above predicate to be true. Should some just be every like all the other checks?

image

1 Like

Thaks @anthony.carl.cfc! Glad we have gotten to the bottom of it, I can reproduce the issue now and looking into a solution.

@anthony.carl.cfc a fix has been pushed to MyGet and is available in the version 6.1.1. Let us know if that resolves the issue you are seeing.

This issue is resolved. We are using v6.2. Thanks for the fast response!

2 Likes