Server events: How to require authorized clients? - servicestack

I have a ServerEventsClient which gets notified when the server raises an event.
The Server has a working custom CredentialsAuthProvider implementation.
This is the code to start the client (I customize the urls at the server):
int port = 4711;
string baseMessagingUrl = "http://localhost:{0}/messaging".Fmt(port);
string authorizationUrl = "http://localhost:{0}/api/auth/login".Fmt(port);
string channel = "customer/4711";
var client = new ServerEventsClient(baseMessagingUrl, channel);
client.RegisterNamedReceiver<CcuEventReceiver>("ccu");
client.ServiceClient.Post<AuthenticateResponse>(authorizationUrl, new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "fred",
Password = "123",
RememberMe = true,
});
client.Start();
At the server: before sending a message I check the subscription details via
List<Dictionary<string, string>> subscriptionsDetails = _serverEvents.GetSubscriptionsDetails(changeEvent.CustomerId);
They didn't contain the expected authentication details I send during authorization, but that:
"userId": -6
"displayName": user6
"profileUrl": a url to githubusercontent
Same information within the "OnConnect" event (ServerEventsFeature).
How do I ensure that only authenticated clients are able to subscribe to events?

Support for Authenticating with ServerEventsClient has been added in this commit which is available in v4.0.32+ that's now on MyGet.
Authenticating ServerEvents Client
There are new explicit Authenticate and AuthenticateAsync API's which can be used to authenticate the ServerEvents ServiceClient which now shares cookies with the WebRequest that connects to the event stream, so you can now authenticate with:
client.Authenticate(new Authenticate {
provider = CredentialsAuthProvider.Name,
UserName = "fred",
Password = "123",
RememberMe = true,
});
client.Start();
These API's are just extension methods on ServerEventsClient so can be easily extended if you want customized behavior:
public static AuthenticateResponse Authenticate(this ServerEventsClient client,
Authenticate request)
{
return client.ServiceClient.Post(request);
}
Limiting Server Events to Authenticated Clients Only
There's also a new LimitToAuthenticatedUsers option in ServerEventsFeature to limit access to authenticated clients only:
Plugins.Add(new ServerEventsFeature {
LimitToAuthenticatedUsers = true,
});
Which when enabled will return a 401 Unauthorized for non-authenticated clients.

Related

How to put an Api key in the Authenticate message?

I'm trying to combine the api key auth provider with the encrypted messaging plugin.
var client = new JsonServiceClient(home);
client.BearerToken = "somesecret";
works
but i want my apikey to be in the message so i tried
var authResponse = client.Post(new Authenticate
{
provider = ApiKeyAuthProvider.Name,
UserName = "somesecret"
});
This post fails at runtime with a 401 not authenticated.
How do i get this to work?
IAuthWithRequest Auth Providers like the API Key Auth Provider needs to be sent per request with the Authenticated User Session it establishes only lasts for the lifetime of that request. It can't be used with the Authenticate Service to Authenticate the client as your example tried to do, it must be included in each request to an Authenticated Service.
The normal way to call a protected Service with the API Key is to just populate the BearerToken property:
var client = new JsonServiceClient(baseUrl) {
BearerToken = apiKey
};
Which will then let you call your [Authenticate] Service:
var response = client.Get(new Secure { Name = "World" });
Encrypted Messaging Support
Previously you could only embed the User SessionId within an Encrypted Messaging Request but I've just added support for Authenticating Encrypted Messaging Services with a BearerToken in this commit which works similar to populating a SessionId, where you can now populate a BearerToken as used in API Key and JWT Auth Providers by having your Request DTOs implement IHasBearerToken, e.g:
public class Secure : IHasBearerToken
{
public string BearerToken { get; set; }
public string Name { get; set; }
}
This will let you embed the BearerToken when calling the protected Service, e.g:
IEncryptedClient encryptedClient = client.GetEncryptedClient(publicKey);
var response = encryptedClient.Get(new Secure { BearerToken = apiKey, Name = "World" });
Where it will be embedded and encrypted along with all content in the Request DTO.
Alternatively you can also set the BearerToken property on the IEncryptedClient once and it will automatically populate it on all Request DTOs that implement IHasBearerToken, e.g:
encryptedClient.BearerToken = apiKey;
var response = encryptedClient.Get(new Secure { Name = "World" });
The new BearerToken support in Encrypted Messaging is available from v5.1.1 that's now available on MyGet.

How to call from one authorized service to another service that requires authorization

I have an Item Service:
[Authenticate]
public class ItemService : ServiceStack.Service {
//implementation
}
Within the ItemService I am debugging this Get method that has received a valid Token and is able to successfully create session:
public GetItemResponse Get(GetItem request)
{
var session = SessionAs<CustomUserSession>();
var authToks1 = session.GetAuthTokens();//empty
var authToks2 = session.GetAuthTokens(_authServiceConnection);//null
var authService = new JsonServiceClient(_authServiceConnection);
//not authorized
ConvertSessionToTokenResponse attempt1 = authService.Send(new ConvertSessionToToken());
//not authorized
ConvertSessionToTokenResponse attempt2 = authService.Send(new ConvertSessionToToken() { PreserveSession = true });
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = "what to do",
RefreshToken = "what to do"
};
return new GetItemResponse();
}
Obviously I am simply trying to call another service, AccountService:
[Authenticate]
public class AccountService : ServiceStack.Service {
//implementation
}
How to include a JWT Refresh token or JWT Bearer token in a request from one authorized service to another service that has the ServiceStack Authenticate attribute.
Note I am using a custom Auth provider, I have a legacy database.
If this another ServiceStack Service with the same Host you should use the Service Gateway as internal requests are executed in process directly which avoid the request filter validation.
var response = Gateway.Send(new MyRequest());
If you're trying to call a remote ServiceStack Service you can get the JWT Token sent with the request with IRequest.GetJwtToken() extension method and forward it to the downstream Service requests:
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = Request.GetJwtToken(),
};

Multiple AuthProvider Servicestack

I am having some issue to make work 2 auth provider at the same time for servicestack.
I am using the : JWT Tokens - Allowing users to authenticate with JWT Tokens I am my users get authenticate fine.
Still Now I would like to use the API Keys - Allowing users to authenticate with API Keys for a few external 3rd Parties user access.
Still when I Configure both my users allready authenticate by JWT Tokens doesnt work anymore.
Here is my configuration AuthProvider configuration :
IAuthProvider[] providers = new IAuthProvider[]
{
new JwtAuthProviderReader(this.AppSettings)
{
HashAlgorithm = "RS256",
PrivateKeyXml = this.AppSettings.GetString("TokenPrivateKeyXml"),
PublicKeyXml = this.AppSettings.GetString("TokenPublicKeyXml"),
RequireSecureConnection = this.AppSettings.Get<bool>("TokenUseHttps"),
EncryptPayload = this.AppSettings.Get<bool>("TokenEncryptPayload"),
PopulateSessionFilter = (session, obj, req) =>
{
ApplicationUserSession customSession = session as ApplicationUserSession;
if (customSession != null)
{
customSession.TimeZoneName = obj["TimeZoneName"];
customSession.Type = (FbEnums.UserType) (obj["UserType"].ToInt());
if (Guid.TryParse(obj["RefIdGuid"], out Guid result))
{
customSession.RefIdGuid = result;
}
}
},
},
new ApiKeyAuthProvider(AppSettings)
{
RequireSecureConnection = false
}
};
I am genereting fine the token with JwtAuth. Still It look like servicestack is not accepting my token as a valid session, because now whenever I do :
var session = httpReq.GetSession();
session.IsAuthenticated --> is always FALSE
If my remove ApiKeyAuthProvider from the configuration, token from JwtAuth working fine again.
How do I make both provider works together and tell servicestack tham some users will use JwtAuth and others ApiKeyAuth ?
You need to call a Service that requires Authentication, e.g. has the [Authenticate] attribute in order to trigger pre-Authentication for the IAuthWithRequest providers like JWT and API Key AuthProviders.

UNAUTHORIZED with API key

I try to authenticate a call from a service to another service using an API key. An administrative service creates 'service account users' when it is started for the first time. Now when a service calls another service I have:
Client = new JsonServiceClient("http://TheOtherServer:1234")
{
Credentials = new NetworkCredential(<the string with my api key>, ""),
};
//.....
var request = new RequestDtoOfOtherServer
{
//set some request props
};
try
{
var result = Client.Get(request);
//do something with result
}
catch (Exception ex)
{
Log.Error($"Error: {ex}");
throw;
}
Whatever key I use from the 2 keys issued for the calling service user, I always get a 401 UNAUTHORIZED error. I turned on the RequestLogsFeature on the receiving service but there is NO entry.
The method I call is annotated with [RequiresAnyRole("User", "Administrator", "bizbusops-service", "SYSTEM")] and the user which is related to the API key I use is in the Role bizbusops-service. Also when I use my WPF UI and login with that user (with username / password) I can access this method without error. So there must be something wrong with establishing the server-to-server connection and / or the API key.
What am I missing?
Does the above code with NetworkCredential establish a session between the two servers and issue a cookie?
I see in the Redis DB that two keys are issued to the user account of the service. Can I use both of them or do I have to set the Environment and KeyType somewhere on the server side, e.g. in a RequestFilter?
UDATE
On the server which receives the authentication calls I have configured the AuthFeature Plugin like so:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BizBusAuthProvider(),
new ApiKeyAuthProvider(AppSettings)
{
KeyTypes = new []{"secret", "publishable"},
},
}
));
This configuration generated 4 API keys for every new user, the ones defined above and the two created by default.
If you're going to use Credentials to send the API Key then you'll need to register the ApiKeyAuthProvider so it's the first AuthProvider listed, e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings)
{
KeyTypes = new []{"secret", "publishable"},
},
new BizBusAuthProvider(),
}
));
This is so when .NET's WebRequest receives a 401 WWW-Authenticate challenge response it will automatically add the Credentials when retrying the Request.
Otherwise you can use a BearerToken to send the API Key, e.g:
var client = new JsonServiceClient(baseUrl) {
BearerToken = apiKey
};

ServiceStack Ws-Security Auth Provider

I'm trying to figure out how to support ws-security as authentication mechanism in SS.
My goal is to have all DTO handled in json,xml,saop11,saop12(that part has been achieved following the SS documentation) and supporting multiple auth providers including one based on ws-security.
DTOs should not be affected at all by the authentication mechanism.
In case the DTO will be sent using saop12, the soap message will be the the call sample generated by the metadata endpoint(soap envelope + soap body) plus a soap header including the ws-security element for the WS-Security Username Authentication. A dedidcated "soap auth provider" should inspect the message, use the soap header -> security element and perform the authentication.
Along with the soap auth provider, I may have other built-in auth mechanism that may used for json message and/or other formats.
Exists a SS auth provider based on ws-security that I'm not aware of?
Any guidelines, suggestions, thoughts to implement it?
At the present than my solution
//AppHost
Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new SoapMessageAuthProvider(),
}
));
// required by the SoapMessageAuthProvider to inspect the message body serching for ws-security element
PreRequestFilters.Add((httpReq, httpRes) =>
{
httpReq.UseBufferedStream = false;
});
I based the SoapMessageAuthProvider on the built-in BasicAuthProvider.
Since the SoapMessageAuthProvider requires to inspect the incoming message on each call serching for ws-security element, I implemented IAuthWithRequest
public void PreAuthenticate(IRequest req, IResponse res)
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null);
var userPass = ExtractSoapMessageUserNameCredentials(req);//req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthenticateService>();
//var response = authService.Post(new Authenticate
//{
// provider = Name,
// UserName = userPass.Value.Key,
// Password = userPass.Value.Value
//});
authService.Request = req;
var session = authService.GetSession(false);
var userName = userPass.Value.Key;
//Add here your custom auth logic (database calls etc)
var userAuth = new UserAuth();
userAuth.Id = 10;
userAuth.UserName = userName;
var holdSessionId = session.Id;
session.PopulateWith(userAuth); //overwrites session.Id
session.Id = holdSessionId;
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.UserAuthName = userName;
}
}
//called by CustomAuthUserSession.IsAuthorized
// to be reviewed to keep isolated from other providers
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName))
{
return false;
}
}
return !session.UserAuthId.IsNullOrEmpty();//filled by PreAuthenticate
}
the custom session calls each provider, including the SoapMessageAuthProvider that meanwhile, through the PreAuthenticate method, filled out the session with authenticated user data.
public class CustomAuthUserSession : AuthUserSession
{
public override bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
return AuthenticateService.GetAuthProvider(provider).IsAuthorizedSafe(this, tokens);
}
...
}
I need to make sure the soap provider will be always invoked for soap message w/ ws-security and the call should not be authenticated by other providers:
- user get authentication through the CustomCredentialsAuthProvider(cookie based)
- user call the service supply json message within the web request that carries the auth cookie
- a further call sends a soap message carrying the same auth cookie: since the message is in soap format and includs the soap header ws-security, the call should be authenticated only using the soap provider using the soap header ws-security within the message.
I understand that a weird scenario, but I'm trying to understand how to accomplish it.
My guess that happends through the ServiceStack.AuthenticateAttribute line 72
matchingOAuthConfigs.OfType<IAuthWithRequest>()
.Each(x => x.PreAuthenticate(req, res));

Resources