Thursday, March 31, 2016

ADFS - Native Client and Web API on Server 2016 TP4 ADFS 4.0

This follows on from my previous post concerning Web App and Web API.

This is for Active Directory Federation Services on Server 2016 Technical Preview 4.

The code is based on the Azure AD sample: Active directory .NET native desktop.

Just ignore all the Azure AD comments. There is no Azure in this solution.

In the solution, I've set the the web API to be at localhost:44324.

The native desktop client is built on WPF.

Just to re-iterate - the ADFS has to be Server 2016 - TP4 and above. This will not work on Server 2012 R2 - ADFS 3.0. 

As before, the changes are all in a gist here.

On to the ADFS configuration:

You need to create a new Application Group.


Create the Native Client


The Client Id needs to be copied over into "ida:ClientId" in the app.config.

Create the web API.


The rest of the configuration is exactly the same as the previous post.

Exactly the same claims rules are required.

You end up with a new application group.


Note in the gist that the code around scope has been commented out. I couldn't get the scope to be passed over.

I raised this in the TP4 forum.

Run up the sample, click "Sign In" in the WPF application and you should be redirected to ADFS for authentication.

Then you should be able to add items to the to-do list.

Enjoy!

Tuesday, March 22, 2016

ADFS - Web App and Web API on Server 2016 TP4 ADFS 4.0

This is for Active Directory Federation Services on Server 2016 Technical Preview 4.

A looooong journey to get this to work because there is (as I write) absolutely no documentation on how to do this. Much midnight oil.

I based this on the Active directory dotnet webapp webapi openidconnect sample.

There is also some useful stuff on OpenId Connect Web Sign On with ADFS in Windows Server 2016 TP3 and Securing a Web API with ADFS on WS2012 R2 Got Even Easier.

This sample has a web app and a web API. The web app connects with OpenID Connect and then calls a ToDoList web API using OAuth with the auth. token from the OpenId Connect call.

Both the web app and the web API are protected by ADFS 4.0.

Now the Open ID Connect part is relatively simple. I've got it working before (see earlier blog entries).

The web API is another kettle of fish :-)

Eventually, I figured it out from Vittorio's new book Modern Authentication with Azure Active Directory for Web Applications. (Seriously, just buy it!).

I have put the important code gists here.

Disclaimer: Don't use this in a Production system e.g. I just simulated a token store. Use at your own risk! Use this as a guide.

The logout is commented out because apparently that doesn't work yet. The Profile tab won't work because that gets user details via the Graph API to Azure AD.

Just ignore all the Azure AD comments. There is no Azure in this solution.

In the solution, the web app is at localhost:44322 and the web API is at localhost:44321.

Just to re-iterate - the ADFS has to be Server 2016 - TP4 and above. This will not work on Server 2012 R2 - ADFS 3.0. (Just for a start, ADFS 3.0 has no OpenID Connect support).

On to the ADFS configuration:

You need to create a new Application Group.


Then select "Server Application or Website".


Give it a name and click next.


Save the client identifier somewhere.

The URI is https://localhost:44322/.

Click Add then Next.


You want a shared secret so click the box and copy to clipboard and save somewhere. You cannot get back to this value. This is exactly how the secret key process in Azure AD works. You can however generate a new one.

Click Next.


You will see a summary.

Click Next.

And the group is successfully created.

Now here's the trick and the bit that held me up. We want to add a web API. So we click the "Add" button on the group screen and not the new group as we did above.


Select Web API


and then Next.


Enter the identifier, then Add and Next.

The identifier is https://localhost:44321/.


For now, we just go with "Permit everyone".

Click Next.


You'll see the web app is automatically included in the permissions but you can add more.

Essentially the web app has default permissions to all the web API in the application group.

Select "user_impersonation" and click Next.

You get a summary.


Click Next.

And the web API entry is created.

You should now see both.


Now we need to add some claims.

Click Edit.

You'll see a number of tabs.


Click "Issuance Transform Rules".

Now we are in familiar territory :-).

We add some claims rules.


viz. a LDAP rule for email and a transform rule to transform email to NameID with a format of email.

These are required because the sample uses these two claims.

Save these and you are done.

Remember to add the clientID and the secret that you saved to the ClientId and AppKey entries in the web app web.config.

Run up the sample, you should be redirected to ADFS for authentication.

Then click the "ToDo List" and you should be able to add items to the list.

Hopefully, you should not have to battle as much as I did.

Enjoy!

Tuesday, March 15, 2016

ADFS : OpenID Connect with Server 2016 TP4

There are some good articles around this:

OpenId Connect Web Sign On with ADFS in Windows Server 2016 TP3

Enabling OpenId Connect with AD FS 2016

Vittorio's article (the first one) is also good for configuring ADFS, setting up AD, promoting it as a DC etc.

I ran up the server as an Azure VM.

I used the second article.

Note that this is obviously copied and pasted from somewhere else because there are a number of errors:
  • You don't need an Azure subscription
  • You don't need to do any Web API configuration
  • You don't need the secret key, only the ClientID
But following the article, I got this sample working really quickly which shows that Server 2016 is maturing.

I added more scopes so I had:

email, profile and openid

When you run up the sample, you may get an error along the lines of:

"The certificate is invalid according to the criteria".

To fix this, you have to add the ADFS SSL certificate to the client's trusted certificate store.

After successful authentication, using the Firefox SAML tracer, look at the response and you will see a parameter called "code" and another called "id_token".

The code is the access token which you can then use if e.g. you want to call a Web API.

The id_token is Base64 encoded and in JWT format so cut and paste it into Auth0's:

http://jwt.io

and you will see something like:

Header:

{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "M7jHG4emiaI2_...50",
  "kid": "M7jHG4emiaI2_...50"
}

Payload data:

{
  "aud": "f93919a8-...142fdb",
  "iss": "https://myadfs.TP4.cloudapp.net/adfs",
  "iat": 1457987350,
  "exp": 1457990950,
  "auth_time": 1457987344,
  "nonce": "635935841286452744.OTI0...DNkYzJi",
  "sub": "US6dgINcoMI...Ehgw=",
  "upn": "user-xxx@dev.local",
  "unique_name": "DEV\\user-xxx",
  "c_hash": "FlQfk4V_9...-xXw"
}

Now if you use the code from an earlier blog post to display the claims on the "Contact" page i.e.

ViewBag.ClaimsIdentity = Thread.CurrentPrincipal.Identity; 

you'll see:
Claim Type Claim Value
aud a29a6605-.0957
iss https://myadfs.TP4.cloudapp.net/adfs
iat 1458089631
exp 1458093231
http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant 1458080168
nonce 6359368642...mMy
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier 3VYhqcs/b2H6n+4L4FXmlqX5A53+lnqqwq9Ectmg+3k=
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn userxxx@dev.local
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name DEV\userxxx
c_hash YZH_zyOqt9...06Q

What's interesting is that you can't add claims rules in the ADFS wizard to a web site application group so e.g. the NameID is a GUID!

You'll notice the claims are a combination of OAuth and the old "WIF style".

Enjoy!

Thursday, March 03, 2016

ADFS : wreply does not redirect after WS-Fed signout

This is with Active Directory Federation services 3.0.

There are literally hundreds of questions around this on the Internet.

Among the comments:
  • It never worked
  • It only works on ADFS 2.0
  • It works fine on ADFS 3.0
and so on.

And further "clarification":
  • The wreply string must match the ADFS identifier exactly 
  • The wreply string must match the ADFS WS-Fed endpoint exactly 
  • The wreply string must be a sub-URL of  the ADFS WS-Fed endpoint  
  • You need to include wtrealm
and so on.

And by "exactly", I mean case and all.

Anyway, I needed to get this to work. It took me literally hours but I managed in the end.

So yes, it does work on ADFS 3.0.

My ADFS RP:

The identifier:

https://my-pc/my-app.SSO/

The endpoint:

https://my-pc/my-app.SSO/

WS-Fed signin:

GET https://my-adfs/adfs/ls/?wa=wsignin1.0&wtrealm=https%3a%2f%2fmy-pc
%2fmy-app.SSO%2f&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fmy-app.SSO

%252f&wct=2016-03-02T18%3a35%3a52Z HTTP/1.1 

which decoded is:

GET https://my-adfs/adfs/ls/?wa=wsignin1.0&wtrealm=https://my-pc/my-app.SSO/
&wctx=rm=0&id=passive&ru=/my-app.SSO/&wct=2016-03-02T18:35:52Z HTTP/1.1

WS-Fed signout:

GET https://my-adfs/adfs/ls/?wa=wsignout1.0&wreply=https%3a%2f%2fmy-pc
%2fmy-app.SSO%2fHome%2f HTTP/1.1 

which decoded is:

GET https://my-adfs/adfs/ls/?wa=wsignout1.0&wreply=https://my-pc/my-app.SSO/Home/ HTTP/1.1

which results in:

GET https://my-pc/my-app.SSO/?wa=wsignoutcleanup1.0 HTTP/1.1 

and ADFS redirects to:

GET https://my-pc/my-app.SSO/Home/ HTTP/1.1

which is what I wanted.

You will notice that both the identifier and the endpoint are the same. I suspect that the endpoint is the one that counts. In other words, the identifier could be different.

As per ADFS 2.0 does not redirect back to 'reply' url on signout :

"The wreply URL for signout requests must be a sub-URL of the Passive Requestor Endpoint defined for the RP. The reason: Any other rule would make it more difficult for the user to verify if the signout process has completed correctly, thus opening the door for unintentional information disclosure in the 'public library browser' scenario."

What is a sub-URL?

"A sub URL is simply any page on your website other than your home page. For example if we signed up www.clicksubmit.com then we may also decide to add www.clicksubmit.com/seo as a sub URL."

So in my case, the endpoint is:

https://my-pc/my-app.SSO/

and the wreply is:

https://my-pc/my-app.SSO/Home/
 
which is a sub-URL.

And this is the URL that ADFS redirects to.

Aside

There is also the matter of the "green tick".

Vittorio has this to say in "Programming WIF".

"What sets apart the cleanup from all other actions I’ve described so far is that it might not end with a redirect. If the message contains a wreply, WSFAM dutifully returns a 302 message to the indicated location; if it doesn’t, it will return an image or .gif of a green check mark."

I couldn't get this to happen.

Update

Then I came across this.

"The Endpoints tab can specify several WS-Federation passive trusted URLs. ADFS takes the value from wreply parameter and tries to match it exactly first. Note that the matching is always case sensitive, just like with any other XML comparisons!

If no exact match is found, ADFS tries to match the wreply URI to any other trusted URL which would possibly be a parent path of the URI specified in wreply.

This applies to any matching, either sign-in or sign-out.

In case of sign-out though, the matched trusted URL must also be marked as default in order for the log-out redirection to work."

Enjoy!