RequiredPermission issue in v5.13

I have:

  • A custom auth provider which adds permissions to the session upon authentication.
  • Service classes/methods annotated with [Authenticated] and [RequiredPermission].

In 5.12, authenticated requests with correct permissions return HTTP/1.1 200 OK. In 5.13, the same requests return HTTP/1.1 403 Invalid Permission but nevertheless still return results from the service.

A simple MRE is at https://github.com/EricMunn/AuthError

v5.12 Example (v5.12 branch):

POST https://localhost:44369/Hello/World HTTP/1.1
Accept: application/json
Authorization: Basic foo:bar
content-type: application/json

returns

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
Set-Cookie: ss-id=GuV90odr99aHfdCeAq7A; path=/; secure; samesite=lax; httponly,ss-pid=xAJhbbK1ux6VrNQrFzKq; expires=Tue, 19 Nov 2041 20:45:58 GMT; path=/; secure; samesite=lax; httponly,ss-opt=temp; expires=Tue, 19 Nov 2041 20:45:58 GMT; path=/; secure; samesite=lax; httponly
X-Powered-By: ServiceStack/5.120 NetCore/Windows, ASP.NET
Date: Fri, 19 Nov 2021 20:46:00 GMT
Connection: close

{
  "result": "Hello, World!"
}

v5.13 Example (master branch):

POST https://localhost:44369/Hello/World HTTP/1.1
Accept: application/json
Authorization: Basic foo:bar
content-type: application/json

returns

HTTP/1.1 403 Invalid Permission
Transfer-Encoding: chunked
Content-Type: text/plain
Vary: Accept
Server: Microsoft-IIS/10.0
Set-Cookie: ss-id=XZF9s1YIegcFBqmOtTwt; path=/; secure; samesite=lax; httponly,ss-pid=I6XK5t7KuENQxIu2Mect; expires=Tue, 19 Nov 2041 21:20:26 GMT; path=/; secure; samesite=lax; httponly,ss-opt=temp; expires=Tue, 19 Nov 2041 21:20:26 GMT; path=/; secure; samesite=lax; httponly
X-Powered-By: ServiceStack/5.130 NetCore/Windows, ASP.NET
Date: Fri, 19 Nov 2021 21:20:31 GMT
Connection: close

Forbidden

Request.HttpMethod: POST
Request.PathInfo: /Hello/World
Request.QueryString: 

{"result":"Hello, World!"}

Not sure if this is related, but debugging with SourceLink in VS 2019 evinces some odd behavior in AuthenticateAttribute.AuthenticateAsync

public static async Task<bool> AuthenticateAsync(IRequest req, object requestDto=null, IAuthSession session=null, IAuthProvider[] authProviders=null)
{
    if (HostContext.HasValidAuthSecret(req))
        return true;

    session ??= await (req ?? throw new ArgumentNullException(nameof(req))).GetSessionAsync().ConfigAwait();
    authProviders ??= AuthenticateService.GetAuthProviders();
    var authValidate = HostContext.GetPlugin<AuthFeature>()?.OnAuthenticateValidate;
    var ret = authValidate?.Invoke(req);
    if (ret != null)
        return false;

    req.PopulateFromRequestIfHasSessionId(requestDto);

    if (!req.Items.ContainsKey(Keywords.HasPreAuthenticated))

The statement at line 131,

var authValidate = HostContext.GetPlugin<AuthFeature>()?.OnAuthenticateValidate;

executes and immediately jumps to line 134:

return false;

However, execution continues with line 138, having skipped line 136 (which is req.PopulateFromRequestIfHasSessionId(requestDto);)

Thanks for the MRE! This issue should now be resolved from this commit.

This change is now available from v5.13.1 that’s now available on MyGet.

I’ll get an updated v5.13.2 release on NuGet shortly.

This fix is now available from v5.13.2 on NuGet.

As it could be useful for future HTTP issues, here’s the curl command I used to repro the issue locally:

$ curl -s -D - -X POST -d "" -u foo:bar -H "Accept: application/json" https://localhost:5001/Hello/World

Thanks, the response is no longer included with the 403.

However, the primary issue still exists in that the request is being rejected with a 403, even though the required permission has been set. The identical request returns a 200 in 5.12, but a 403 in 5.13.

Yeah that’s because it was expected for a valid UserSession to have its UserAuthId populated with the Users Id. It’s still recommended, but no longer needed in the latest v5.13.3 now available on MyGet.

Thanks for the quick response and explanation! I had no downstream use for UserAuthId so never populated it.

1 Like