ServiceStack MQ: how to populate data in RequestContext - servicestack

I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.

The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}

Related

What is the right way to pass request data to services in nestjs?

I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.

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(),
};

ServiceStack keep a long-live connection and send response asynchronously

I have a client app which monitors the changes in real-time by establishing a long-live HTTP connection to server.
In ASP.NET WebAPI, the server can take use PushStreamContent to keep the connection for a long time and send response once there is an update.
But in ServiceStack, seems there is no similar stuff.
I looked at the sample code of Different ways of returning an ImageStream
IStreamWriter.WriteTo method is only called once, and I can't use async IO operation to avoid blocking server thread.
Is there a way to send progressive response to client asynchronously?
here is sample code in WebAPI which does the job
public static async Task Monitor(Stream stream, HttpContent httpContent, TransportContext transportContext)
{
ConcurrentQueue<SessionChangeEvent> queue = new ConcurrentQueue<SessionChangeEvent>();
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
Action<SessionChangeEvent> callback = (evt) =>
{
queue.Enqueue(evt);
tcs.TrySetResult(null);
};
OnSessionChanged += callback;
try
{
using (StreamWriter sw = new StreamWriter(stream, new UTF8Encoding(false)))
{
await sw.WriteLineAsync(string.Empty);
await sw.FlushAsync();
await stream.FlushAsync();
for (; ; )
{
Task task = tcs.Task;
await Task.WhenAny(task, Task.Delay(15000));
if (task.Status == TaskStatus.RanToCompletion)
{
tcs = new TaskCompletionSource<object>();
SessionChangeEvent e;
while (queue.TryDequeue(out e))
{
string json = JsonConvert.SerializeObject(e);
await sw.WriteLineAsync(json);
}
task.Dispose();
}
else
{
// write an empty line to keep the connection alive
await sw.WriteLineAsync(string.Empty);
}
await sw.FlushAsync();
await stream.FlushAsync();
}
}
}
catch (CommunicationException ce)
{
}
finally
{
OnSessionChanged -= callback;
}
}
Writing to a long-running connection is exactly what Server Events does. You can look at the implementation for ServerEventsHandler or ServerEventsHeartbeatHandler to see it's implemented in ServiceStack.
Basically it just uses a custom ASP.NET IHttpAsyncHandler which can be registered at the start of ServiceStack's Request Pipeline with:
appHost.RawHttpHandlers.Add(req => req.PathInfo.EndsWith("/my-stream")
? new MyStreamHttpHandler()
: null);
Where MyStreamHttpHandler is a custom HttpAsyncTaskHandler, e.g:
public class MyStreamHttpHandler : HttpAsyncTaskHandler
{
public override bool RunAsAsync() { return true; }
public override Task ProcessRequestAsync(
IRequest req, IResponse res, string operationName)
{
//Write any custom request filters and registered headers
if (HostContext.ApplyCustomHandlerRequestFilters(req, res))
return EmptyTask;
res.ApplyGlobalResponseHeaders();
//Write to response output stream here, either by:
res.OuputStream.Write(...);
//or if need access to write to underlying ASP.NET Response
var aspRes = (HttpResponseBase)res.OriginalResponse;
aspRes.OutputStream...
//After you've finished end the request with
res.EndHttpHandlerRequest(skipHeaders: true);
return EmptyTask;
}
}
The ApplyCustomHandlerRequestFilters() and ApplyGlobalResponseHeaders() at the start gives other plugins a chance to validate/terminate the request or add any HTTP Headers (e.g. CorsFeature).
Have a look at ServerEvents. If I understood you right, this is what you are looking for.

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));

Extending service stack authentication - populating user session with custom user auth meta data

I am trying to extend Service Stack's authentication and registration features. I have the authentication and registration working fine, however I need to add some custom data for each user. From Service Stack's documentation and various other posts I found you can add your own data using the MetaData column built into the UserAuth table.
I created a CustomAuthRepository so I can set the meta data property of UserAuth, here is my custom repo:
public class CustomAuthRepository : OrmLiteAuthRepository, IUserAuthRepository
{
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new LoginInfo
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
}
This is working great for setting the meta data, I end up with a serialized version of the LoginInfo object in the meta data column of the UserAuth table.
Now what I am trying to do is when a user authenticates I need to change the AuthResponse based on some of that meta data. For example, if a user is not yet activated I want to return an AuthResponse with a property IsActive = get value from custom meta data
I figure I could do this if I can get my custom metadata into the AuthSession. That way in my custom credentials auth provider I could change the response object based on what's in the AuthSession:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var customUserAuthSession = (CustomUserAuthSession)session;
if (!customUserAuthSession.LoginInfo.IsActive)
{
return new
{
UserName = customUserAuthSession.UserName,
IsActive = customUserAuthSession.LoginInfo.IsActive
};
}
var isAuthenticated = base.Authenticate(authService, session, request);
return isAuthenticated;
}
}
Am I going about this the right way, or is there a better way to store and retrieve custom meta data?
How can I change the AuthResponse based on a user's custom meta data?
How can I get my custom meta data into the AuthSession?
Edit
I am getting closer to what I am trying to do. In my CustomAuthSession OnAuthenticated() method :
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
var customUserAuthSession = (CustomUserAuthSession) session;
var userAuth = authService.ResolveService<IUserAuthRepository>().GetUserAuth(session, null);
customUserAuthSession.LoginInfo = userAuth.Get<LoginInfo>();
authService.SaveSession(customUserAuthSession);
base.OnAuthenticated(authService, session, tokens, authInfo);
}
I am refetching the UserAuth and populating the session with the data that I need. Based on the service stack documentation for a custom user session, you need to save the session after you populate it with some custom data. I am doing that but it doesn't seem to be saving.
In my CustomCredentialsAuthProvider, Authenticate method, I don't see the custom data I've added to the session.
Edit
The problem with my first edit above is that the user gets authenticated, then we get to the CustomAuthSession code where I can check if they are active or not. In the case they are not active I would need to log them out, not ideal.
I found instead that I can do all of this in the Authenticate method of my custom CredentialsAuthProvider.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var loginInfo = userAuth.Get<LoginInfo>();
if (!loginInfo.IsActive)
{
return new CustomAuthResponse
{
UserName = userAuth.UserName,
ResponseStatus = new ResponseStatus("500"),
IsActive = loginInfo.IsActive
};
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When the request comes in I can use the username in the request to fetch the UserAuth, and check if the user IsActive or not. If not then I can return some error before Service Stack authenticates them.
I think this works well enough for what I am trying to do. I should be able to return an error to the client saying the user is not active.
If anyone has a cleaner way to do this that would be great.
Here is my answer so far. It works and I can do what I am trying to do, but I would love to hear from some of the Service Stack guys as to whether this is the best way to go about this.
To Save custom meta data
Create a new class that subclasses the OrmLiteAuthRepository. In my case I just want to use Service Stack's built in Sql database persistence and have it create the tables needed.
Re-implement the CreateUserAuth method to save any custom metadata :
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new AccountStatus
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
Fetching custom meta data
Create a new class that subclasses the CredentialsAuthProvider. Override the Authenticate method.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var accountStatus= userAuth.Get<AccountStatus>();
if (!accountStatus.IsActive)
{
throw new InvalidOperationException(string.Format("User {0} is not activated.", request.UserName));
}
if (!accountStatus.PasswordNeedsReset)
{
throw new InvalidOperationException("Your password needs to be reset before you can login.");
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When an authentication request comes into this method, fetch the UserAuth and the custom meta data. If the user is inactive, or their password needs to be reset throw an InvalidOperationException with an error message.
In my client application's Login controller I can check the error message coming back from the service and redirect the user to some page saying there account isn't active yet, or their password needs to be reset before they can be authenticated.

Resources