Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passthrough Authentication in ServiceStack

I have two ServiceStack servers X and Y. Server X has functionality to register and authenticate users. It has RegistrationFeature,CredentialsAuthProvider, MemoryCacheClient and MongoDbAuthRepository features to handle the authentication. Recently, I introduced server Y and GUI forms that talk to server Y to handle another part of my business domain. Server Y needs to make requests to authenticated endpoints on server X.

How do I configure server Y in such a way that when it gets login requests from the GUI forms, it passes that responsibility to Server X which has access to the user information?

I tried implementing a custom CredentialsAuthProvider in server Y like so:

public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
    // authenticate through server X
    try
    {
        var client = new JsonServiceClient("http://localhost:8088");
        var createRequest = new Authenticate
        {
            UserName = userName,
            Password = password,
            provider = Name,
        };

        var authResponse = client.Post(createRequest);
        return true;
    }
    catch (WebServiceException ex)
    {
        // "Unauthorized
        return false;
    }
}

but later when I try to make a request from a service in server Y to an authenticated endpoint in server X, I get Unauthorized error.

public class MyServices2 : Service
{
    public object Any(TwoPhase request)
    {
        try
        {
            // make a request to server X on an authenticated endpoint
            var client = new JsonServiceClient("http://localhost:8088");

            var helloRequest = new Hello
            {
                Name = "user of server Y"
            };

            var response = client.Post(helloRequest);

            return new TwoPhaseResponse { Result = $"Server X says: {response.Result}" };
        }
        catch (WebServiceException e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

    ...
}
like image 765
tony Avatar asked Apr 11 '17 11:04

tony


1 Answers

This is highly dependent on the method of Authentication you choose. If you want to use CredentialsAuthProvider than you must ensure each Server is configured to use the same distributed Caching Provider instance (i.e. any Caching Provider other than MemoryCacheClient). This is because when you're authenticated, the Session Cookie Ids which point to an Authenticated User Session are populated on the Service Client which is sent with each Request. The ServiceStack Instance that receives the Session Cookie Ids would use it to access the Authenticated User Session in the registered caching provider.

If both ServiceStack Services are configured to use the same Caching Provider you could transfer the Session Cookie from the incoming Request to a new Service Client with something like:

Transferring Session Id

public object Any(ClientRequest request)
{
    // make a request to server X on an authenticated endpoint
    var session = base.SessionAs<AuthUserSession>();
    var client = new JsonServiceClient("http://localhost:8088");
    client.SetSessionId(session.Id);

    var response = client.Post(new Hello {
        Name = "user of server Y"
    });

    return new TwoPhaseResponse { Result = $"Server X says: {response.Result}" };
}

Transferring BasicAuthProvider Credentials

Otherwise if you're using HTTP Basic Auth with the BasicAuthProvider then the UserName/Password is sent with the Request which you can transfer to your internal Service Client with:

var basicAuth = base.Request.GetBasicAuthUserAndPassword();
client.UserName = basicAuth.Value.Key;
client.Password = basicAuth.Value.Value;
client.AlwaysSendBasicAuthHeader = true;

Which will copy the UserName/Password sent on the incoming request and send it with the outgoing Request. But for this to work both ServiceStack Instances must be configured to use the same BasicAuthProvider and User Auth Repository since the downstream Server needs to be able to validate the UserName/Password provided.

Transferring API Key

Likewise you can use the API Key AuthProvider to do something similar but instead of forwarding UserName/Password you can forward an API Key with:

var apikey = base.Request.GetApiKey();
client.BearerToken = apikey.Id;

Again this will need to be configured with the Same ApiKeyAuthProvider and User Auth Repository as the downstream server will require validating the API Key provided.

Using JWT AuthProvider for Stateless Authentication

Otherwise if you don't want each Server to share the same infrastructure dependencies (e.g. Caching Provider / User Auth Repository) I'd look at consider using the JWT Auth Provider which is ideal for this scenarios where Authenticating with one ServiceStack Instance that issues the the JWT Token encapsulates the Users Session and lets you make authenticated Requests to other ServiceStack instances which just need to have a JwtAuthProviderReader registered.

To transfer the JWT Token you can access it with:

var bearerToken = base.Request.GetBearerToken()
    ?? base.Request.GetCookieValue(Keywords.TokenCookie);

and populate it on the internal Service Client with:

client.BearerToken = bearerToken;
like image 141
mythz Avatar answered Nov 17 '22 22:11

mythz