Tuesday, February 14, 2017

AD : Using PowerShell to create users and groups

ADFS specifically targets authentication and authorisation.

It does not target provisioning users into AD or adding them to groups. You need an Identity Manager for that.

You can do this with PowerShell and there are many links to do this around on the Internet e.g. PowerShell: Bulk create AD Users from CSV file.

My version

csv file format is:

Firstname,Lastname,Maildomain,SAM,OU,Password,Email
Joe,Bloggs,mydomain.local,jbloggs,"OU=MyUsers,DC=mydomain,DC=local",password,jb@abc.co.nz

Script

$Users = Import-Csv -Path "C:\blah\Users.csv"            
foreach ($User in $Users)            
{            
    $Displayname = $User.'Firstname' + " " + $User.'Lastname'            
    $UserFirstname = $User.'Firstname'            
    $UserLastname = $User.'Lastname'            
    $OU = $User.'OU'            
    $SAM = $User.'SAM'            
    $UPN = $User.'Firstname' + "." + $User.'Lastname' + "@" + $User.'Maildomain'            
    $Password = $User.'Password'  
    $Email = $User.'Email'
    
    New-ADUser -Name "$Displayname" -DisplayName "$Displayname" -SamAccountName $SAM -UserPrincipalName $UPN -GivenName "$UserFirstname" -Surname "$UserLastname" -Description "$Description" -AccountPassword (ConvertTo-SecureString $Password -AsPlainText -Force) -EmailAddress "$Email" -Enabled $true -Path "$OU" -ChangePasswordAtLogon $false –PasswordNeverExpires $true -server mydomain.local 

    Write-Host –NoNewLine "Adding user:  "
    Write-Host $SAM
}
 
$Users = Import-Csv -Path "C:\blah\Users.csv"            
foreach ($User in $Users)            
{   
    $SAM = $User.'SAM'            
    
    Add-ADGroupMember -Identity "My Group 1" -Member $SAM
    Add-ADGroupMember -Identity "My Group 2" -Member $SAM
    
    Write-Host –NoNewLine "Adding groups for user:  "
    Write-Host $SAM
}    

Enjoy!

Wednesday, February 08, 2017

ADFS : Useful PowerShell cmdlets

I seem to be running these on just about every installation I do these days so thought it would be worthwhile to note them.

Login with email address

This seems to be more and more common.

Set-AdfsClaimsProviderTrust -TargetIdentifier "AD AUTHORITY" -AlternateLoginID mail -LookupForests company.co.nz 

There are some gotchas with this especially if you are thinking of extending out to Azure (via AD Connect) at some point.

Configuring Alternate Login ID

Certificate revocation

Most Dev. instances don't have access to the extranet. The ADFS login will be slower because ADFS will try and check for certificate revocation.

So it makes sense to remove this functionality.

Set-AdfsRelyingPartyTrust -TargetIdentifier urn:xxx -SigningCertificateRevocationCheck None -EncryptionCertificateRevocationCheck None

Skew

Although the time across servers should be consistent, in a lot of cases it isn't. This means that if the ADFS server is ahead, the SAML token will be in the future and the SAML RP will reject it.

Some SAML RP that I have dealt with have the skew hard coded so it cannot be altered.

The best solution is to ensure that the server time is synchronised but if that is not possible, you can "back date" the time in the token. The cmdlet below sets the time 3 minutes backwards.

Set-AdfsRelyingPartyTrust -TargetIdentifier urn:xxx:de -NotBeforeSkew 3

ADFS Not Before Time Skew 

Enjoy!

Wednesday, January 18, 2017

WebAPI : Using Swagger and Postman with multiple POST

I've been busy on a WebAPI project where I need multiple POST actions in a a WebAPI controller.

There's a ton of stuff around best practice for this - some say each controller should only have one POST; others disagree.

I finally decided to break the controllers up by category and then within each controller have multiple POST functions.

I also added Swagger via Swashbuckle and used Postman for some unit tests so I thought I would write this all up.

As an example, I have a Maths controller with Add and Multiply.

The controller:

using MathsWebAPI.Models;
using System.Web.Http;

namespace MathsWebAPI.Controllers
{
    public class MathsController : ApiController
    {
        // POST: api/Maths
        [HttpPost]
        public int Add([FromBody] Maths info)
        {
            int number1 = info.number1;
            int number2 = info.number2;

            return (number1 + number2);
        }

        // POST: api/Maths
        [HttpPost]
        public int Multiply([FromBody] Maths info)
        {
            int number1 = info.number1;
            int number2 = info.number2;

            return (number1 * number2);
        }
    }
}

Note that I am passing in a model object as I have multiple parameters.

The model object:

namespace MathsWebAPI.Models
{
    public class Maths
    {
        public int number1 { get; set; }

        public int number2 { get; set; }
    }
}

The WebAPIConfig.cs:

using System.Web.Http;

namespace MathsWebAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            /*config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );*/

            config.Routes.MapHttpRoute(
                name: "ControllerAndAction",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new {id = RouteParameter.Optional}
            );
        }
    }
}

In SwaggerConfig.cs, uncomment:

c.DisableValidator();

to avoid the error message.

Now if I navigate to:

http://localhost:44571/swagger

where 44571 is my IIS Express port

I see:


Now if you click the yellow area, it will populate the "Info" box with the model object format and you just have to fill in the values.

Click "Try it out" and you see:


 Note the URL is:

api/Maths/Add

Now if we add Postman, the commands are in this gist.

Running the script, we see:


Enjoy!

Friday, January 13, 2017

ASP : Classic ASP and ADFS

This question comes up from time to time and there was a recent one on the forum.

I did this back in the day so thought it would be a good idea to write up.

Classic ASP is basically just static HTML files, no web.config, no ASP pipeline, no code behind etc.

And they are still out there.

So we will integrate it with ADFS using WIF i.e. everything is driven by a web.config.

Which means we need to add one - example in this gist.

To test this, I used a Windows 8 PC.

This PC has both WIF 3.5 and WIF 4.5 installed. WIF 4.5 is built into ASP.NET 4.5.

So you may need to install this.

This is the line in the web.config:

section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

Also WIF doesn't run on Server 2003 - hopefully you've upgraded by now :-)

The key to getting WIF to be added to the pipeline is this line:

modules runAllManagedModulesForAllRequests="true"

Note that the logging at the end is optional and this part of the file can be removed if not required.

All the above has to be done "on the fly". There is no way to create a Classic ASP project in Visual Studio. (In fact, Microsoft go out of their way to actively discourage this. The official line is that Classic ASP is no longer supported). Consequently, there is no context checking, intellisense or compile / run phase.

I didn't use VS at all - just Notepad++.

Also remember to enable ASP under Windows Features / IIS / World Wide Web Services / Application Development Features.

And you can't use an classic Application Pool in IIS - it needs to be integrated.

I just took a folder on my local PC and added a Default.asp (which contained some basic HTML) and the web.config.

Then I added this as an application in IIS. I called the application "ClassicASPBasic".

I then configured an RP on ADFS. There is no metadata so it easiest to do this manually.

Then configure the web.config. You just need to alter the "my-adfs" to your ADFS FQDN and "my-pc" to wherever you have the website hosted.

Also remember to update the thumbprint with your ADFS token-signing certificate.

Then in IIS on the RHS - "Browse Application" - use the port 443 link.

IIS sees the web.config. The web.config tells it to use the WIF classes. WIF by default protects everything so it sends you off to ADFS to authenticate. Authenticate and you are redirected back to the simple .asp page.

The next step is to Sign Out. You cannot use the standard WIF FederatedSignOut messages because there is no .NET framework so it has to be done via a URL link on the page. If you want the user to end up on the ADFS logout page, use:

<a href="https://my-adfs/adfs/ls/?wa=wsignout1.0">Sign out</a>

If you want to the user to be redirected back to the application use:

<a href="https://my-adfs/adfs/ls/?wa=wsignout1.0&wreply=https://xxx/yyy/Logout.asp">Sign out and return</a>

where:
  • xxx is the ADFS URL
  • yyy is the application URL
  • Logout.asp is a new page added to the site

The other way is to use VS and create an MVC application. Refer How To: Build Claims-Aware ASP.NET MVC Web Application Using WIF.

Then simply copy all your static files over.

This way allows you to access the claims as per the link. 

Enjoy!

Monday, January 09, 2017

AD : ERROR_DS_CONSTRAINT_VIOLATION (0x8007202f)

I was helping a customer who kept getting:

ERROR_DS_CONSTRAINT_VIOLATION (0x8007202f)

when trying to change passwords programmatically.

I referred him to the password rules in the default policy i.e. the usual:

Passwords may not contain the user's samAccountName (Account Name) value or entire displayName (Full Name value). Both checks are not case sensitive.

The password contains characters from three of the following categories:
  • Uppercase letters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
  • Lowercase letters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
  • Base 10 digits (0 through 9)
  • Non-alphanumeric characters (special characters) (for example, !, $, #, %)
The customer was adamant that the password conformed to these.

But there is also another constraint - by default you cannot change your password more than once a day.

This was the reason that was tripping the customer up.

The moral of the story is that if the error says "Password constraint" it's because there is one :-)

Bonus

You may be wondering how to handle the case where you register a new user programmatically. Normally you create a temporary password and send this in an email and the user clicks on a link which takes them to some kind of "Change Password" page.

The user then changes their password.

In this case, the password is being changed twice so how do you get around the constraint?

Add the "User must change the password at next logon" flag when you create the temporary password.

Enjoy!

Thursday, December 22, 2016

AAD : The V2.0 endpoints

I've been helping a few people lately with this and I see the same few issues that continually trip people up.

Table to help decide whether to use the v2.0 endpoints or not.

And note that currently the "Resource owner password credentials" grant is not supported.

Have a look at the current restrictions.

The endpoints are different:

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
https://login.microsoftonline.com/common/oauth2/v2.0/token

The scopes are different. They now take the form of a URI e.g.

"scope": "https://outlook.office.com/mail.read https://outlook.office.com/mail.send"

You can use the default scope e.g.

https://graph.microsoft.com/.default

This means that the application uses the pre-defined scope.

You need to use the Application Registration Portal to register the application. Don't use the usual Azure AD Applications screens.

You need to cater for the admin. consent endpoint if you want to grant consent for the application to anyone who can authenticate.

Use the MSAL library rather than the ADAL library.

Enjoy!

Wednesday, December 21, 2016

Swagger : Using Swagger for Implicit Grant on Azure AD

Review my previous post - Swagger : Using Swagger for Implicit Grant on ADFS 4.0.

This builds on that.

Given that I couldn't get it working on ADFS, I thought I would try Azure AD (AAD).

One of the problems with AAD is that the web API calls are protected so if you want to use Swagger, authentication always fails.

I started with the OpenID Connect - web app. with web API sample. Follow the details to configure the web app. and the web API in AAD.

I'm running this on my PC.

This is a standard "adding items to a ToDo list" project.

Then I added swashbuckle etc. as per my post above.

As always, the gist is here.

Note that AAD needs the "resource" parameter, hence the extra line:

additionalQueryStringParams: new Dictionary() { { "resource", "https://azure-tenant.onmicrosoft.com/TodoListService" } }

Also, you do not need the clientSecret for implicit flow. This is because implicit flow is normally used for SPA and the secret key could be easily exposed in the JavaScript.

Looking at the ToDoListController:

if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation")
            {
                throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized, ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found" });
            }

            if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
            {
                todoBag.Add(new TodoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });
            }

So we need a scope of  "user_impersonation" and we need a NameID claim.

We get the OAuth endpoints from the "View Endpoints" button in AAD. (Using the classic portal).


We need to add Swagger as an Azure AD application - the normal web site flow.

Ensure there is  "Reply URL" for:

  https://my pc/TodoListService/swagger/ui/o2c-html

We have to give this application permission to access the ToDoListService:


So what we are going to do is to use the same web API back-end but use Swagger as the web app.

Since there is a common back end, the ToDo list should reflect across both.

From the project, we add item "1234".


Now we try with Swagger.


and we get the 401.

So we click the red exclamation mark and we see:


Click "Authorize" and then authenticate with AAD.

Problem: we need to explicitly enable implicit flow. You do this via the manifest and then update:

"oauth2AllowImplicitFlow": true,

Now if we break-point at the NameID code and then check which attribute this matches in the token, we''ll see it matches the "sub":

...
"scp": "user_impersonation",
"sub": "nYa...Zow",
...

The OWIN classes must do this mapping internally viz. "sub" --> "NameID".


Now that we have authenticated, let's try again.


and we see that we get back the "1234" that we created with the web app.

Now we add a new item using the POST.

The object requires a "Title" and an "Owner" so we need to build one that looks like:

{
    "Title": "5678",
    "Owner": "nYa...Zow"
}

That returns OK and when we do another GET, we see:

[
  {
    "Title": "5678",
    "Owner": "nYa...Zow"
  },
  {
    "Title": "1234",
    "Owner": "nYa...Zow"
  }
]
 

Now go back to our web app.

so it all lines up.


Enjoy!



Monday, December 12, 2016

Swagger : Using Swagger for Implicit Grant on ADFS 4.0

This is based on this post.

I like Swagger. It maps your REST API and documents them and then allows you to test them from the documentation. Very neat.

You can write the code first and then Swagger reflects the API - much like the default API page in an MVC project or you can use the Swagger UI link to write the contract first and then generate.

Very much along the lines of code first vs. contract first.

(Aside: Once upon a time, I used SOAP and wsdl's and the WSCF (Web Services Contract First) project from thinktecture that does much the same thing. That article is dated from 2006! The more things change ...)

ADFS 4.0 (Server 2016) opens up the OpenID Connect / OAuth stack with full support for all four grant types (refer previous blog entries for a number of examples of this).

So it was a no brainer to try and put the two together.

I used this as a start point and that post gives a good overview of Swagger and how to integrate into a .NET MVC project with NuGet Swashbuckle.

The problem of course is that you need to authenticate the web API and the only OAuth type support in Swagger as I write is the implicit flow.

So following the article, as usual the gist is here.

You will notice that there is a scope called "sampleui". It's simple to add this to the Scopes section in the ADFS wizard.

So we run up the project in VS 2015 and then navigate to:

https://localhost:44326/swagger

(or whatever port your web API project uses).


Notice the highlighted exclamation mark in red.

Click that.


Check the boxes, click "Authorize".

It should take you off to the ADFS login page where you authenticate.


 Notice the exclamation mark is now blue (and if you click it you can Logout).

Now we put a value e.g. 5 in the "id" box and click "Try it out".

And it returns:

{
  "Message": "Authorization has been denied for this request."
}

I've tried everything I can think of  but I can't get this to work.

The JWT returned is:

{
  "aud": "microsoft:identityserver:7b23c943-6782-4d5b-b56c-7ecd59da17f2",
  "iss": "http://my-adfs/adfs/services/trust",
  "iat": 1480889151,
  "exp": 1480892751,
  "apptype": "Public",
  "appid": "7b2...7f2",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "auth_time": "2016-12-04T22:05:50.803Z",
  "ver": "1.0",
  "scp": "sampleapi user_impersonation"
}

and when I access the API normally from a client in the VS project, I get the JWT:

{
  "aud": "https://localhost:44326/NativeTodoListService1",
  "iss": "http://my-adfs/adfs/services/trust",
  "iat": 1480889366,
  "exp": 1480892966,
  "apptype": "Confidential",
  "appid": "0cc...549",
  "authmethod": "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password",
  "auth_time": "2016-12-04T22:09:26.486Z",
  "ver": "1.0"
}

I suspect the problem is that the "aud" is wrong. But I can't figure out how to get rid of the "microsoft:identityserver" one?

So close :-(

Any bright ideas?

Enjoy!

Monday, November 28, 2016

Postman : Using Postman for Implicit Grant on ADFS 4.0

This is ADFS 4.0 (Server 2016) and you need the official release.

I tried to get this working on the TPx series but no joy.

I kept getting the error:

"MSIS9358: Received invalid OAuth authorization request. The authorization server does not support the response type 'token' for confidential clients."

However, there is an extra entry in the official release which seems to to the job.


When you select this entry, you first get to configure a "Native Application" that generates a ClientID and you need to provide a "Redirect URI" and then an "Access Policy".

You end up with something like:


and you can configure the web application with claims etc.

The "Native Application" seems a bit weird but refer Customizing Id_Token Claims with OpenId Connect in AD FS 2016.

"Behind the scenes, this template creates a native client and new app type called Web application, which is just a Web API with an Identifier (RPID) that matches the native client's client ID. This means the Web application is simultaneously client and resource, so you can assign issuance transform rules as you would with a Web API."

As always, the gist is here.

You need to run this in a browser, The implicit flow returns an access token straight away. You miss out the intermediate step of getting a code and then using that for an access token. It's normally used for SPA where JavaScript is involved. Hence no security key is required as that would be easy to access.

If you look at the access token with e.g. jwt.io, you see:


Notice the "apptype" is "Public" and this is what is required for this flow. Hence the error about "confidential clients".

Enjoy!

Wednesday, November 23, 2016

ADFS : Metadata on ADFS 4.0

This is on Server 2016.

I've had a number of questions around compatibility e.g. can ADFS 3.0 federate with ADFS 4.0?

The servers are very different e.g. 2016 has containers, full OpenID Connect / OAuth support etc.  but the SAML level on both is still 2.0 so there should be no issues.

As an exercise, I checked the ADFS metadata on both.

Other than the expected differences e.g. federation service name, certificates, ID's etc. there is no difference which bears out the fact that they are compatible.

Enjoy!

Friday, November 11, 2016

Postman : Using Postman for Resource Owner Password Grant on ADFS

This is on Server 2016 TP5 - ADFS 4.0

Couldn't find any examples of this so rolled my own.

As always, the gist is here,

Note that you need the user in the "domain\user" format.

Be careful of this flow  - you are potentially exposing the user name and password.

This is supposed to be for "trusted" clients.

Enjoy!

Postman : OpenID Connect / OAuth errors

This is on ADFS 4.0 on Server 2016 TP5.

Yes, I know the official release is out but I've had other priorities :-)

So some of this may not apply to the official release.

This is based on the Postman collection described here.

While I was trying to get this to work, I encountered a number of errors and quickly established that there is basically zero documentation on any of them.

So I documented as I went along.

There is a authorisation request. This is an https request with a number of parameters and I removed them manually one by one.

Remove client id

MSIS9220: Received invalid OAuth authorization request. The 'client_id' parameter is missing or found empty. The client could not be validated.

Remove "response_type=code"

MSIS7065: There are no registered protocol handlers on path /adfs/oauth2/authorize to process the incoming request.

Remove "redirect_uri=https%3A%2F%2Fmy-pc%2FTodoListWebApp%2F"

MSIS9221: Received invalid OAuth authorization request. The 'redirect_uri' parameter is missing or found empty. Public clients must send the redirect_uri parameter with valid redirect URI in the OAuth authorization request.

This is followed by a REST token request.

This is driven by Postman and one of the nice features of Postman is there is a tick box next to each parameter and if you untick the box, that parameter is not sent.

Remove client_id

MSIS9629: Received invalid Client credentials.'client_secret' was present but 'client_id' parameter is missing or found empty.

Remove code

MSIS9610: The 'code' parameter is not specified. The access token request must contain the 'code' parameter which specifies the previously issued authorization code."

Remove "grant type"

MSIS7065: There are no registered protocol handlers on path /adfs/oauth2/token to process the incoming request.

Remove "client_secret"

MSIS9267: No Client credentials found in the request. Client 'a07...e75' is configured as a confidential client.

Remove "redirect_uri"

MSIS9221: Received invalid OAuth authorization request. The 'redirect_uri' parameter is missing or found empty. Public clients must send the redirect_uri parameter with valid redirect URI in the OAuth authorization request.

MSIS9608: The 'redirect_uri' parameter is not specified. The access token request must contain 'redirect_uri' parameter for public clients

Note there are two slightly different errors here. I noticed this in some other scenarios as well.

The first error is from the ADFS event log i.e. the server side (which is where most of the errors were copied from).

The second is what Postman actually reports i.e. the client side.

I have no idea why they are different?

Enjoy!