Hi Rui,
I'm glad to hear it :) Yeah it serves me quite well from sometime as well, but its often hard to structure the documentation properly and explain the benefits - Something I need to find more time for and get better at!
Note: a lot of your requirements and how you ultimately handle the authentication is up to your coding style preference.
What's important is to be able to execute auth/validation logic for every request and that you have access to the container so you can pull out your re-usable AuthenticationService code to validate the request.
Something to keep in mind is that in ServiceStack we're free of
ASP.NET configuration authentication providers and the like, so there is no special magic behaviour you need to meet or special configuration to get the desired behaviour you want, you simply just have to code for the behaviour you want. The
UserSession classes is similar to the approach we took at mflow, though we ultimately stored our authentication in one of the ServiceStack caching providers.
So based on your requirements:
2. The client would have an supplied API key and user name that allows
it to call the service;
3. We could have LoginService that would return a session ID to be
used on the following service calls;
It sounds like you want this information passed as part of an explicit initial request into your LoginService, which will check to ensure this information is valid, if it is it should create a new SessionId (i.e. Guid) and store a reference the the user and perhaps their Roles, ClientType, etc i.e. any other information the IAuthProvider will need as per your fine-grained authentication requirements.
4. We would have also the need to have services roles for example we
have a service that have several methods but we might want to restrict
access for some of them based on the client type (customer, supplier,
etc);
From here you're going to want to access the auth provider and apply your specific style of validation.
I would do something like use the SessionId to retrieve the user's Session (prepared in the login) and use that to work out what type of client and whether they have access or not.
If different services have different authorization rules, you might want to differentiate this by using a custom attribute on the request DTO:
So If you're applying the validation logic in a baseclass you can throw a C# exception for unauthorized requests, e.g:
[CustomAuth("SpecialAccess")]
class AnyRequestDto : IRequiresSession
{
public Guid SessionId {get;set;}
}
public class ServiceBase<T> {
public IAuthProvider AuthProvider { get; set; } // Injected by IOC
public object Execute(TRequest request)
{
try
{
var requiresSession = req as IRequiresSession;
if (requiresSession != null)
{
if (!AuthProvider.IsValid(requiresSession.SessionId))
throw new UnAuthorizedException();
var attrs = requestType.GetCustomAttributes(typeof(CustomAuthAttribute), true);
if (attrs.Length > 0) {
var authAttr = (CustomAuthAttribute)atts[0];
if (!AuthProvider.IsValid(requiresSession.SessionId, authAttr.ClientType))
throw new UnAuthorizedException();
}
}
return Run(request);
}
catch (Exception ex)
{
return HandleException(request, ex);
}
}
Here is the source code of the ServiceBase.cs and RestServiceBase.cs classes, which just provides convenience exception handling for your services:
They're kept in a separate 'user high-level' assembly, separated from the framework on purpose as I expect different people may want to tweak it to their own needs or subclass it as desired.
this.RequestFilters.Add((req, res, dto) =>
{
var requiresSession = req as IRequiresSession;
if (requiresSession != null)
{
if (!AuthProvider.IsValid(requiresSession.SessionId)) {
res.ReturnAuthRequired();
return;
}
var attrs = requestType.GetCustomAttributes(typeof(CustomAuthAttribute), true);
if (attrs.Length > 0) {
var authAttr = (CustomAuthAttribute)atts[0];
if (!AuthProvider.IsValid(requiresSession.SessionId, authAttr.ClientType)) {
res.ReturnAuthRequired(); return;
5. We don't want to duplicate code on each service to comply (only in
the restriction exceptions? just an idea) with the previous;
6. Just as a matter of code-design, I would personally I would wrap
all your EF + Auth logic behind an IAuthProvider-like class &
interface and have that registered as a normal dependency in Funq IOC
in your AppHost.Configure().
My major goal is to achieve this requirements keeping a good code-
design.
Yeah I agree that should always be the goal. What I'm alluding to is that you should provide your own custom class + interface to do this, insulating the fact that its actually works using EF behind the scenes.
e.g. based on the above source code, we would need a custom class that implements this:
interface IAuthProvider {
bool IsValid(Guid sessionId);
bool IsValid(Guid sessionId, string clientType);
}
then in your AppHost you would need something like this:
public override void Configure(Container container)
{
container.Register<IAuthProvider >(new MyCustomEfAuthProvider(someConfigThisNeeds));
}
Note: I'd probably have the above interface a little different API myself to take inconsideration that you only want 1 db call (or my personal pref 1 redis call :) at most (if possible) to handle, but I'll leave that up to you to work out.
Anyway I hope this helps make things a little clearer!
If you manage to get this up an running I would really appreciate if you can share your approach in a blogpost or something so we can share the knowledge and help others looking to do the same thing :)
Thanks,