wcfazureadfs2.0azure-active-directory

Azure Active Directory and WCF authentication


I have WCF service and I need to secure it with Azure Active Directory. I've already read all related questions here and at social.msdn but still can't get my sample working. I want authentication to work in following way.

  1. No any popups displayed when user calls WCF service from client.
  2. User passes username/password and receives auth token from Azure ADFS
  3. Then in application (let it be console app for now) we open channel via Create CreateChannelWithIssuedToken method and all subsequent calls are made with the token we get from AAD.

What I'm doing and what my problems are. I'm for now using VS 2012 and Identity and Access tool to generate proper config file for the WCF service. So my service has following address. http://localhost:1785/Service1.svc

I go to azure portal and create new Web application with name http://localhost:1785/Service1.svc, sign on url http://localhost:1785/Service1.svc and app id url http://localhost:1785/Service1.svc

Then I check endpoints of the application and copy federation metadata which looks as https://login.windows.net/{some guid}/federationmetadata/2007-06/federationmetadata.xml

Using Identity and Access tools I add WAAD Identity provider to the Service project. My web.config file looks as below:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
  </configSections>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="ida:FederationMetadataLocation" value="https://login.windows.net/34bb8966-5537-4b1b-85ed-f501a06c1225/federationmetadata/2007-06/federationmetadata.xml" />
    <add key="ida:ProviderSelection" value="productionSTS" />
  </appSettings>
  <location path="FederationMetadata">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceCredentials useIdentityConfiguration="true">
            <!--Certificate added by Identity and Access Tool for Visual Studio.-->
            <serviceCertificate findValue="CN=localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
      <add scheme="http" binding="ws2007FederationHttpBinding" />
      <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <bindings>
      <ws2007FederationHttpBinding>
        <binding name="">
          <security mode="Message">
            <message>
              <issuerMetadata address="https://login.windows.net/adfs/services/trust/mex" />
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>
    </bindings>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true" />
  </system.webServer>
  <system.identityModel>
    <identityConfiguration>
      <audienceUris>
        <add value="http://localhost:1785/Service1.svc" />
      </audienceUris>
      <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
        <authority name="https://sts.windows.net/34bb8966-5537-4b1b-85ed-f501a06c1225/">
          <keys>
            <add thumbprint="92B88C3DD981BF1EBCB244FCFA63C007706C79E0" />
            <add thumbprint="3270BF5597004DF339A4E62224731B6BD82810A6" />
          </keys>
          <validIssuers>
            <add name="https://sts.windows.net/34bb8966-5537-4b1b-85ed-f501a06c1225/" />
          </validIssuers>
        </authority>
      </issuerNameRegistry>
      <!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.-->
      <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
  </system.identityModel>
</configuration>

Then I created console application and added service reference to the service project. After adding reference, config looks as

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <bindings>
            <ws2007FederationHttpBinding>
                <binding name="WS2007FederationHttpBinding_IService1">
                    <security>
                        <message>
                            <!--<issuer address="http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous" />-->
                          <issuer address="https://login.windows.net/34bb8966-5537-4b1b-85ed-f501a06c1225/wsfed" binding="ws2007HttpBinding"/>
                          <issuerMetadata address="https://login.windows.net/adfs/services/trust/mex" />
                            <tokenRequestParameters>
                                <trust:SecondaryParameters xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
                                    <trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
                                    <trust:KeySize xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">256</trust:KeySize>
                                    <trust:KeyWrapAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>
                                    <trust:EncryptWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>
                                    <trust:SignWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>
                                    <trust:CanonicalizationAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/10/xml-exc-c14n#</trust:CanonicalizationAlgorithm>
                                    <trust:EncryptionAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>
                                </trust:SecondaryParameters>
                            </tokenRequestParameters>
                        </message>
                    </security>
                </binding>
            </ws2007FederationHttpBinding>
            <ws2007HttpBinding>
                <binding name="">
                    <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="InheritedFromHost" />
                        <message establishSecurityContext="false" />
                    </security>
                </binding>
            </ws2007HttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:1785/Service1.svc" binding="ws2007FederationHttpBinding"
                bindingConfiguration="WS2007FederationHttpBinding_IService1"
                contract="SomeService.IService1" name="WS2007FederationHttpBinding_IService1">
                <identity>
                    <certificate encodedValue="AwAAAAEAAAAUAAAACLf4gntwdBYHCTmyInF5gU9oXNYgAAAAAQAAANUBAAAwggHRMIIBOqADAgECAhAT1EWwzeLBk0ez5Bg+JKyVMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMjExMTUxNTMyMDdaFw0xNzExMTUwMDAwMDBaMBQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtgkwd4GLTIwRtoHFjoSCBqrEcFgYMPh7f8aSWSYTrBGtaS9c2zOAuhxnaIAo1ELe3JLWUJmHq35IEu34gTwN9RfSna9Gis45TKrINY5nlAmKu0XpuI3ncf4WQRPbPx7hS6A0BHytXqQ3+FA1BSRr13iNUaaAkqwcCLWHXqno188CAwEAAaMkMCIwCwYDVR0PBAQDAgSwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4GBACKXEQpUUVm1g3BfpqRSn9hIYpuLojPX2wfySbIAan4jK5oo0dC5QUkbRjyEtqe6Io+POL3gbtoVzJOXnUDvYDUXRaR6dUmHJ5A4JLChUJZLHis0resLKO5yXrf4JqJSwEsL4Et5xiIRPoEvdPWWBhkDwuMvDGtQrzpp6ZYEt2sh" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

I tried to launch console application, but the error that valid issue should be specified has raised. So I thought, that valid issue can be found in endpoints at Azure AD configuration page, and copied WS-Federation Single sign-on endpoint as an issuer.

Console program code is below.

var client = new SomeService.Service1Client();
            client.ClientCredentials.UserName.UserName = "<valid user name>";
            client.ClientCredentials.UserName.Password = "<password>";
            client.GetData(10);

When I call service, I see in fiddler, that request is sent to WS-Fed endpoint but in response endpoint sends me html page with error

AADSTS20012: An error occurred when we tried to process a WS-Federation message. The message was invalid.

Could you please suggest me where I'm wrong and what to do to implement scenario I described in the beginning of the question? Thanks.


Solution

  • It is possible to obtain a token from Azure AD using username and password, see http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/. However note that, as Brent highlighted, this is done via OAuth2 and not WS-Trust. You cannot use WCF machinery like createchannelwithissuedtoken or similar. In general, Azure AD and all of our modern libraries do not integrate with WCF and focus on Web API instead. WCF integration is technically possible, but it requires VERY deep knowledge of WCF extensibility. If you have a chance of refactoring your services to use Web API, you'll find that everything will be much easier to do. HTH V.