C
C#6d ago
webczat

Usage of AsyncLocal in singleton DI service to override authentication tokens

I am creating a discord client library, which is based around DI and is supposed to be registered into a DI container, like in service worker or aspnetcore apps. One of it's use cases is of course bots. It's DI services like IUsers etc use an ITokenProvider to retrieve a bot token. Bot is not the only mode of operation, but... The library will support discord login on the web. It will have an aspnetcore authentication handler that can store user's access and refresh tokens and so you can call discord api as the user. However, ITokenProvider is singleton and because of different contexts this library can be used, it can't be scoped. My idea was to use AsyncLocal<Token> for a concept of ambient token. When a discord user authenticated request cames in and request handler wants to execute api request on behalf of that user, they can just set the ambient token, call api like from IUsers and then reset the token. Normally a bot token would still be used. Does that idea sound good? othervise I could probably have some factory that creates things like IUsers/etc with a different token instead.
14 Replies
JakenVeina
JakenVeina5d ago
if you truly can't use DI scoping, then yeah, that sounds reasonable what I would recommend on top of that though is abstracting it like, your default implementation for token storage can be async local, but give consumers the ability to swap that out if it doesn't quite fit their use case you could also go a more direct DI route
viceroypenguin
yes, AsyncLocal<> is a common way to handle this. it's used by IHttpContextAccessor, and I use it in some of my projects to set default values in normal operation and testing values in test operation.
JakenVeina
JakenVeina5d ago
I.E. make an OperationContext object that has the token, and whatever other contextual information your library needs, in it and DI that through parameter injection, across your API
webczat
webczatOP5d ago
what I have is: singleton ITokenSource, depending on the authentication mode selected on app config, for example a bot or a client id/client secret authentication, or global user (relevant only for some gui apps or such like).
JakenVeina
JakenVeina5d ago
sure
webczat
webczatOP5d ago
There is also ITokenProvider that checks AsyncLocal, and if it's null, delegates to the source. so the other services don't know or care
viceroypenguin
yup. i support that idea
webczat
webczatOP5d ago
so the thing is: if you selected global user authentication, you inject an even higher level service like IAuthenticationContext and do like BeginAuthenticate/EndAuthenticate as in non web based oauth flow. begin session, run browser with uri, process redirect, token stoled globally. For web login flow, aspnetcore auth handler would do the same as other auth handlers do, and there would be some extension method on HttpContext to impresonate user that would set the token on token provider's async local. exception: if user auth is selected globally, then auth middleware would immediately set the token. as in user auth globally means either global user auth for single user or web login only/use library for discord as idp.
viceroypenguin
@lolcat https://github.com/viceroypenguin/VsaTemplate/blob/master/Api/Features/Users/Services/CurrentUserService.cs this is what i use. it could easily be updated to have an AsyncLocal<> with a method to set the current user, and that local's value would be checked first before httpcontextaccessor
webczat
webczatOP5d ago
unsure if that's in any way related. the only thing I want is managing token that is used when the request handler or a controller wants to call discord as user. authentication and authorization on aspnetcore side would be a different beast, in addition, probably in that case only a bot could look up user's roles in a guild, so any kind of authz policy would likely not need that.
viceroypenguin
sure, but the concepts around "who is the currnt user" is the same, whether you're talkign about the user accessing the website or which bot identity is accessing discord or whatever.
webczat
webczatOP5d ago
the thing is also you can use scoped services probably. I can't here because I want to have a facade for simple usages like I don't want to care, new up a discord client, call api and logout. This internally spins up di. Could create a scope, but I don't want to have to guarantee thread safety for scoped services, it's kinda something you wouldn't expect. so that's why my token provider is not scoped. othervise it could be
viceroypenguin
i know some middlewares do it via scoped services, but i trust the asynclocal<> method better than i trust the scopd services method. that said, they do have different purposes, because they will create different scopes.
webczat
webczatOP5d ago
if my lib was only for aspnetcore and workers, i could use scoped honestly. but because it's not... I can't unless a web only thing is being designed but okay I get it. you just have the user retrieval instead of asynclocal, which is kinda the same idea and this auth state whatever instead of ITokenSource. ITokenSource either uses IOptionsMonitor and allows bot token reloads, or in other auth modes, manages token refresh ofc this is 3 impls, not one

Did you find this page helpful?