Has anyone used setting Thread.CurrentPrincipal
for authentication within F# asynchronous code?
Was there any pit falls to watch out for, or did it all just work as expected?
Has anyone used setting Thread.CurrentPrincipal
for authentication within F# asynchronous code?
Was there any pit falls to watch out for, or did it all just work as expected?
That doesn't sound like a good idea, because Thread.CurrentPrincipal
relies on the IPrincipal
associated with a given thread, but inside an async workflow, you may not be running on the same thread all the time.
Consider this example:
open System.Threading
let collectThreadInformation jobId =
async {
let tId1 = Thread.CurrentThread.ManagedThreadId
do! Async.Sleep 1000
let tId2 = Thread.CurrentThread.ManagedThreadId
return jobId, tId1, tId2 }
This is a simple async workflow that reports on the thread ID before and after sleeping for a second. Here's what it looks like running 10 of them in parallel:
> [1 .. 10]
|> List.map collectThreadInformation
|> Async.Parallel
|> Async.RunSynchronously;;
val it : (int * int * int) [] =
[|(1, 15, 15); (2, 14, 15); (3, 15, 15); (4, 15, 15); (5, 15, 15);
(6, 15, 15); (7, 15, 15); (8, 14, 15); (9, 15, 15); (10, 15, 15)|]
As you can see, some of the thread IDs have changed from before and after the Sleep
, e.g. the second tuple: (2, 14, 15)
. Before the Sleep
it ran on thread 14, but after the sleep, it was resurrected on thread 15.
I think it would be safer to pass in an IPrincipal
instance as a function argument.
I had various conflicting responses to this in different places, so I actually built a test program. Running the code below suggests that as async
workflows run on the ThreadPool, they do actually preserve the thread context.
module TestCurrentPrinciple
open System
open System.Security.Principal
open System.Threading
let rand = Random()
let spawnWorkflow userName =
async {
let identity = GenericIdentity(userName)
let principle = GenericPrincipal(identity, [||])
Thread.CurrentPrincipal <- principle
for i in 1..10000 do
let principleName = Thread.CurrentPrincipal.Identity.Name
if principleName <> userName then
failwithf "Mismatch! Principle name %s does not match username %s (Iteration %d)"
principleName userName i
do! Async.Sleep(rand.Next(10))
}
let names =
[ "mavnn"; "ploeh"; "dsyme"; "biboudis"; "MattDrivenDev"; "fssnip"; "marprz_93"; "skillsmatter"; "thinkb4coding" ]
printfn "Starting"
names
|> List.map spawnWorkflow
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
printfn "Done"
Console.ReadLine() |> ignore
Which might be interesting for my current project.