Api key auth not working

Hi there,

I was using SS v5.9.0 and update it to the latest version i.e. v6.0.0
After updating I updated the configuration for JWT Token with UseTokenCookie = false
With this configuration my normal default credential auth provider with customerUserSession is working well.
When I hit my APIs with API Key it is failing to authenticate the request whereas it was working with older version.

Here are my auth config.
I used async providers as well i.e CredentialsAuthProviderSync

Plugins.Add(new AuthFeature(() =>
    new CustomUserSession(),
    new IAuthProvider[]
    {
        new ApiKeyAuthProvider(AppSettings) {
            AllowInHttpParams=true,
            Environments=new string[]{"live","dev","test"},
        SessionCacheDuration = new TimeSpan(1, 0,0),
        RequireSecureConnection = false,
    },
    new CredentialsAuthProviderSync(AppSettings),
    new JwtAuthProvider(AppSettings)
    {
        AuthKeyBase64 = appConfig.EnvironmentConfig.JwtConfig.JwtAuthKeyBase64,
        UseTokenCookie = false,
        RequireSecureConnection = false,
        EncryptPayload = appConfig.EnvironmentConfig.JwtConfig.EncryptPayload,
        ExpireTokensIn=new TimeSpan(1,0, 0),
        ExpireRefreshTokensIn = new TimeSpan(1,0, 0),
        CreatePayloadFilter = (payload,session) =>
        {
            payload[Keywords.Session]=session.Id;
        },
        PopulateSessionFilter = (session, payload, req) =>
        {
            ((CustomUserSession) session).UserSessionId = payload[Keywords.Session];
        }
    }                  
}));

Please suggest what changes need to be done to authenticate request with API Key.

Please post the full HTTP Error Response Headers (e.g. using Chrome Web Inspector, Fiddler, WireShark, etc), with any sensitive info scrubbed out (e.g xxxx).

The request.IsAuthenticated() and request.IsAuthenticatedAsync() is always returning False

Request:

POST http://localhost:58531/api/test/apiRequest HTTP/1.1
Host: localhost:58531
Connection: keep-alive
Content-Length: 0
sec-ch-ua: " Not;A Brand";v=“99”, “Google Chrome”;v=“97”, “Chromium”;v=“97”
sec-ch-ua-mobile: ?0
Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Cache-Control: no-cache
Postman-Token: abb7fdf7-ddd4-2ef5-fc4f-b844de55a790
sec-ch-ua-platform: “Windows”
Accept: /
Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
Sec-Fetch-Site: none
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: ss-pid=qpPIOwaJozZg0KLcuZNF; ss-id=sudDOLX5eM9RvsunfX1h

Response
HTTP/1.1 403 Forbidden
Cache-Control: private
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
X-Powered-By: ServiceStack/6.00 Net45/Windowsnetfx/NO
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Origin, Authorization, Access-Control-Allow-Origin
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RTpcQWx5bmtcQWx5bmtBcGlcQXV0b21vdGl2ZUx5bmsuQXBpXGFwaVxjaGFyZ2VjdXN0b21lcmFjY291bnRzXGFyXHBkZg==?=
X-Powered-By: ASP.NET
Date: Tue, 01 Feb 2022 16:47:23 GMT
Content-Length: 1461

{“responseStatus”:{“errorCode”:“UnauthorizedAccessException”,“message”:“Attempted to perform an unauthorized operation.”,“stackTrace”:“System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.\r\n at AutomotiveLynk.Api.TenantInfoSecurityFilter.<>c.b__0_0(IRequest req, IResponse res, IRequiredRequestInfo dtoInterface) in E:\Alynk\AlynkApi\AutomotiveLynk.Api\Filters\TenantInfoSecurityFilter.cs:line 61\r\n at ServiceStack.Host.TypedFilter1.Invoke(IRequest req, IResponse res, Object dto)\r\n at ServiceStack.ServiceStackHost.ExecTypedFilters(Dictionary2 typedFilters, IRequest req, IResponse res, Object dto)\r\n at ServiceStack.ServiceStackHost.d__425.MoveNext()\r\n— End of stack trace from previous location where exception was thrown —\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at ServiceStack.ServiceStackHost.d__424.MoveNext()\r\n— End of stack trace from previous location where exception was thrown —\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at ServiceStack.Host.RestHandler.d__14.MoveNext()\r\n”,“errors”:[]}}

The exception is coming from within your TenantInfoSecurityFilter:

at AutomotiveLynk.Api.TenantInfoSecurityFilter.<>c.b__0_0(IRequest req, IResponse res, IRequiredRequestInfo dtoInterface) 
in E:\Alynk\AlynkApi\AutomotiveLynk.Api\Filters\TenantInfoSecurityFilter.cs:line 61

Yes this is my custom filter, I added a simple check first i.e. request.IsAuthenticated() and request.IsAuthenticatedAsync()

if request pass this then I add my custom logic but it failed on the first check

Not sure then, the API Key behavior hasn’t changed in a long time.

It would likely need debugging into the implementation to find out why it’s not authenticating.

Can you try to see if this request succeeds or throws an Exception:

var authResponse = client.Post(new Authenticate {
    Provider = "apikey",
    UserName = "ApiKey",
    Password = yourApiKey,
});

I tried this,
var authResponse = client.Post(new Authenticate {
Provider = “apikey”,
UserName = “ApiKey”,
Password = yourApiKey,
});

the request succeeds
this is when I use C# client

When I send the same thing with HTTP Request (Postman) it throws exception 401

Request
POST http://localhost:58531/api/json/reply/Authenticate HTTP/1.1
Host: localhost:58531
Connection: keep-alive
Content-Length: 94
sec-ch-ua: " Not;A Brand";v=“99”, “Google Chrome”;v=“97”, “Chromium”;v=“97”
Cache-Control: no-cache
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
sec-ch-ua-platform: “Windows”
Postman-Token: 2c969d03-d0c2-2dea-2f8f-d48a92a04462
Accept: /
Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
Sec-Fetch-Site: none
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: ss-pid=qpPIOwaJozZg0KLcuZNF; ss-id=sudDOLX5eM9RvsunfX1h

{
“provider”:“apikey”,
“userName”:“ApiKey”,
“password”:“xxxxxxxxxx”
}

Response
HTTP/1.1 401 Unauthorized
Cache-Control: private
Vary: Accept
Server: Microsoft-IIS/10.0
WWW-Authenticate: Basic realm="/auth/apikey"
X-Powered-By: ServiceStack/6.00 Net45/Windowsnetfx/NO
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, Origin, Authorization, Access-Control-Allow-Origin
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RTpcQWx5bmtcQWx5bmtBcGlcQXV0b21vdGl2ZUx5bmsuQXBpXGFwaVxqc29uXHJlcGx5XEF1dGhlbnRpY2F0ZQ==?=
X-Powered-By: ASP.NET
Date: Tue, 01 Feb 2022 17:37:01 GMT
Content-Length: 0

The same request should have the same behavior, I’d expect something’s different, I’d start with removing all Cookies from Postman.

Can you also try to see if you can call a protected service that authenticates with the Service Clients via any of these supported methods:

var client = new JsonServiceClient(baseUrl) {
    Credentials = new NetworkCredential(apiKey, "")
};

var client = new JsonHttpClient(baseUrl) {
    BearerToken = apiKey
};

I found that If I decorate my request with [Authenticate] attribute it successfully passes the request.IsAuthenticated() check.

Now in IOC the services whose life cycle is ReusedWithin(ReuseScope.Request) I have custom logic of fetching HostContext.GetCurrentRequest() or base.TryGetCurrentRequest(). This request is not getting authenticated. for the same request.

Eg:
I have service OrderService which in constructor resolve IShipmentProvider and request method is ShipOrder.
Here IShipmentProvider resolved ReusedWithin(ReuseScope.Request) and accept base.TryGetCurrentRequest().

Now calling ShipOrder method getting authenticated successfully with api key but when IOC container goes to resolve the request from base.TryGetCurrentRequest() in IShipmentProvider it throws request.IsAuthenticated() false.

Any suggestions ?

I’d strongly recommend passing in IRequest (base.Request in Services) or the Users Session to any method that needs it, i.e. instead of trying to access it from a singleton request context which is more fragile, fails in bg threads, less testable & host dependent.

In hosts that support it it will return a new IRequest instance it creates Host Request and wont have access anything attached to the IRequest that’s passed through the ServiceStack request pipeline.

As per above eg: My OrderService is driven from ServiceStack.Service but IShipmentProvider is our custom service. How can I access base.Request in my custom service.

The IOC registration is as followed and the request which I get in GetConfig() method is unauthorized and is not containing any session.
container.Register(c => c.Resolve().GetConfig(base.TryGetCurrentRequest())).ReusedWithin(ReuseScope.Request);

If IShipmentProvider is a dependency, pass base.Request or base.SessionAs<T>() in as an argument to the methods which need it.

base.Request and base.SessionAs is not available to pass as argument


Not when registering it in the IOC, when calling it from your Service:

class MyService : Service
{
    public IShipmentProvider Dep { get; set; }
    public object Any(MyRequest request) => Dep.Method(base.SessionAs<MySession>());
}