Comparing to the Client Credentials Flow which I described in my previous post - the Authorization Code Flow involves one more entity - the End-User (aka Resource Owner). That makes the whole process “interactive”, since the End-User needs to take an action - log in and allow our application (the Client) to have access to a Protected Resource (for instance - retrieving user’s email, setting, or some user-specific value on behalf of the user, stored behind the API that is protected by the Identity Provider). David Neal explained this flow realy well in his post. I’m assuming you’re familiar with the C# equivalent setup of the IdentityServer4 for Interactive Applications with ASP.NET Core - if not, give it a go, it’s explained really well and should give you a good understanding of how things work.
Just to refresh - here’s the Authorization Code Flow sequence diagram:
Krzysztof Cieślak has explained how to use the GitHub provider in Saturn, but even though the setup is really similar, it still took me a while to make it work with IdentityServer4 properly, I’ve also struggled with logging out which I’ll elaborate on below.
You can follow along by cloning the source code from this repository.
The solution involves two services: the web application itself, and the Identity Provider (IdentityServer4 in this specific case).
For the former - you can either use the template (simpler option):
dotnet new -i Saturn.Template # Run it only once - when you've never used the Saturn template before. mkdir SaturnWithIdentityServerInteractive && cd SaturnWithIdentityServerInteractive dotnet new saturn -lang F# dotnet tool restore
… or set it up manually (if you know what you’re doing). I’ve used the template, but removed all the irrelevant code which would obscure the main topic of this post (error handling, database migration etc).
Setting up IdentityServer4 is really simple, you just need to create the project out of the
is4inmem template (run these commands from the root directory of the project,
SaturnWithIdentityServerInteractive in my case):
cd src dotnet new -i IdentityServer4.Templates # Run it only once - when you've never used IdentityServer4 templates before. dotnet new is4inmem -n IdentityServer cd .. dotnet sln add .\src\IdentityServer\IdentityServer.csproj
Config.cs set up the
Clients variable to:
This should give you a ready-to-run instance with two logins:
bob) and a set of client credentials (client_id:
Saturn application settings
To use the OpenID Authentication we need to set up the services to use Authentication, Cookie and OpenIdConnect handlers. In C# we’d do it like this:
Luckily - Saturn.Extensions.Authorization package contains an Application Computation Expression Custom Operation:
use_open_id_auth_with_config which helps us to achieve exactly this. In order to be able to use it - you need to add
nuget Saturn.Extensions.Authorization to
src\SaturnWithIdentityServerInteractive\paket.references files. Then run
dotnet paket install and
Looking at the implementation (as of 13.10.2020) we can see that the code is semantically similar to the C# equivalent mentioned above:
We should use the
use_open_id_auth_with_config operation like so:
How can we leverage this protection now? Let’s say our application wants to access a protected secret of the user - their favourite colour. Our
Router.fs would look like this:
We’ve got the
defaultView - the home page which can be accessed by anyone, and the
/protected). It can be accessed only by authenticated users, and is set up like this (in
protectedViewPipeline uses the Giraffe’s
requiresAuthentication together with
challenge - which uses the
OpenIdConnectDefaults.AuthenticationScheme scheme (being equal to
"OpenIdConnect" as mentioned above). Pay special attention to this value - we don’t want to be using
CookieAuthenticationDefaults.AuthenticationScheme as this is a different scheme which doesn’t handle the OpenIdConnect protocol. It will not redirect the user to the Identity Provider (
https://localhost:5001/account/login), but to the application itself (
https://localhost:8085/account/login) which will result in
404 error if you don’t have the
account/login endpoint. That’s a no-no!
protectedHandler retrieves user’s details (
given_name) from the token claims (remember to add the
profile scope to
OpenIdConnectOptions in the application setup!), retrieves user’s secret colour by calling the API with the Client Credentials Flow and provides these values to the view which returns a personalised “secret” webpage. If the user doesn’t have the secret colour yet, one is informed about it as well.
Logging the user out
There’s one more bit which took me a while to figure out. Giraffe provides a
signOut handler which signs the user out from a given authentication scheme. The documentation gives an example of how to use it:
Since we want to sign the user out of both
"OpenIdConnect" schemes - you’d probably try doing something similar in our Saturn router:
If you log in as
bob, go to
/logout page - you’ll be logged out indeed - but the next time you’d like to log in - the IdentityServer session will still be active - so instead of being taken to the login screen (where now you’d like to log in as
alice) - you’ll be automatically redirected back to your app logged in as
bob. This StackOverflow question describes exactly this scenario. It turns out - we need to use overloaded
SignOutAsync function which accepts
AuthenticationParameters but Giraffe doesn’t offer it out of the box. Hence, we need to write our own wrapper for calling this function (the implementation is based on the
Let’s try it again and it… works! Logging it as
bob, then logging out and logging back in again - we’ll be taken to the login page where we can log in as
That should be it! If you’ve got any questions, feel free to post one below or reach out through Twitter.