8

I am in the situation that I need to access a ASP.NET Web Api that is using ADFS for authentication. I can hit it reliably through my browser by going through the ADFS login portal and getting the relevant FedAuth cookie. Unfortunately I need to access it from outside of a dedicated browser for use in a mobile app. The project is pretty much a slightly modified version of the standard visual studio web api template set up for Work and School Authentication (on-premises) and set up for cookie authentication.

bit of code from Startup.Auth.cs:

public void Configuration(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    app.UseWsFederationAuthentication(
        new WsFederationAuthenticationOptions
        {
            Wtrealm = realm,
            MetadataAddress = adfsMetadata
        });
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
    });
}

I can't seem to figure out where to start. I've tried requesting a access token from the ADFS and can get different versions of SAML assertions using relevant login info, but it gets rejected by the web API. Have I misunderstood how it's supposed to work?

From my understanding it's supposed to go like this: How I think it's supposed to work

  1. App requests a authentication token from the ADFS
  2. ADFS gives the requestee an auth token if the information provided was correct
  3. App makes request to the web API and sending the token along inside a cookie called FedAuth(by default anyway) as a base64 encoded string
  4. Web Api sends the token to the ADFS to find out if the token is correct.
  5. ADFS responds with some sort of success message
  6. Web Api responds to the app either with a rejection or a piece of data depending on how authentication went.

This is what I have right now while trying to figure out how to get a hold of the correct tokens.

using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.Net;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Security;
using Thinktecture.IdentityModel.Extensions;
using Thinktecture.IdentityModel.WSTrust;

namespace ConsoleApplication1
{    
    class Program
    {
        private const string UserName     = "USERNAME";
        private const string Password     = "PASSWORD";
        private const string Domain       = "DOMAIN";
        private const string ADFSEndpoint = "ADFS ENDPOINT";
        private const string ApiBaseUri   = "THE API";
        private const string ApiEndPoint  = "AN ENDPOINT";

        static void Main(string[] args)
        {
            SecurityToken token = RequestSecurityToken(); // Obtain security token from ADFS.
            CallApi(token);                               // Call api. 
            Console.ReadKey();                            // Stop console from closing
        }

        private static SecurityToken RequestSecurityToken()
        {
            var trustChannelFactory =
                new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
                    new EndpointAddress(new Uri(ADFSEndpoint)))
                {
                    TrustVersion = TrustVersion.WSTrust13,
                    Credentials = { UserName = { UserName = UserName + "@" + Domain, Password = Password } },
                };

            var requestSecurityToken = new RequestSecurityToken
            {
                RequestType = RequestTypes.Issue,
                KeyType = KeyTypes.Bearer,
                AppliesTo = new EndpointReference(ApiBaseUri)
            };

            RequestSecurityTokenResponse response;
            var securityToken = trustChannelFactory.CreateChannel().Issue(requestSecurityToken, out response);

            return securityToken;
        }

        private static async void CallApi(SecurityToken securityToken)
        {
            using (var handler = new HttpClientHandler { CookieContainer = new CookieContainer() })
            {
                using (var client = new HttpClient(handler))
                {
                    handler.CookieContainer.MaxCookieSize = 8000; // Trying to make sure I can fit it in the cookie

                    var cookie = new Cookie {
                        Name = "FedAuth",
                        Value = Base64Encode(securityToken.ToTokenXmlString()),
                        HttpOnly = true,
                        Secure = true
                    };
                    handler.CookieContainer.Add(new Uri(ApiBaseUri), cookie);
                    var response = client.GetAsync(new Uri(ApiBaseUri + ApiEndPoint)).Result;
                    string result = await response.Content.ReadAsStringAsync();
                    Console.WriteLine(result);
                }
            }
        }

        public static string Base64Encode(string plainText)
        {
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
            return System.Convert.ToBase64String(plainTextBytes);
        }
    }
}

I can't quite remember what code I based my example of, but if anyone can point me in the right direction or tell me where I fucked up I'd appreciate it.

Edit: Sorry, forgot to add what I am getting. The Web Api vomits out a bunch of debug information because an exception was thrown, telling me that a SecurityContextToken is expected instead of a saml:Assertion that I am apparently getting. Maybe my googlefoo is not powerful enough, but I can't seem to figure out where to start with this. Can I setup the api to accept SAML assertions or do I need to request the token in a different way?

Tennaheim
  • 141
  • 1
  • 1
  • 4

1 Answers1

7

You can't use WS-Fed to call a web API. You need OpenID Connect / OAuth as in Calling a web API in a web app using Azure AD and OpenID Connect.

It's for Azure AD but it does illustrate the flow.

What version of ADFS?

rbrayb
  • 46,440
  • 34
  • 114
  • 174
  • It looks like the ADFS server was 2.0 but will get upgraded soon. I've applied a bit of a patchwork solution so far (OAuth token generation and validation on the web api), but just for the sake of anyone else finding this post, what would you suggest as an acceptable solution to this problem? – Tennaheim Feb 16 '17 at 13:33
  • Use identityserver and federate with ADFS. – rbrayb Feb 16 '17 at 17:51
  • I am also trying to call the web api but it is not called using wpf application. Authorization failed is getting at the time of calling.If I removed authorize attribute from controller, it is getting called – Mohan Feb 15 '18 at 20:49