lørdag den 7. maj 2011

ADFS 2.0 and ASP.NET

I want to learn more about ADFS, and possibly use it my self in a few projects, so I set my self the goal to create a simple webpage that implemented SSO through Windows Identity Foundation and using my existing ADFS 2.0 server. I wanted a website that supports anonymous users, and sign on using both Kerberos and Forms based authentication.

Man has that been more reading than testing, but I think I’m starting to get the basic concepts. One of the first things that had me confused was that I had just seen in CRM 2011 to control how ADFS decides to authenticate you was based on URL’s and or FQDN’s. Well, it doesn’t. Yes you can modify FormsSignIn.aspx and IdpInitiatedSignOn.aspx. You can also force a specific way by editing the list of handlers in web.Config on the ADFS server. But that would just break my CRM2011 installation and just felt wrong.

Turns out, ADFS doesn’t decide, YOU decided by setting the authenticationType in the URL used to redirect the user to the ADFS server. Gah, I’m getting away from the point of this blog post. I wanted to give my self and others a quick how to, on how to quickly implement a webpage that supports all the above.

1) install ADFS.

2) on your developer machine, install Windows Identity Foundation SDK. I’m working in .NET 4.0 so just download that and install it. After installation, if you are a VB coder like me you will get disappointed to see you cant find the “Claims based aware website” template. Ah well, screw them we don’t need help, do we ? Open-mouthed smile …Create a new empty website. To save your self *A LOT* of trouble, make it use SSL.

image

image

If you decide to add it later, or just make sure the application in the IIS has force SSL enabled. You can remove it again in a minute don’t worry.

image

Now, right click your website in visual studio and choose “Add STS reference…”

image

Application URI is what people will type in there browser to access your website. Now, you can either run this guide multiple times for each FQDN, or just edit it later in web.config and “Update federation metadata”. Just to be safe I ran the guide twice. Once for the internal URL and once for the external URL. ( the screenshots here is from an test app I call SSI2 )

image

Press next and choose “Use existing STS”, type the URL to your STS ( ADFS 2.0 server. this could ofc also just be the build in STS server in windows 2008 R2 –> Server manager –> Roles –> Add Roles –> “Active Directory Federation Service” … )
In my case the STS is at https://adfs.wingu.dk/FederationMetadata/2007-06/federationmetadata.xml

image

If you don’t have an STS, you can also choose “Create a new STS project” but …
First of all. It is C# and who wants to look at ugly code with an semicolon in the end of every sentence ? and secondly.
You will miss out on all the wonderful trouble shooting and the “WAOUW” feeling when it finally works. With the STS sample you just type a name in the login screen and everything works. Where is the fun in that ?

Next decided if you want the wsFederation to validate certificates. of course you do. *cough*

image

Next choose a certificate used to encrypt tokens send back and forth from the STS and your Web app. You can skip this, but I really don’t recommend it.

image

Accept the confirmation page that also list all the claims you can get from the STS. Don’t worry they will all be in your web.config ready to be “un remarked” for your pleasure.

image

First thing you want to do is update your STS with the new project you just created. Notice the guided added an XML file, you can use to link your STS to your webapp with.

image

Go on, jump to your ADFS and add the URL. Open AD FS 2.0 console and add your Relying Party Trust

image

Choose Import data about the relying party published online or on local network and past in the URL to your application. ( in my case it’s https://admin.wingu.dk/ssi2/FederationMetadata/2007-06/FederationMetadata.xml )

image

Choose default setting the rest of the guide and jump back to Visual Studio.
Open your web.config and go to
configuration->system.web->authorization and remark out the deny rule

<authorization>
  <!-- <deny users="?"/> -->
</authorization>

Hell, while you are there disable errors too, you will see one soon. I promise.

<system.web>
  <customErrors mode="Off"/>
  <authorization>
    <!-- <deny users="?"/> -->
  </authorization>

If you go down to configuration –> microsoft.identityModel –> service-> applicationService –> audienceUris you will see the URL’s you added above.

<audienceUris>
  <add value="https://admin.wingu.dk/ssi2/"/>
  <add value="https://prov01.int.wingu.dk/ssi2/"/>
</audienceUris>

You can edit this at anytime and update it by right clicking your project and choose “Update federation metadata”.
image

Remember you need to refresh this on the ADFS server too

image

Add 2 Web Forms. Default.aspx and login.aspx. On default.aspx drag in a FederatedPassiveSignInStatus and then double click it. Add the following code

Protected Sub FederatedPassiveSignInStatus1_SigningOut(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles FederatedPassiveSignInStatus1.SigningOut
    Dim authModule As WSFederationAuthenticationModule = FederatedAuthentication.WSFederationAuthenticationModule
    Dim signoutUrl As String = WSFederationAuthenticationModule.GetFederationPassiveSignOutUrl(authModule.Issuer, authModule.Realm, Nothing)
    WSFederationAuthenticationModule.FederatedSignOut(New Uri(authModule.Issuer), New Uri(authModule.Realm + "Default.aspx"))
End Sub

This code will trigger when the user is logged in, and clicks the “logout” link. If user isn't logged in, and he clicks the “sign in” link, the user gets redirect to login.aspx.

So go to that and add the following code to handle logins.

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    Dim authModule As WSFederationAuthenticationModule = FederatedAuthentication.WSFederationAuthenticationModule
    authModule.PassiveRedirectEnabled = True
    'Dim mess As WSFederationAuthenticationModule.SignInRequestMessage = authModule.CreateSignInRequest("passive", returnUrl, False)
    Select Case Request.Url.Host.ToLower
        'Case "localhost", "prov01", "prov01.int.wingu.dk"
        '    Dim returnUrl As String = "https://prov01.int.wingu.dk/ssi2/"
        '    Dim mess As WSFederation.SignInRequestMessage = authModule.CreateSignInRequest("passive", returnUrl, False)
        '    mess.Realm = "https://prov01.int.wingu.dk/ssi2/"
        '    Dim redirURL As String = mess.WriteQueryString()
        '    Response.Redirect(redirURL)
        Case "localhost", "prov01", "prov01.int.wingu.dk"
            Dim returnUrl As String = "https://admin.wingu.dk/ssi2/"
            Dim mess As WSFederation.SignInRequestMessage = authModule.CreateSignInRequest("passive", returnUrl, False)
            mess.AuthenticationType = "urn:federation:authentication:windows"
            mess.Realm = "https://admin.wingu.dk/ssi2/"
            Dim redirURL As String = mess.WriteQueryString()
            Response.Redirect(redirURL)
        Case Else
            Dim returnUrl As String = "https://admin.wingu.dk/ssi2/"
            Dim mess As WSFederation.SignInRequestMessage = authModule.CreateSignInRequest("passive", returnUrl, False)
            mess.AuthenticationType = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
            mess.Realm = "https://admin.wingu.dk/ssi2/"
            Dim redirURL As String = mess.WriteQueryString()
            Response.Redirect(redirURL)
    End Select

    'HttpContext.Response.Redirect(mess.WriteQueryString())

End Sub

Yeah, I suck. I haven't solved all the problems yet,I will update this post once I get it working the way I want. But I’m still sure others searching on ADFS might find a bit of this useful. Basically I want to update “AuthenticationType” so that users internally don’t get prompted at all, and just fly’s in via Kerberos and people externally gets ADFS’s forms based login page. Since ADFS has both the internal URL and external URL I would expect the code I remarked out to work, but I keep getting

ID3206: A SignInResponse message may only redirect within the current web application: 'https://prov01.int.wingu.dk/ssi2/' is not allowed.

so instead I force all users to be redirected to the external page. It works, sure but if the user logs out he cant login again using Kerberos ( he's now coming from the external domain ) so he either have to update the URL in the browser or login using forms based authentication. If I find out why I get that error ill update the post. For now this almost solved the goals I set for my self

4 kommentarer:

  1. Hi,
    Were you able to figure out the error: ID3206: A SignInResponse?

    I keep getting this even I catch the ThreadAbortException.

    SvarSlet
  2. I've written an answer here http://blog.skadefro.dk/2011/12/id3206-signinresponse-message-may-only.html

    SvarSlet
  3. Can we get Loggedin user list from ADFS for MS CRM 2011.

    SvarSlet