ServerEvents + JWT authentication

Hi

I’ve build an SPA that uses JWT to authenticate with my server. I know want to fire event to specific userId’s.

I’ve got the following code:
var eventsClient = new ServerEventsClient(runtimeConfig.apiRoot, [ ‘import-policy’ ])
eventsClient.serviceClient.setBearerToken(this.$auth.getToken())
eventsClient.start()

but getting the following error messages:
GET http://localhost:32768/event-stream?channels=import-policy&t=1512577874867 401 (Unauthorized)

Failed to load http://localhost:32768/event-stream?channels=import-policy&t=1512577874867: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:8080’ is therefore not allowed access.

Please help.

It looks like you’re attempting a cross-domain request? In which case you’ll need register the CorsFeature and register all the domains you want to allow access from, e.g:

Plugins.Add(new CorsFeature(
    allowOriginWhitelist: new[] { "http://localhost:8080" },
    allowCredentials: true,
    allowedHeaders: "Content-Type, Allow, Authorization, X-Args"));

Thanks that has got rid of the one error, I am still getting the 401 Unauthorized error:
http://localhost:32768/event-stream?channels=import-policy&t=1512584431355 401 (Unauthorized)

Is this the correct way to authenticate my eventsClient?
var eventsClient = new ServerEventsClient(runtimeConfig.apiRoot, [ ‘import-policy’ ])
eventsClient.serviceClient.setBearerToken(this.$auth.getToken())
eventsClient.start()

You would typically need to authenticate with the server so it returns authenticate UserSession Cookies that are attached to subsequent requests. So it would work if you authenticated with the Service Client.

In this case you already have the JWT Token and need to convert it to a JWT Cookie. Something you can try is calling the ConvertSessionToToken API with the JWT Token before connecting with ServerEventsClient, e.g:

var headers = new Headers();
headers.set("Authorization", "Bearer " + this.$auth.getToken());
fetch(runtimeConfig.apiRoot + "/session-to-token", 
    { method:"POST", headers, credentials:"include" });

This should convert the Session into a JWT Cookie.

That code executes fine, but I am still getting the 401 error, here is my code at the moment:
let headers = {
Authorization: 'Bearer ’ + this.$auth.getToken()
}

fetch(runtimeConfig.apiRoot + '/session-to-token', {
  method: 'POST',
  headers,
  credentials: 'include'
}).then(result => {
  console.log(result)
  // client.setCookie('ss-tok', this.$auth.getToken())
  let eventsClient = new ServerEventsClient(
    runtimeConfig.apiRoot,
    ['import-policy'],
    {
      handlers: {
        onMessage: msg => {
          console.log(msg)
        }
      }
    }
  )
  // eventsClient.serviceClient.setBearerToken(this.$auth.getToken())
  eventsClient.start()
})

Can you provide the raw HTTP Request / Response Headers for both requests. Please stub out any sensitive info with xxxx.

Also please provide your complete Server AuthFeature plugin configuration.



        host.Plugins.Add(new AuthFeature(() => new S3BUserSession(), new IAuthProvider[] {
            new S3BCredentialProvider() {
                PersistSession = true,
                LoadUserAuthFilter = ( session, tokens, arg3 ) => {

                }
            },
            new JwtAuthProvider {
                AuthKey = GlobalConfig.Settings.SigningKey,
                RequireSecureConnection = false,
                Issuer = "S3B",
                PersistSession = true,
                PopulateSessionFilter = ( session, o, arg3 ) => {
                    var userAuth = HostContext.TryResolve<IAuthRepository>().GetUserAuth( session, null );
                    if ( session is S3BUserSession typedSession ) {
                        typedSession.LegacyUserId = userAuth.RefIdStr?.ReplaceFirst( @"AUTOMIGRATED-", String.Empty );
                    }
                },
            },
            new ApiKeyAuthProvider( host.AppSettings ) {
                // Both for test, and live, which uses cloudflare. Change later
                RequireSecureConnection = false,
                KeyTypes = new[] {"private", "public"},
                KeySizeBytes = 48,
                SessionCacheDuration = TimeSpan.FromMinutes( 20 ),
                AllowInHttpParams = true,
                RedirectUrl = "/signin",
                PersistSession = true
            }
        }));

Is there a HTTP request missing? There’s only the preflight OPTIONS /session-to-token request but not the POST /session-to-token it’s meant to have sent.

Is there any error messages in Chrome Inspector’s console window indicating why the preflight request may have failed?

No console errors and I’ve been noticing quite often requests being made, but it doesn’t show the POST and GET’s in network tab in Chrome.

But when I put a console log in my .then of the promise it does return output.

Can you try to get the raw HTTP Request/Response Headers using Fiddler then? The Response of the POST /session-to-token is the important request.

POST http://localhost:32768/session-to-token HTTP/1.1
Host: localhost:32768
Connection: keep-alive
Content-Length: 0
authorization: Bearer xxxxx
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
Accept: /
Referer: http://localhost:8080/import
Accept-Encoding: gzip, deflate, br
Accept-Language: en-ZA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: __uvt=; uvts=6RaxsJzzJxBbLKnL; _ga=GA1.1.1076114447.1503064080; ss-pid=nMxPrEXZTvuSU7yidBkO; ss-id=DdOKLTHBE6iEHdBww1jg

Note: don’t post the JWT token itself as it’s unencrypted so anyone can read its contents.

Ok it looks like the issue is that /session-to-token wasn’t converting JWT’s to a JWT Cookie since it was already a JWT which should be doing it from this commit.

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

Also if you’re using .NET Core please note the v5 changes where the ServiceStack .NET .Core packages are now merged into the main NuGet packages so you’ll need to remove the .Core suffix, e.g:

<PackageReference Include="ServiceStack" Version="5.*" />