We are experiencing a regression after updating our application from 5.12 to 6.1 on .NET Framework 4.8:
We are using AuthFeature with two auth providers and the HtmlRedirect property set to “/login”.
If a non-authenticated user accesses the app per browser, he will be redirected to /login when running with 5.12. With 6.1 he gets the browsers Windows auth frontend. IIS is configured to use anonymous.
I’ve switched the two modules, now I still get a 401 with this header:
WWW-Authenticate: apphost realm="/auth/apphost"
but without the Negotiate and NTLM headers. Very strange.
The auth modules are our own, as we have an own auth handling in the underlying business logic layer.
If this is no longer the first AuthProvider registered than I’m not clear why it’s returning its AuthProvider headers if it’s enforced by the built-in Auth attributes.
Can you override your custom Auth Providers OnFailedAuthentication() and log Environment.StackTrace so it shows where it’s being called from.
var auth = new AuthFeature(
() => new AuthUserSession(),
new IAuthProvider[]
{
new SessionAuthProvider(/* ... */),
new OAuth2AuthProvider(/* ... */)
},
_GetVirtualPath("login"));
The SessionAuthProvider without our custom code is in post 4.
What WWW-Authenticate: apphost realm="/auth/apphost" does it return in that case?
Do they both use the same apphostProvider name, because they should be unique.
Failed HTML Requests should redirect to the configured AuthFeature.HtmlRedirect which defaults to /login. Can you try overriding your AuthProvider’s OnFailedAuthentication so that it runs the error handling explicitly, e.g:
public override Task OnFailedAuthentication(IAuthSession session, IRequest httpReq, IResponse httpRes)
{
var feature = HostContext.AssertPlugin<AuthFeature>();
if (feature.HtmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
{
var url = feature.GetHtmlRedirectUrl(req, feature.HtmlRedirect, includeRedirectParam:true);
res.RedirectToUrl(url);
return TypeConstants.EmptyTask;
}
}
They have different provider names: apphost vs. oauth2.
In the header there was always the /auth/apphost realm.
And as I said: it failes also if I remove the OAuth2 provider completely.
Overriding OnFailedAuthentication works for us, now the redirect to the /login page is done like before.
I could live with this workaround.
This is the code that should be getting run, more specifically the AuthFeature registers custom Error HttpHandlers for different Auth Failure response codes, e.g:
appHost.CustomErrorHttpHandlers[HttpStatusCode.Unauthorized] =
new AuthFeatureUnauthorizedHttpHandler(feature);
public class AuthFeatureUnauthorizedHttpHandler : HttpAsyncTaskHandler
{
private readonly AuthFeature feature;
public AuthFeatureUnauthorizedHttpHandler(AuthFeature feature) => this.feature = feature;
public override Task ProcessRequestAsync(IRequest req, IResponse res, string operationName)
{
if (feature.HtmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
{
var url = feature.GetHtmlRedirectUrl(req, feature.HtmlRedirect, includeRedirectParam:true);
res.RedirectToUrl(url);
return TypeConstants.EmptyTask;
}
//...
}
}
Somewhere along the failure path that’s not happening, it’s also unclear why apphost is being returned when it’s not the first AuthProvider registered, since that’s what it uses.
Anyway OnFailedAuthentication is how you can override the default behavior, I would also return the base method for non HTML Requests, e.g:
public override Task OnFailedAuthentication(IAuthSession session, IRequest httpReq, IResponse httpRes)
{
var feature = HostContext.AssertPlugin<AuthFeature>();
if (feature.HtmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
{
var url = feature.GetHtmlRedirectUrl(req, feature.HtmlRedirect, includeRedirectParam:true);
res.RedirectToUrl(url);
return TypeConstants.EmptyTask;
}
return base.OnFailedAuthentication(session, httpReq, httpRes);
}