I have an ASP.NET MVC 5 application, and need to support SAML 2.0 authentication. I am evaluating Sustainsys.Saml.Mvc. The User.Identity.Name property in my controller is an empty string, while the User.Identity.IsAuthenticated property is true, and can't for the life of me figure it out.
While installing the Sustainsys.Saml2.Mvc NuGet package, I had to do the following things:
- Upgrade the .NET framework from 4.5.1 to 4.6.1
- Install Sustainsys.Saml2.Mvc v2.2.0
- Upgrade the Microsoft.AspNet.Mvc package from 5.2.3 to 5.2.7
- Tweaked Web.config settings according to the Sustainsys.Saml2 documentation
- Downloaded the .cert and .pfx files from their demo MVC application
Contents of Web.config:
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=301880
-->
<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
</sectionGroup>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="sustainsys.saml2" type="Sustainsys.Saml2.Configuration.SustainsysSaml2Section, Sustainsys.Saml2" />
</configSections>
<connectionStrings>
...
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.
The following attributes can be set on the <httpRuntime> tag.
<system.Web>
<httpRuntime targetFramework="4.6.1" />
</system.Web>
-->
<system.web>
<customErrors mode="Off" />
<compilation debug="true" targetFramework="4.6.1" />
<httpRuntime targetFramework="4.6.1" requestValidationMode="2.0" />
<authentication mode="Forms">
<forms loginUrl="~/Saml2/SignIn" />
</authentication>
<membership defaultProvider="ABC">
<providers>
<clear />
<add name="ABC" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName" />
</providers>
</membership>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</httpModules>
</system.web>
<system.webServer>
<handlers>
<add name="UrlRoutingHandler" type="System.Web.Routing.UrlRoutingHandler, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" path="Authorization/Permissions/*" verb="GET,POST" />
</handlers>
<staticContent>
<remove fileExtension="eot" />
<remove fileExtension="otf" />
<remove fileExtension="woff" />
<remove fileExtension="woff2" />
<remove fileExtension="ttf" />
<remove fileExtension="json" />
<mimeMap fileExtension="eot" mimeType="application/vnd.ms-fontobject" />
<mimeMap fileExtension="otf" mimeType="application/x-font-opentype" />
<mimeMap fileExtension="woff" mimeType="application/x-font-woff" />
<mimeMap fileExtension="woff2" mimeType="application/font-woff2" />
<mimeMap fileExtension="ttf" mimeType="application/x-font-ttf" />
<mimeMap fileExtension="json" mimeType="application/json" />
</staticContent>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</modules>
<httpErrors errorMode="Detailed" />
</system.webServer>
<sustainsys.saml2 entityId="http://localhost/MyMvcApp/Saml2" returnUrl="http://localhost/MyMvcApp/">
<identityProviders>
<add entityId="https://stubidp.sustainsys.com/Metadata" signOnUrl="https://stubidp.sustainsys.com/" allowUnsolicitedAuthnResponse="true" binding="HttpRedirect">
<signingCertificate fileName="~/App_Data/stubidp.sustainsys.com.cer" />
</add>
</identityProviders>
<!--<federations>
<add metadataLocation="http://localhost:52071/Federation" allowUnsolicitedAuthnResponse="true"/>
</federations>-->
<serviceCertificates>
<add fileName="~/App_Data/Sustainsys.Saml2.Tests.pfx" />
</serviceCertificates>
</sustainsys.saml2>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
</federationConfiguration>
</system.identityModel.services>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" culture="neutral" publicKeyToken="30ad4fe6b2a6aeed" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.5.0.0" newVersion="3.5.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89b483f429c47342" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.121.2.0" newVersion="4.121.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-5.2.7.0" newVersion="5.2.7.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory" from="foo@mycompany.com">
<network host="smtp.mycompany.com" port="25" defaultCredentials="true" />
<specifiedPickupDirectory pickupDirectoryLocation="..." />
</smtp>
</mailSettings>
</system.net>
<elmah>
<!--
See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for
more information on remote access and securing ELMAH.
-->
<security allowRemoteAccess="true" />
<errorLog type="Elmah.MemoryErrorLog, Elmah" size="50" />
</elmah>
<location path="elmah.axd" inheritInChildApplications="false">
<system.web>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<!--
See http://code.google.com/p/elmah/wiki/SecuringErrorLogPages for
more information on using ASP.NET authorization securing ELMAH.-->
<authorization>
<allow users="..." />
<deny users="*" />
</authorization>
</system.web>
<system.webServer>
<handlers>
<add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
</handlers>
</system.webServer>
</location>
</configuration>
I'm currently hosting my application from my laptop:
- Windows 10 Enterprise
- .NET framework up to 4.6.1 is installed
- IIS 10.0.15063.0
- Application pool settings in IIS:
- .NET 4.0
- Allow 32-bit applications
It is using the mock Identity Provider at: https://stubidp.sustainsys.com/
The XML posted to /MyApp/Saml2/Acs:
<saml2p:Response Destination="http://localhost/MyApp/Saml2/Acs" ID="idb4440bf88fba449f8526760d4330dd53" Version="2.0" IssueInstant="2018-12-20T18:21:22Z" InResponseTo="idd9b8948ac5ac4c389bf65072169464ac"
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://stubidp.sustainsys.com/Metadata
</saml2:Issuer>
<Signature
xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="#idb4440bf88fba449f8526760d4330dd53">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>...</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</saml2p:Status>
<saml2:Assertion
xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Version="2.0" ID="_d6ffcd18-44ec-45db-bd74-9cf48ea1cfa2" IssueInstant="2018-12-20T18:21:22Z">
<saml2:Issuer>https://stubidp.sustainsys.com/Metadata</saml2:Issuer>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">JohnDoe</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData NotOnOrAfter="2018-12-20T18:23:22Z" InResponseTo="..." Recipient="http://localhost/MyApp/Saml2/Acs" />
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotOnOrAfter="2018-12-20T18:23:22Z">
<saml2:AudienceRestriction>
<saml2:Audience>http://localhost/MyApp/Saml2</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AuthnStatement AuthnInstant="2018-12-20T18:21:22Z" SessionIndex="42">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
</saml2:Assertion>
</saml2p:Response>
I also set the Options.Logger property on the controller, and this is what I'm getting in the debug output:
[Saml2, DEBUG]: Http POST binding extracted message
<saml2p:Response Destination="http://localhost/MyApp/Saml2/Acs" ...>...</saml2p:Response>
[Saml2, DEBUG]: Signature validation passed for Saml Response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id
[Saml2, DEBUG]: Extracted SAML assertion _a5ee9c7f-4ca5-4693-a7f7-301ae5e6d4a6
[Saml2, INFO]: Successfully processed SAML response Microsoft.IdentityModel.Tokens.Saml2.Saml2Id and authenticated JohnDoe
While debugging the application, the User.Identity.Name property in my MVC controller is null. Further inspection of that object:
Why is System.Web.Mvc.Controller.User.Identity.Name null after successfully asserting the SAML response?

