JWT Tokens with custom credentials auth provider

Hi all,

  1. We have a custom credentials auth provider that returns a custom authentication response with custom auth user session object. I have set up a JWTAuthProvider as follows:
Plugins.Add(new AuthFeature(() => (new MembershipAuthSession()),
    new IAuthProvider[] {
        new JwtAuthProvider(AppSettings) { 
            AuthKey = AesUtils.CreateKey(), 
            RequireSecureConnection = false 
        },
        new CredentialsProvider()
    });

However, in Postman I only see the ss-id and ss-pid cookies, and nothing related to bearer tokens.
I also tried testing via JsonServiceClient and the auth response has BearerToken as null. Anything else I need to do to be able to return bearer tokens.

var authClient = new JsonServiceClient("http://localhost:49480/api/auth/credentials?applianceId=EE6CDE78-EF5F-4D23-87D1-41AA6FE51066");

var authResponse = authClient.Send(new Authenticate {
        //<AuthenticationResponse>("api/auth/credentials?applianceId=EE6CDE78-EF5F-4D23-87D1-41AA6FE51066", 
     new Authenticate
    //{
        provider = CredentialsAuthProvider.Name, // credentials auth provider name
        UserName = "xxxx@xxxx.com.au",
        Password = "testing",
        //UseTokenCookie = true
    });
    try
    {
        var token = authResponse.BearerToken;
        authClient.Send(new ConvertSessionToToken());
        var t = authClient.GetTokenCookie();
    }
    catch(Exception ex)
    {
        var x = ex.Message;

  1. In my custom auth session object, we have a provider id being returned. Once this session is saved to the bearer token and sent on the next request. How is it possible to extract this provider id from the bearer token?

Thanks,
Leeny

Your baseUrl used in all Service Clients is the BaseUrl of your ServiceStack instance, it’s not a URL to an endpoint, so it should be something like:

var authClient = new JsonServiceClient("http://localhost:49480/api/");

You can’t add a custom queryString to the BaseUrl if you want to add additional info you can use the Meta dictionary on the Authenticate Request DTO, e.g:

var authResponse = authClient.Send(new Authenticate {
    Meta = new Dictionary<string, string> {
        ["applianceId"] = "EE6CDE78-EF5F-4D23-87D1-41AA6FE51066"
    }
});

Alternatively you can send additional metadata via the HTTP Headers, e.g:

var authClient = new JsonServiceClient("http://localhost:49480/api/") {
    RequestFilter = req => req.Headers["applianceId"] = "EE6CDE78-EF5F-4D23-87D1-41AA6FE51066"
}

Your Custom AuthProvider will need to look for it using whatever approach you’ve used to send it.

Only the essential UserSession info is embedded in the JWT, see the section on Modifying the Payload for adding and retrieving any custom fields in the JWT.


When including source code please wrap them in triple backticks otherwise it’s unreadable, e.g:

```csharp
// C# source code
```

Thank you Demis for being so prompt with responses. It makes such a huge difference.

I will implement these changes now. So the custom credentials provider does not need to do anything to generate these bearer tokens other than specify the jwtauthprovider?

Leeny

The JwtAuthProvider does it, but it’s possible your CredentialsProvider could interfere with it being created, wont know without seeing the implementation. Make the changes and look at the raw HTTP Response Headers to see if the BearerToken is being returned.

Note: as mentioned at the start of the JWT AuthProvider instead of AesUtils.CreateKey() you should use a consistent AuthKey otherwise whenever your App restarts it will invalidate all JWT that were created before. If maintaining it in a configuration file you can serialize binary data to Bas64 with Convert.ToBase64String().

thanks. I tried this but the bearer token is still null.

My custom credentials provider overrides TryAuthenticate - calls our login methods and add custom values to the custom auth session object.
It also overrides ‘Authenticate’ - here it calls base.Authenticate an if successful, then creates the custom authentication response object.

Anything else I need to do here?

Does it call OnAuthenticated?

couple of things here

  1. I tried adding CreatePayload… to JwtAuthProvider and put a breakpoint there but it never gets called.
  2. I had an override OnAuthenticated and added this line as in another forum topic
            authService.Request.Items[Keywords.DidAuthenticate] = (object)true;
  1. I did add OnAuthenticated manually and the response is null.

CreatePayloadFilter does get called whenever the JWT is generated.

OnAuthenticated() only returns non-null for error or redirect responses.

Look at my previous comment showing how CredentialsAuthProvider calls OnAuthenticated() where it only returns if its not null otherwise it returns its AuthenticateResponse.

Thanks Demis. I finally managed to manually create a jwt token in OnAuthenticated and this came through. I am not sure if this is because of my custom authentication response object.

Do you think it should work out of the box even with a custom auth response without manually creating a token? But I suppose, I have a custom session object which i need to manually add to the token, so may need to go this route anyway.

Thanks for your replies yesterday.

You’re still returning a AuthenticateResponse DTO? i.e. you’re not changing the DTO type you’re returning right?

The override Authenticate method is returning a custom Authentication response, so not AuthenticateResponse dto.

Also, do I have to manually call IsValidJwt or would it be done automatically by the [Authenticate] attribute?

You shouldn’t be changing the Response Type for the Authenticate service, it’s expected a successful Authenticate request will return an AuthenticateResponse. If you want to return additional metadata you can populate the Meta dictionary. If you need a custom Response DTO for whatever reason call your own Service separately but don’t change the contract of the built-in Authenticate service.

The JWT AuthProvider only allows authentication of valid JWT tokens.

Thank you Demis. You are a star!