Web Service Call for ADFS 2.0 Authentication

2.7k views Asked by At

I have an ASP.NET MVC web app that uses ADFS 2.0 for authentication. Some of the MVC controller actions function as generic web service endpoints, receiving and serving JSON. I want to build a client application that automates some of the app's functionality. For that purpose, I am building an API access library that will make HTTP requests to the web service in order to do its job.

I am stuck at trying to authenticate. I am using forms authentication for ADFS 2.0, so shouldn't I be able to simply simulate the form post with a valid username and password in order to generate a token? Instead of receiving a token back, I just get the login page. I am not sure what else I need to do in order to authenticate my request. My code is pasted below... but maybe I'm doing it totally wrong and there's something I don't know about?

string postData = string.Empty;
postData += "ctl00$ContentPlaceHolder1$UsernameTextBox=" + username + "&";
postData += "ctl00$ContentPlaceHolder1$PasswordTextBox=" + password;
postData += "&AuthMethod=FormsAuthentication";// Submit the data back

string url = "{url of website}";
HttpWebRequest getTokenRequest = WebRequest.Create(url) as HttpWebRequest;

getTokenRequest.CookieContainer = cookies;
getTokenRequest.ContentType = "application/x-www-form-urlencoded";
getTokenRequest.ContentLength = postData.Length;
getTokenRequest.Method = "POST";

// post the data to the request
using (StreamWriter sw = new StreamWriter(getTokenRequest.GetRequestStream()))
{
    sw.Write(postData);
    sw.Flush();
    sw.Close();
}

HttpWebResponse getTokenResponse = (HttpWebResponse)getTokenRequest.GetResponse(); 

string responseString = ResponseToString(getTokenResponse);

I have also tried another approach, which also doesn't work. This uses WCF. I get the error:

Secure channel cannot be opened because security negotiation with the remote endpoint has failed.This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint.

        const string relyingPartyId = "[ID]"; //ID of the relying party in AD FS
        const string adfsEndpoint = "https://[server]/adfs/services/trust/13/usernamemixed"; //url to hit - username & pw?
        const string certSubject = "[subject]"; //?

        //Setup the connection to ADFS
        var factory = new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
            new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential), 
            new EndpointAddress(adfsEndpoint));

        factory.TrustVersion = TrustVersion.WSTrust13;

        factory.Credentials.UserName.UserName = "[un]";
        factory.Credentials.UserName.Password = "[pw]";

        //Setup the request object 
        var rst = new Microsoft.IdentityModel.Protocols.WSTrust.RequestSecurityToken
        {
            RequestType = Microsoft.IdentityModel.SecurityTokenService.RequestTypes.Issue,
            KeyType = Microsoft.IdentityModel.SecurityTokenService.KeyTypes.Bearer,
            AppliesTo = new EndpointAddress(relyingPartyId)
        };

        //Open a connection to ADFS and get a token for the logged in user
        var channel = factory.CreateChannel();

        //added to solve a trust certificate issue - bad from a security perspective
        System.Net.ServicePointManager.ServerCertificateValidationCallback +=
        (se, cert, chain, sslerror) =>
        {
            return true;
        };

        var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
3

There are 3 answers

3
paullem On BEST ANSWER

Most likely the problem is in: string url = "{url of website}";. And/or missing parameters in the POST.

It shouldn't be just any URL. It should be a properly formatted WS-Federation request. With timestamps etc. etc. And normally/sometimes (in current ADFS) it is a two step process. First the normal request, then the real (uid+pwd) authentication. Both with proper WS-Fed parameters in the proper places. Only uid and pwd is not enough.

Now the obvious reply, no insult intended: I suggest you make a trace of the regular logon process and then compare those exact HTTP requests with your requests.

0
Elisheva Wasserman On

You have to first request to ADFS to identify your self , accept a token back, and Than send the request to your application including the token inside (as a cookie)

I suggest you use Fiddler to capture the request sent from Browser, and see which parts of your request are missing

0
Amirhossein Mehrvarzi On

Forms Authentication in ASP.NET relies on cookies by default. Once the user is signed in to an Application the runtime can issue a cookie on the browser. The browser will then send the cookie with every subsequent request to the application. ASP.NET will see the cookie and know that the user is already authenticate and does not need to sign on again.

So you need something like this with or without WebRequest.Credentials:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("yoururl");
request.CookieContainer = new CookieContainer();
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
byte[] bytes = Encoding.UTF8.GetBytes(yourpostdata);//as user pass
request.ContentLength = bytes.Length;

//or use credentials
//request.Credentials = new NetworkCredential("UserName", "PassWord");

using (Stream streamOut = request.GetRequestStream())
{
    streamOut.Write(bytes, 0, bytes.Length);
    streamOut.Close();
}

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    Stream stream = response.GetResponseStream();
    StreamReader sr = new StreamReader(stream);
    //save cookie to reuse
    //var _cookie = response.Cookies;
    string responseString = sr.ReadToEnd()
}

I wish to solve your problem.