Hi
I have Azure Ad B2C user Authentication set at ASP.Net level and then for ServiceStack I use NetCoreIdentityAuthProvider.
I have created BaseService class, which extends ServiceStack.Service and I use that BaseService class as base for all of my services.
In that BaseService class I access UserSession:
var session = base.SessionAs();
From there I get all relevant information Azure is sending me.
I use UserID from Azure to check which data user can access and in some cases user can’t access data at all (some uers in directory are rejected)
In such cases I throw UnauthorizedAccessException, which should lead to HTTP 403, but instead front end get’s http 500
I do not use custom exception handler.
Any Idea on what might be wrong?
Latest version of servicestack - 5.9.3…
Update, frontend gets:
{“ResponseStatus”:{“ErrorCode”:“Exception”,“Message”:“Error trying to resolve Service ‘Netos.Manager.Api.Services.Services.BillingPeriodService’ or one of its autowired dependencies (see inner exception for details).”,“Errors”:[]}}
In my case:
public class BillingPeriodService : BaseService
Well it does.
For users that do not get rejected by the system, it’s working.
I presume problem is that ServiceStack is catching all exceptions while resolving BillingPeriodService and treating them all in the same way (does not care which exception is thrown)
Below is my BaseService class for better understaning (in this moment is still in beta, so some debug stuff gets logged):
public class BaseService : Service
{
public readonly ILog _log;
public UserSession Session()
{
var session = base.SessionAs<UserSession>();
if (session == null)
{
_log.Error($"UserSession = null");
throw new AuthenticationException();
}
//ToDo Check user information in database - read TenantId
session.TenantId = (new DebugUser()).TenantId;
session.UserId = Guid.Parse(session.Id);
var userlist = HostContext.AppSettings.GetString("AllowedUsersList");
if (String.IsNullOrEmpty(userlist))
{
_log.Error("AllowedUsersList = null");
throw new UnauthorizedAccessException();
}
if (Array.IndexOf(userlist.Split(','), session.UserId.ToString()) == -1)
{
_log.Error($"Unauthorized user Id: {session.UserId}, Name: {session.FirstName}");
throw new UnauthorizedAccessException();
}
_log.Info($"User loged on, Name: {session.FirstName}");
return session;
}
public BaseService()
{
_log = LogManager.GetLogger(typeof(BaseService));
HostContext.Container.Register(Session());
}
Each of two “throw” is causing frontend to get
{“ResponseStatus”:{“ErrorCode”:“Exception”,“Message”:“Error trying to resolve Service ‘Netos.Manager.Api.Services.Services.BillingPeriodService’ or one of its autowired dependencies (see inner exception for details).”,“Errors”:[]}}
If you set DebugMode you should see the Exception StackTrace for more details about the Exception:
SetConfig(new HostConfig { DebugMode = true })
There are several ways to intercept Exceptions, e.g. you can intercept all Response Exceptions by overriding OnExceptionTypeFilter in your AppHost where you can inspect the InnerExceptions to identify the cause:
Entire issue is based on my expectation that me throwing UnauthorizedAccessException will automatically cause http 403 to be sent back to caller. Instead, caller get’s http 500
Seems I need to moove my code for checking the user access, as service resolver oes not check for type of exception
No don’t do that, also don’t mutate the IOC which should be immutable after Startup. You can set instance variables on the Service class if you need it.
Should you need to you can also Intercept Service Requests and execute code before a Service is executed if you want to execute any generic pre-service Request validation:
class MyServices : Service
{
public override void OnBeforeExecute(object requestDto)
{
//...
}
}
I actually do not have access to service or any of it’s extensions in the area where things are getting done.
What’s the best place to update my UserSession (extending AuthUserSession) with data from xyz source (now from app config, in close future from db) so I can access it anywhere in code?
Where you can check if the info isn’t already populated on the session before populating it.
If you need to instead populate this info on each request it should instead be stored in IRequest.Items[] which you can do in a Global Request Filter, see Multitenancy docs for examples.
In my case it’s per request, but I had some issues with that approach.
Will try again with OnSessionFilter later, If I have further problems, I’ll continue in this thread…
If it’s per request it should really be attached to the Request Context. I’d look at resolving what ever issues you had using it instead. If you populate it in a Request Filter you’ll be able to access everywhere that has access to IRequest, e.g. base.Request.Items in your Service.
Also I don’t understand why the session.Id is a Guid:
session.UserId = Guid.Parse(session.Id);
It should instead be an automatically generated Unique Identifier that matches the Session Cookies Id.
The IAuthSession.UserAuthId is what’s supposed to be use to retain the Users Id.
Are you populating session.Id somewhere? What Caching Provider are you using?
You can populate the external User reference on session.UserAuthId but it shouldn’t be resolving it from session.Id which should be left alone so its auto generated Id matches the session Cookie.
Plugins.Add(new AuthFeature(() => new UserSession(),
new IAuthProvider[] {
new NetCoreIdentityAuthProvider(AppSettings)
}));
This two together provide AuthUserSession.Id as Guid user Id from Azure. That part is working.
UI is sending bearer token which is verified per request, that part is working as expected.
Now I have created a PreRequestFilter which does what was in BaseService and I inject UserSession into a request:
request.Items[Keywords.Session] = session;
But now I’m back to my base issue (issue that got me entangled in all this):
public class BillingPeriodService : BaseService
{
private readonly IBillingPeriodBS _BillingPeriodBS;
public BillingPeriodService(IBillingPeriodBS BillingPeriodBS)
{
_BillingPeriodBS = BillingPeriodBS;
}
public async Task<SaveBillingPeriodResponse> Put(SaveBillingPeriod request)
{
return await _BillingPeriodBS.Save(request);
}
ServiceStack resolves IBillingPeriodBS, but I can’t seem to find a way to tell him to inject UserSession from my request into it
Save needs access to UserSession (CreatorUserId, ModifierUserId, TenantId), but that data is not accessible unless I use HostContext to read my IRequest and UserSession out of it, but then I have problems in UnitTests where I do not have HostContext.
It’s a bit late and if I don’t make sense, I’ll retype this tomorrow
Some of my BS classes have lot’s of methods that need access to UserSession so I created a property in my base class and a method which initializes it’s value (some BS classes cal several other BS so when I initialize session in one, itneeds to be initialized in all)
All working now, all unit tests have green
Thank you for pointing me in right direction.