0

To enforce authorization in my applications, I usually decorate every domain service with an authorization decorator, that takes some service authorizer to perform authorization.

For instance: CarService : ICarService is decorated with CarServiceAuthorizationDecorator : ICarService that gets an ICarServiceAuthorizer injected to perform authorization and when successfull, calls the actual CarService.

This setup has served me well, except one little detail that has always bugged me: I have to pass some Token with every single call to ICarService, because the authorizer needs it. Even if it doesn't, then the CarService may call a CarOwnerShipService that is again decorated with an authorizer, that may need it as well. A lot of Tokens everywhere.

The only way that I can come up with, to fix this, is to inject some ITokenProvider into the CarServiceAuthorizer that acts like a storage device: You put in the token at the start, and it keeps it along the way for any service that gets it out, so it doesn't need to be passed with every method call.

For this to work, the lifetime of that ITokenProvider must be the same as the lifetime of the thread. And not only that: newly spawned threads will need the token too.

So my question is: Is there a way to scope the lifetime of my ITokenProvider to a thread, including child threads, in .NET DI?

(AddScoped doesn't seem to help me, I think, because not all threads in my application start with a web request. Also, I have had major issues with newly spawned threads with AddScoped, that would lose their state.)

Joep Geevers
  • 567
  • 4
  • 18
  • _"For this to work, the lifetime of that ITokenProvider must be the same as the lifetime of the thread."_ - why? Can you please share some meaningful [mre]? – Guru Stron Nov 07 '22 at 22:40
  • You made me realize I left out an important piece of information: the ITokenProvider would act like a storage device: You put in the token at the start, and it keeps it along the way for any service that gets it out. So it should remember it during the lifetime of the thread and it's children. (I can't create an example today, but will do so tomorrow. Will also update my question a bit) – Joep Geevers Nov 07 '22 at 22:50
  • Also I would argue that writing code relaying on thread storage is usually an unnecessary complication and should be avoided. But if you really want to you can look into something like `ThreadStatic` and `ThreadLocal` or even `AsyncLocal` (maybe you will need even something like [this](https://github.com/ptupitsyn/UnmanagedThreadUtils)). But again - I would suggest to to rethink your app architecture in this part. – Guru Stron Nov 07 '22 at 22:54
  • 1
    _`AddScoped` doesn't seem to help me, I think, because not all threads in my application start with a web request_ - while web requests spawn new scopes automatically nothing prevents you from creating scopes outside web requests (for example like [here](https://stackoverflow.com/questions/70386368/how-to-access-dbcontext-in-net-6-minimal-api-program-cs/70386597#70386597)) when needed. Actually it is quite often done in background services in ASP.NET Core specifically to consume scoped services. – Guru Stron Nov 07 '22 at 22:58
  • Thanks for pointing out these attributes, I'm going to look into those, although I do agree it probably gets very tricky quite easily, and then just passing the token along is way more safe. – Joep Geevers Nov 07 '22 at 23:10
  • "For instance: CarService : ICarService is decorated with CarServiceAuthorizationDecorator". Unrelated to your question, but your application might really benefit from using a more message-oriented architecture, such as described [here](https://blogs.cuttingedge.it/steven/p/commands/). – Steven Nov 08 '22 at 14:00

1 Answers1

2

that acts like a storage device: You put in the token at the start, and it keeps it along the way for any service that gets it out, so it doesn't need to be passed with every method call.

AddScoped doesn't seem to help me, I think, because not all threads in my application start with a web request.

Based on the first quote I would argue that scoped ITokenProvider is exactly what you need (though without seeing the actual code it is more of a guess). Just create a scope when needed and use it, and do not rely on threading (i.e. you can spawn several threads using the same scope). Something along this lines:

IServiceProvider serviceProvider = ...;
using (var serviceScope = serviceProvider.CreateScope())
{
    var tokenService = serviceProvider.GetRequiredService<ITokenProvider>(); // or even split `ITokenProvider` into two interfaces
    tokenService.SetToken("");
    // resolve and call something using the token somewhere down the pipeline ...
    // Maybe even with Task.Run(...)
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • This looks like a very promising approach! I'm going to play around with it and see what I can come up with, and come back to you very soon. Thanks! – Joep Geevers Nov 07 '22 at 23:09
  • 1
    I agree with Guru. This method is the way to go. – Steven Nov 08 '22 at 13:59
  • And it works. Perfectly! I learned that I never really knew exactly how AddScoped works, and that I made some wrong assumptions. Also, I had not expected it to work so well :) Fire of a new thread? The ITokenProvider is still there. Fire and forget? Works. I'm still curious how it will work in big, older systems, because there's been a lot of ways to fire (and forget) new threads, some of which had undesired effects. Then still, I can implement this method, keep passing the token, and compare both results inside my authorizers, logging when they are different, and fix them. – Joep Geevers Nov 08 '22 at 20:39