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:
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="[email protected]">
<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:
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?
You have properly completed the authentication. And you have a working session authentication cookie.
What you don't have however is a claim that matches the default name claim type. You have the NameIdentifier claim (which is what the stub idp supplies by default). But you don't have a claim with the default NameClaimType
The Name
property of a ClaimsIdentity
is implemented as
return Claims.FirstOrDefault(c => c.Type == NameClaimType)?.Value;
So to get a value on the Name
property you either need to change the NameClaimType
(can be done by modifying the created identity in the AcsCommandResultCreated
notification) or get your Idp to provide an attribute of type http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
. You can do that by adding an attribute at the bottom of the stubidp form.
I'm not using Identity server, but same basic issue:
Look at your Claims under User.Identity.Claims
and find one that does have your username.
Then wherever you configure the tokenvalidation you have to put the NameClaimType
to be the one you saw above:
Then when the middleware validates it it will copy that claim into UserName
. I'm assuming this is some default for Windows authentication [sic].
var tokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With