locked
ADFS 3.0 + Cordova Mobile App +API RRS feed

  • Question

  • Our Cordova app needs to request security tokens from an on-premise ADFS (3.0). Then use the token to connect to web services. All the examples I found say this is possible but only demonstrate how to do so using Azure. 

    Where can I find detailed information for configuring ADFS 3.0?


    Cody Skidmore


    • Edited by Cody Skidmore1 Thursday, November 3, 2016 4:56 PM Clarifying what I need.
    Thursday, October 13, 2016 6:01 PM

All replies

  • The online documentation for ADFS is available online at this location:

    This is a good start... You install ADFS according to the doc, you create a relying party trust according to the doc. You will need information from the application (usually we refer to those info as federation metadata) which are the data you will need to enter in ADFS when you create the relying party trust.

    Give it a try and tell us where you are stuck. Good luck!


    Note: Posts are provided “AS IS” without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.

    Tuesday, October 18, 2016 11:25 PM
  • But, for Cordova Apps to be displayed in Mobile, What should we pass as the Relying Party Trust to ADFS. Kindly, help

    Thanks
    Sourav
    Wednesday, October 19, 2016 7:29 PM
  • Sourav,

    I'm still researching how everything is setup, but what I found so far is you register the mobile app /w ADFS using ADFS' Powershell commandlet Add_AdfsClient.

    https://technet.microsoft.com/en-us/library/dn479319%28v=wps.630%29.aspx?f=255&MSPPError=-2147217396

    Add-AdfsClient [-ClientId] <String> [-Name] <String> [[-RedirectUri] <Uri[]>

    ClientId can be anything but usually it's a generated GUID. Name will be the name of the new client relationship you're establishing. RedirectUri is largely redundant (as far as I can tell). Set it to a fake URI. I used "http://wpfclienturi/" in my test.

    I don't have a Cordova prototype yet but my WPF client creates an AuthenticationContext passing "https://<my adfs sever FQDN>/adfs" as the authority. I pass false for the validateauthority setting. The I call AquireToken.

    AuthenticationContext ac = new AuthenticationContext(authority, false);
    AuthenticationResult ar = ac.AcquireToken(resourceURI, clientID, new Uri(clientReturnURI));
    string authHeader = ar.CreateAuthorizationHeader();
    I'm using ADAL 3.13.5(https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/3.13.5).

    The most difficult part of this has been finding samples & documentation that match.

    On the API side I create a token validation handler (see http://sanderstechnology.com/2015/securing-a-web-api-with-adfs-3-0-and-jwt-tokens/14297/#.WApef-ArKUk), however I don't register the handler.

    Instead I modified the sample code so I could call it manually when the authentication request occurs. If the token is valid, I assign the ClaimsPrincipal. This allows me to have both protected & unprotected controllers & methods.

    My plan is to convert the WPF sample to Javascript using Cordova's ADAL plugin. It should work the same.
    • Edited by Cody Skidmore1 Tuesday, October 25, 2016 6:14 PM Can't get the paragraph formatting right.
    Tuesday, October 25, 2016 6:12 PM
  • What about doing it in Azure if it is documented and works? :)

    Note: Posts are provided “AS IS” without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.

    Thursday, November 3, 2016 3:35 PM
  • I work within the bounds of customer requirements. I don't peddle Azure for you. 

    Don't say things like that. It makes you sound naive. Customers are slow to adopt new technology. Just getting them moved up to Windows Server 2016 will take years. You should know that.



    Cody Skidmore

    Thursday, November 3, 2016 4:45 PM
  • Despite the emoticon, this was an actual question. Sometimes customers are not doing it the way it is documented because they ran through some issues and are trying to find a workaround rather than addressing the original issue. I am just making sure it wasn't the case. No need for the high level sarcasm :)

    Note: Posts are provided “AS IS” without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.

    Thursday, November 3, 2016 5:56 PM
  • Azure gets pushed in my face constantly, but it is simply out of the question. I'm dealing with state  & city municipal IT infrastructures that require on-premise single sign-on support.  

    In case others are reading, here's the current state.

    Our mobile app is Telerik AppBuilder/Cordova based. It needs to request JWT tokens from ADFS and send them to an API. 

    The main problem I face is version mismatches. I can't find examples that actually work. The security libraries like ADAL changed a lot just the the past year. AppBuilder is limited to Cordova 4.0 plugin support, so ADAL is out.

    The closest I've come is sending a SOAP request to ADFS's usernamemixed endpoint. If the prototype works, our API will authenticate users on behalf of the mobile app using the usernamemixed endpoint. It has to convert the received binary security token to a JWT token. Then send the JWT token to the mobile app. 


    Cody Skidmore


    Thursday, November 3, 2016 6:16 PM
  • There are a number of good ADFS examples here.

    Part of the problem is that ADFS 3.0 only supports a very small feature set viz. authoristation code grant for confidential clients.

    It has no OpenID Connect support and doesn't support the other three OAuth grant types or the public client for the flow above.

    Azure AD on the other hand has full support for this. And so does ADFS 4.0 (Server 2016).

    In terms of converting the token, ADFS can do this for you - refer JSON Web Token (JWT) support in ADFS.

    This may also help.

    Thursday, November 3, 2016 6:46 PM
  • That's basically what I'm doing at the moment. I request a JWT token from the usernamemixed endpoint. It sends the token back packed inside a 64 bit binary security token which needs to be converted.

    Once I figure out how to convert the binary token to a JWT token I should have a complete solution.


    Cody Skidmore

    Thursday, November 3, 2016 6:52 PM
  • I solved this problem today. Here's the working example. It should work for all mobile apps, not just Cordova.

                string adfsHost = "https://<Your ADFS FQDN>";
                string sendTo = $"{adfsHost}/adfs/services/trust/13/usernamemixed";
                string _username = "<Your Domain\\<Your username>"; 
                string _password = "<Your password>";
                string applyTo = "<Your Resource URI>";
                string tokenType = "urn:ietf:params:oauth:token-type:jwt";

                string soapMessage = $@"
                    <s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
                                xmlns:a=""http://www.w3.org/2005/08/addressing""
                                xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">
                      <s:Header>
                        <a:Action s:mustUnderstand=""1"">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
                        <a:To s:mustUnderstand=""1"">{sendTo}</a:To>
                        <o:Security s:mustUnderstand=""1"" xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
                          <o:UsernameToken u:Id="" uuid-00000000-0000-0000-0000-000000000000-0"">
                            <o:Username>{_username}</o:Username>
                            <o:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">{_password}</o:Password>
                          </o:UsernameToken>
                        </o:Security>
                      </s:Header>
                      <s:Body>
                        <trust:RequestSecurityToken xmlns:trust=""http://docs.oasis-open.org/ws-sx/ws-trust/200512"">
                          <wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy"">
                            <a:EndpointReference>
                              <a:Address>{applyTo}</a:Address>
                            </a:EndpointReference>
                          </wsp:AppliesTo>
                          <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType>
                          <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
                          <trust:TokenType>{tokenType}</trust:TokenType>
                        </trust:RequestSecurityToken>
                      </s:Body>
                    </s:Envelope>
                ";

                XmlDocument xml = new XmlDocument();
                xml.LoadXml(soapMessage);

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(sendTo);

                request.ContentType = "application/soap+xml; charset=utf-8";
                request.Method = "POST";
                request.Accept = "application/json";

                var stream = request.GetRequestStream();
                xml.Save(stream);

                WebResponse response = request.GetResponse();
                string strResponse;
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(responseStream, System.Text.Encoding.ASCII))
                    {
                        strResponse = sr.ReadToEnd();
                    }
                }

                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();

                XmlDocument xml2 = new XmlDocument();
                xml2.LoadXml(strResponse);

                XmlNode node = xml2.SelectSingleNode("//*[@ValueType='urn:ietf:params:oauth:token-type:jwt']");
                XmlReader reader = new XmlNodeReader(node);
                JwtSecurityToken token = (JwtSecurityToken)handler.ReadToken(reader);

                string encryptedToken = token.RawData;

    The code simulates receiving user credentials from a mobile app at the top. Then it setups up the rest of the values necessary to call ADFS 3.0. The values are inserted into the SOAP envelope using string interpolation. 

    Next, it creates a web request. Then it adds the SOAP envelope to the request and calls the ADFS endpoint. You should receive a SOAP response containing a BinarySecurityToken and a status code 200.

    The JWT token is wrapped inside the BinarySecurityToken. To get it out, you must select the wsse:BinarySecurityToken which contains it and use JwtSecurityTokenHandler.ReadToken() to pull it out. Then you can send the token to the mobile app where it can be used to complete API requests.

    You could use the same approach to call ADFS directly from the mobile phone also but I prefer doing it on the API side.

    Also, I strongly advise you NOT to use self-signed certificates. IMHO, many of the ADFS interactions simply won't work. Just save yourself the headache.

    Cody Skidmore

    Friday, November 4, 2016 1:28 PM