title: Authentication to ASP.NET Web Api using ADFS
body: <p>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.</p>
  
  <p>bit of code from Startup.Auth.cs:</p>
  
  <pre><code>public void Configuration(IAppBuilder app)
  {
      app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
      app.UseWsFederationAuthentication(
          new WsFederationAuthenticationOptions
          {
              Wtrealm = realm,
              MetadataAddress = adfsMetadata
          });
      app.UseCookieAuthentication(new CookieAuthenticationOptions
      {
          AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType
      });
  }
  </code></pre>
  
  <p>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?</p>
  
  <p>From my understanding it's supposed to go like this:
  <a href="https://i.stack.imgur.com/NjMey.png" rel="noreferrer">How I think it's supposed to work</a></p>
  
  <ol>
  <li>App requests a authentication token from the ADFS</li>
  <li>ADFS gives the requestee an auth token if the information provided was correct</li>
  <li>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</li>
  <li>Web Api sends the token to the ADFS to find out if the token is correct.</li>
  <li>ADFS responds with some sort of success message</li>
  <li>Web Api responds to the app either with a rejection or a piece of data depending on how authentication went.</li>
  </ol>
  
  <p>This is what I have right now while trying to figure out how to get a hold of the correct tokens.</p>
  
  <pre><code>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);
          }
      }
  }
  </code></pre>
  
  <p>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.</p>
  
  <p>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?</p>