First of all, great effort! Works like a charm! I had a few questions about the usage though:
account/login instead of authservices/signin)?authservices/signin?How and where do you read requested attributes after successful auth?
AttributeStatements are exposed as claims on ClaimsPrincipal.Current.Identities.First().Claims
Is there a post auth hook that can be configured to do custom logic after auth?
Override ClaimsAuthenticationManager, see https://msdn.microsoft.com/en-us/library/hh987036(v=vs.110).aspx
Can the login URL be customized (like account/login instead of authservices/signin)?
The first half can be configured in web.config with modulePath
https://github.com/KentorIT/authservices/blob/master/doc/Configuration.md#modulepath-attribute
"SignIn" in unfortunately hard coded: https://github.com/KentorIT/authservices/blob/master/Kentor.AuthServices/WebSSO/AuthServicesUrls.cs#L86
In the sample Owin application code, shouldn't the URL be authservices/signin?
Do you mean LoginPath = new PathString("/Account/Login"), in Startup.Auth.cs?
That url points to the MVC login page, and on that page the partial https://github.com/KentorIT/authservices/blob/master/SampleOwinApplication/Views/Account/_ExternalLoginsListPartial.cshtml lists login buttons to all external login providers including AuthServices.
If you want to sign in directly with AuthServices you can change LoginPath to Authservices/SignIn, but if you want to choose login method you can leave it as is.
Thanks! I almost forgot about the login options on the standard MVC login page.
Is it possible to make the return URL point to the original page (which requested auth)? In the sample, I see that ExtrenalLoginCallback has a returnUrl param but that always comes as null.
The missing returnUrl in the sample application is because of https://github.com/KentorIT/authservices/issues/182. If no discovery service is used, the returnUrl should work.
I don't have any discoveryService configured. This is what I'm using:
<add entityId="..."
metadataUrl="..."
destinationUrl="..."
allowUnsolicitedAuthnResponse="true" binding="HttpRedirect">
<signingCertificate fileName="~/App_Data/idp.pem" />
</add>
I think all questions are answered, now except the bug report on ReturnUrl being lost, so I'm changing the title of the issue and tagging this as a bug.
Any progress on this?
Looked at this and can't reproduce it. In the Sample owin application it works. Can you provide more details on how to reproduce it?
I have this in my web.config:
<kentor.authServices entityId="http://localhost:54695" returnUrl="http://localhost:54695/Account/ExternalLoginCallback">
<metadata cacheDuration="0:30:00">
<organization name="MyApp" displayName="MyApp" url="http://myCompany.com" language="en" />
<requestedAttributes>
<add friendlyName="Remote User" name="CONF_REMOTE_USER" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" />
<add friendlyName="Email" name="CONF_EMAIL" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" />
<add friendlyName="First Name" name="CONF_FIRST_NAME" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" />
<add friendlyName="Last Name" name="CONF_LAST_NAME" nameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true" />
</requestedAttributes>
</metadata>
<identityProviders>
<!--Use https://identity.stg.company.net/idp/profile/SAML2/POST/SSO for HttpPost binding-->
<add entityId="https://identity.stg.company.net/idp/shibboleth"
metadataUrl="https://identity.stg.company.net/idp/profile/Metadata/SAML"
destinationUrl="https://identity.stg.company.net/idp/profile/SAML2/Redirect/SSO"
allowUnsolicitedAuthnResponse="true" binding="HttpRedirect">
<signingCertificate fileName="~/App_Data/idp-staging.pem" />
</add>
</identityProviders>
<federations>
<add metadataUrl="https://federation.example.com/metadata.xml" allowUnsolicitedAuthnResponse="false" />
</federations>
</kentor.authServices>
And my startup.auth.cs:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
},
},
CookieSecure = CookieSecureOption.SameAsRequest,
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseKentorAuthServicesAuthentication(new KentorAuthServicesAuthenticationOptions(true));
Here's the screenshot

Was there any update on this? I have the exact same issue signing in via Azure AD and SAML and returnURL is always null.
@GraemeSMiller I haven't worked anything more on this. To proceed I'd like to see a reproduction. Would you be able to show me in a Skype call with shared screens?
After hours of stepping through the source code (OWIN, SAML Auth Flow is really complex! So glad you have built Kentor) I managed to change the code to basically preserve the URL but it was horrible and not good as I basically had to copy huge chunks of your code into my app. So that clearly was wrong!
So after further changes I discovered the issue solves itself if I simply set the KentorAuthServicesAuthenticationOptions AuthenticationMode = AuthenticationMode.Passive. It then hits the Account/Login controller and I can issue the ExternalLoginCallBack with reutnrUrl as part of the ChallengeRequest.
Does this seem right - as far as I can see you don't seem to set Passive in your sample? Without passive being set it never hits Account/Login it just does everything itself apart from preserving the ReturnUrl in query string.
From the article from Brock https://brockallen.com/2013/10/27/host-authentication-and-web-api-with-owin-and-active-vs-passive-authentication-middleware/ it would appear that I want Kentor to only get called if there is a named challenge request for it. Otherwise it takes control of the Auth flow and Account/Login is never called before going into Kentor Auth Flow and therefore nothing is setting redirectUri.
Does this seem right?
@GraemeSMiller Thanks a lot for digging through this and finding out that the middleware being active is the problem.
Originally Authservices was passive by default, but I changed that when adding logout. The reason was to make it easier to trigger a logout without having to explicitly name the provider. But that creates other issues.
I've added an issue #548 to change the middleware to passive again. I've also added an issue #549 to add functionality to preserve the returnurl when the MW is active.
No problem. Really appreciate the product it made it so much easier to get SAML2 working for us.
Great - both issues they sound good. Maybe just include somewhere a bit of explanation of passive/active on docs. I hadn't dealt with it with for Auth middleware but maybe that is just me.
Also I know this ticket is unrelated - but I wonder if some of your code could make less use of private. I looked at providing other Command SingIn implementations in CommandFactory and couldn't - due to private. Also couldn't inherit from KentorAuthMiddleware as private. In the end that was the wrong approach to solving my issue but I do wonder if it maybe useful for others to be able to have more access to parts of your code without forking it. Just my two cents - maybe reasons for them to be private.
@GraemeSMiller The reason that most things are private is to minimize the API surface that cannot easily be changed. If there is anything specific you need, feel free to open an issue to discuss if it can be made public.
Most helpful comment
@GraemeSMiller Thanks a lot for digging through this and finding out that the middleware being active is the problem.
Originally Authservices was passive by default, but I changed that when adding logout. The reason was to make it easier to trigger a logout without having to explicitly name the provider. But that creates other issues.
I've added an issue #548 to change the middleware to passive again. I've also added an issue #549 to add functionality to preserve the returnurl when the MW is active.