We have implemented a rather complex mixed mode authentication solution more or less using the ForwardDefaultSelector scheme as mentioned in the docshttps://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/mixed-mode-authentication
One thing we are struggling a bit with is the Optimizely Quick Navigator. The ForwardDefaultSelector will only use the scheme for Optimizely edit/admin if url starts with /episerver and then we have some other logic to determine the Forward scheme based on settings on the current site and other stuff. That means we forward to the other schemes when not in editor or admin.
The quick navigator html extension will use the user from the viewcontext which will be set by the controller. If I put athorization attribute on the page controller with authenticationschemes set to all the schemes I have the quick navigator appears. But we want to avoid having to do this and I am also nervous if it affects Optimizely built-in authorization (restrict access to pages) if we do this.
I tried using AddAuthorization (mentioned below) and a default policy adding all our schemes to authenticationschemes ... but that didn't do anything.https://code-maze.com/dotnet-multiple-authentication-schemes/
Can the editors live without the Quick Navigator ... of course if they must ... but it is surely a nice tool I personally miss alot :)
I'm not sure I understand the issue. The quick nav is displayed if you have edit or admin acess. If you want to have more control over this, you can just wrap the html helper with an if-statement with your business logic.
Understandable if it was hard to understand :) ... it is also hard to explain ... but if you use the mixed-mode auth example from the docs directly the scheme used when you access the end user website is the "a-scheme". The user/identity will be extracted from this scheme and will therefore not have edit or admin access and therefore you will never be able to get the quick navigator to work.
So how do I authorize with both schemes (without messing around with the built in Optimizely authorization). There is a link on the mixed-mode auth doc page to https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0
Here is a section "Use multiple authentication schemes" which I guess move the the config from the controller attribute into being global (read default authorization policy). Trying AddAuthorization and DefaultPolicy didn't help.
It is a complex topic ...
EDIT: I just tried adding the athorize attribute to our default page controller and it is definitely messing with the built-in optimizely authorization (and naturally anonymous access is gone). But if logged in it will get the user but in reverse order of priority which is strange. So if no user in a-scheme it will give me the another-scheme user (optimizely user). Logged in with both it gives me the user from a-scheme.
[Authorize(AuthenticationSchemes = "another-scheme,a-scheme" )]
I think I understand your issue. Instead of forwarding the scheme based on the URL, you can do it based on other criterias, e.g. cookie name. That way you can make sure that, if the user is authenticated as an editor (has a cookie with a specific name), the frontend/templates will sign in you as well. That's the point of the forwarding -- be able to select an authentication scheme based your own business logic.
I totally see what you mean. But I have a feeling it is kind of contradicting the ForwardSelector idea. The problem being I cannot at this point determine if the user really is authenticated just by looking at the cookies.
Basicially we need this logic in the forwarder:
It would be nice to look at identifies instead of cookies. But the context in the ForwardDefaultSelector has no info of any identitfies cause they are determined (added to context) after the forwarder based on its results. Would be nice if I could check all identities from the context somehow during forward selection (all valid auth cookies) and base the logic from those.
If we look at cookies only we could run into this I think: The ticket for scheme/cookie BZ could expire but the cookie will remain and therefore we will not be able to get to 2.I hope I make sense.
Aha I could use AutheticateAsync on the context to check, something like this for point 2 (maybe it was something like this I was looking for):
var authenticatedWithBZResult = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!authenticatedWithAResult .Succeeded)
var authenticatedWithAResult = await context.AuthenticateAsync(IdentityAndSecurityConstants.OptimizelyCookieSchemeName);
forwardAuthenticationSchema = IdentityAndSecurityConstants.OptimizelyCookieSchemeName;
I just need to figure out what to do with the Sign-in and Sign out buttons on the website. Should user be able to sign in with BZ scheme if signed in as optimizely editor/admin.
"I cannot at this point determine if the user really is authenticated just by looking at the cookies." that's not what I meant either. But you can see if a specific cookie exist and then select appropriate authentication and sign-in scheme. Then it's up to that scheme to do its job.
Why do you need the identities? If the token in the cookie has expired, the user will be challanged for a new one.
You can of course keep the URL check as well, to always enforce scheme 'A' in edit and admin mode, then check the cookies and prefer 'A' in the frontend too.
You can also replace the default implementation of Microsoft's AuthenticationSchemeProvider, but forwarding should be enough here.
Nah you are right ... I do not need the identifies. But I believe I need to make sure I can authenticate against the cookies and not only check if they exist (as the ticket in the cookie can expire and the cookie will not go away) so just looking at the cookie existance is not enough. It will introduce weird behaviours (for users that login with multiple schemes and cookie ticket expiry set).
I am still not convinced that Forwarding is the way to go here.
My example above (edited) did work ... but with the caveat that ForwardDefaultSelector is not async (why?!?!). Enough though that this is not the road I want to take.
As this was actually only to show the Quick Navigator I could experiment creating a custom QuickNavigatorHtmlHelperExtensions, maybe even turn it into a proper view component instead of the shitty static html extensions (static with dependencies are still evil ...). I could Authenticate (get user) directly with the optimizely cookie scheme for the IsPermitted method.
I still don't understand your concerns about what the cookie contains. Usually the cookie has the same expire date as the token. Also don't understand why you need to authenticate and sign in the user before you select "correct" scheme. The whole point of selecting a scheme is so it can authenticate and sign in the user. If you're already authenticating and signing in the user, then there's no need to select a scheme.
If the token has expired in the cookie, the scheme will challenge for a new token, and depending on the implementation a new token is acquired, either silently in a back channel by using a refresh token, or the user is redirected to the provider to log in again.
If you replace the quick nav with your own implementation, editors will only get the navigation, they will not get authenticated and signed-in in the frontend. I.e. they cannot access protected content in the frontend, unless they're previewing it inside the editing UI.
"Usually the cookie has the same expire date as the token."Nope by default the cookie is a session cookie and the ticket can expire. And from the class summary on options.Cookie is seems that you can not change that.
"authenticate and sign in the user before you select "correct" scheme."Only wanted to AuthenticateAsync which is not actually signing anyone in but it is authenticating the user on the specific scheme. (it will not create a challenge). I could use it to verify if the current request is authenticated with the specific scheme (instead of looking for specific cookie existance which potentially can result in yanky behavior which you do not want in critical parts of your system ... cookie is there but is not authenticated, ticket expired, and therefore choosing wrong forward scheme).
"If you replace the quick nav with your own implementation, editors will only get the navigation, they will not get authenticated and signed-in in the frontend. I.e. they cannot access protected content in the frontend, unless they're previewing it inside the editing UI."Nope and they shouldn't .... perfectly fine ... not for now at least. Just wanted to show the Quick Navigator
Before I went to bed I did manage to implement a view component with reflected code from the QuickNavigatorHtmlHelperExtensions. It works perfectly fine. Need to run a few more tests, implement unit tests and see how it goes. Now you can be signed in with a user front end with different scheme (different idp) but at the same time be signed in as editor and you will be able to see the Quick Navigator (also as anonymous on frontend ofc).
And btw Johan, thank you so much for the inputs, I appreciate it :)