Happy Holidays 2015

It’s that time of the year once again!

As everyone winds down the year, it’s a time to reflect, rejoice and prepare for yet another year.  It’s been a somewhat turbulent time for me and although there have been struggles, life continues. 

2016 brings new challenges with new possibilities, and I will continue to look to update Sanders Technology with new and improved content.

I hope you have a safe holiday and wish you the best for the coming year.

R


Dynamics CRM 2016 – Notes from Presentation

Microsoft Canberra

09/12/2015


IMG_7415_Medium

 

Note: These are unedited shorthand notes taken from a presentation about the new features coming with Microsoft Dynamics CRM 2016 which launched recently (CRM Online) and will launch soon (On Premise).  Provided As-is.

 

Presenters:

Mike Hopwood, Duncan Haskins – CRM Specialists

Personal business assistant

– mines crm data and surfaces on mobile device

– link tasks to locations, e.g remind you to buy a lightbulb from Bunnings when you’re near a Bunnings

 

Intelligent customer service w/h AzureML

– data set analysis (case/notes) & suggest other cases & kbs

– crm online preview only

 

Relationship focus with Delve

– analysis of collaboration

– email interactions, document reviews

– documents from SP Online

 

[Video – Jersey Use Case]

CRM 2016 = Efficiency (Agility, ability to respond, data on the road).  Multi-purpose platform.

 

Mobile Expansion (some features in preview)

– Task based experiences

– CRM Online only (a common theme!)

– Document management (via SharePoint)

– Offline support enhanced

– Min 30 licensed users

– Leverages Azure mobile

– Mobile management

– Encryption at rest, iOS, Android & Win phone native clients

– Consistent UI X-platform

– Device optimised UX

– integration to One Note

 

Cortana still a little flaky…. Load times still slow… Recognition so-so

 

– mail merge and templates based on organisation (on mobile device), server-side config

 

Excel templating

CRM App for Outlook

– available soon

– server-side integration

– no need for client side installs

– Office365/CrmOnline only

– can sync to Exchange on premise

 

see pic for hybrid

 

– Private storage via OneDrive

[Demo – Outlook/Exchange Web Client]

 

CRM 2013 introduced the current UX approach

 

[Web Experience]

 

– Menus changed for second level menu items (last 6 months online)

– Excel spreadsheets can open native or inside Excel online

– Data changes inside Excel will propagate back to CRM.  Subject to CRM validation rules

 

[Improved Search]

– Result stack ranking

– Natural language support (inexact match)

– Cross-entity search capability

– Gameification: fantasy sales team

   o Achievements for tasks (e.g Xbox live style)

   o Encourages data quality, task completion

 

[Unified Service]

– Best of breed customer management

– Service desk (next gen) – replaces Unified Service Desk?  Integrated by default, added OAuth2 support, easier install

– kb article management (including versioning), can push to external portals

 

[Survey Tools]

– survey tools can be published (anon & known) for gauging service quality?

– can survey to track satisfaction against individual, organisation etc

– still in development, TBD

 

[Field Service Productivity]

– match qualified personnel to active jobs

– integrated for mobile

– manage supply chain data (related), e.g part orders

– Service dashboard (tier 1)

– Stats integrated (like VSTS)

– Record drill down (task/client view)

   o service history

   o time segmented, etc

   o push notifications – get updates in real-time

   o attaches relevant kbs

– metadata and telemetry integrated (activity history)

– basis for Azure ML functionality

 

[Social Engagement]

– enhanced analytics

– read customer sentiment (external)

– trawl social media for relevant topics

– leverage obvious social media tools

– realtime

– can use on internal comms (yammer, skype) to read internal sentiment

 

[Intelligent Social]

– What are people saying?

– Find cases/leads fast

– Supports multiple languages (60? languages)

– analysis & reporting

– global, national, local

– online only

 

[Dynamics Marketing]

– SMS campaign not available in Oz yet

– Campaigns and marketing supported

– modern ui (html 5)

 

[IRAP Certified]

– CRM Online certified

– Certified to store Unclassed (DLM)

 

[ExpressRoute]

– Dedicated route into AU Azure DCs

– For CRM Online (hot off the press!)

– Integrates prem to AZ DCs

– QoS

 

[Launch Dates – 2015/2016]

Dec 1 – CRMOnline (2016)

Dec 15 – CRM 2016 on premise

Jan/Feb – migration 2015-16 for existing customers

– Online first, On premise second (see second picture, below)

– Two releases per year (online & onprem)

Hybrid Solutions
IMG_7417_Medium 

 

Online First
IMG_7418_Medium


Securing a Web API with ADFS 3.0 and JWT tokens

Introduction

As APIs and web services become more and more prevalent, particularly in the Enterprise, there is an increasing need to look at ways to secure the more important interfaces, particularly if they enable access to sensitive data.

Recently, I’ve been investigating ways to secure ASP.NET Web APIs using Active Directory Federation Services (AD FS) version 3.0 (rather than Azure Active Directory) – which ships as a standard role in Windows Server 2012 R2.  Those who read this site regularly will not be surprised to find yet another ADFS article!

The Problem Defined

There are heaps of articles which explain how to secure a web application and a web API using Windows Identity Foundation (WIF), and with WS-Federation.  This suits scenarios where the user would authenticate in an interactive fashion with web applications and/or ADFS.

However, what if we want to cater for scenarios where interactive authentication (i.e. responding to a redirect to a Web Form or Windows Integrated Authentication) isn’t preferable – or possible.  I’m talking about software-to-server scenarios, or software to API where the software is manually (statically) configured, like setting a username and password a-la Basic Authentication.

What we want is for the API consumer to obtain a Json Web Token (JWT) using a SOAP request (over secure transport) and then pass that JWT in the header of subsequent REST calls to the target Web API. The Web API should validate the JWT and obtain the user’s credentials, and then complete the request as the authenticated user.

Although this would work over (unencrypted) HTTP, HTTPS is strongly recommended for all communications between a client and services you host.

Can we do it?  Yes we can. 

Here’s what the solution looks like in diagram form:

image002

In order to properly understand how this all fits together, it would help immensely if you have some prior knowledge and experience in the following:

  • Configuring ADFS Relying Parties (and working with ADFS),
  • PowerShell, or equivalent,
  • SOAP and REST,
  • ASP.NET Web APIs/.NET Framework 4.6 (or later),
  • Visual Studio 2013 or 2015

For simplicity, we’ll authenticate identities in Active Directory (as illustrated above).

The Solution – Part 1: Obtain a JWT Token

So I’m  going to take serious liberties with an approach which is reasonably well documented in the following article: http://virtualstation.azurewebsites.net/?p=4331

The original article focuses on using a JWT with Azure AD, but the same approach works just fine as it turns out for on-premise ADFS Relying Parties (RPs).

You set up a relying party (RP) as per normal, although it doesn’t require WS-Fed or SAML configuration – because we’re not going to use either.  We’ll request a JWT token, C/- ADFS 3.0’s lightweight OAuth2 implementation.  The script accomplishes this by crafting a SOAP message and sends it to the appropriate ADFS endpoint specified to request a JWT token using the username and password specified.  Note that the endpoint specified in the $sendTo variable must be enabled.

# Originally found at http://virtualstation.azurewebsites.net/?p=4331
$ADFShost = "https://<your-adfs-server>"
$sendTo = "$ADFShost/adfs/services/trust/13/usernamemixed"
$username = "<domain>\<username>"
$password =  "<password>” 
$applyTo = "https://<rp-identifier>"
 
$tokenType = "urn:ietf:params:oauth:token-type:jwt"
 
$xml = @"
<s:envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:header>
    <a:action s:mustunderstand="1">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:action>
    <a:to s:mustunderstand="1">$sendTo</a:to>
    <o:security s:mustunderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <o:usernametoken u:id=" uuid-00000000-0000-0000-0000-000000000000-0">
        <o:username>$Username</o:username>
        <o:password type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">$password</o:password>
      </o:usernametoken>
    </o:security>
  </s:header>
  <s:body>
    <trust:requestsecuritytoken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
      <wsp:appliesto xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <a:endpointreference>
          <a:address>$applyTo</a:address>
        </a:endpointreference>
      </wsp:appliesto>
      <trust:keytype>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:keytype>
      <trust:requesttype>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:requesttype>
      <trust:tokentype>$tokenType</trust:tokentype>
    </trust:requestsecuritytoken>
  </s:body>
</s:envelope>
"@
 
$tokenresponse = [xml] ($xml | Invoke-WebRequest -uri $sendTo -Method Post -ContentType "application/soap+xml" -TimeoutSec 30 )
$tokenString = $tokenresponse.Envelope.Body.RequestSecurityTokenResponseCollection.RequestSecurityTokenResponse.RequestedSecurityToken.InnerText
$token = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($tokenString))
$resource = "https://<your-rp-identifier><controller api>/<value>"
Invoke-RestMethod -Method Get -Uri $resource -Header @{ "Authorization" = 'Bearer '+ $token }

Assuming you properly configure the variable assignments at the start of this script, have configured the target RP and provide valid user credentials you ought to be able to run this script and obtain a valid JWT.  You may need to enable the OAuth2 endpoint in ADFS if it is disabled (possibly), and the credentials endpoint:

image004

You’ll need to configure the Web API at the end to handle the ADFS issued JWT, which we’ll look into shortly.

The Solution – Part 2: Accept and validate a JWT Token

The next part took me a while, and then I somehow stumbled upon a dated official Microsoft sample which demonstrates exactly how to validate an ADFS-issued JWT token!  Here’s the address: https://code.msdn.microsoft.com/AAL-Server-to-Server-9aafccc1

I strongly recommend you download and extract the sample linked above.  For one thing, it’ll save me from having to list the various NuGet packages you’ll need to get this solution working.

Note that although this sample related to Azure Active Directory, it works just fine with on-premise ADFS.  The key is in implementing functionality which strips the Authorization: Bearer <JWT> out of a request header.

If we look at the sample’s Web API implementation (TelemetryServiceWebAPI), all we really need to get working is the Global.asax.cs implementation of a global request handler.

Note that the samples are distributed under the following license:

Copyright 2013 Microsoft Corporation
//
//    Licensed under the Apache License, Version 2.0 (the "License");

The Application_Start configures a token handler:

GlobalConfiguration.Configuration.MessageHandlers.Add(new TokenValidationHandler());


Which invokes a token validation class as follows.  Note that I don’t believe you have to register the Relying Party with ADFS, i.e. you don’t require a client id or a client secret.  Now confirmed. 

internal class TokenValidationHandler : DelegatingHandler
    {
        const string Audience = https://<rp-identifier>;
        // Domain name or Tenant name
        const string DomainName = https://<rp-identifier>;

        static string _issuer = string.Empty;
        static List<X509SecurityToken> _signingTokens = null;
        static DateTime _stsMetadataRetrievalTime = DateTime.MinValue;

        // SendAsync is used to validate incoming requests contain a valid access token, and sets the current user identity 
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            string jwtToken;
            string issuer;
            string stsMetadataAddress = string.Format(CultureInfo.InvariantCulture, "https://<your-adfs-server>/federationmetadata/2007-06/federationmetadata.xml", DomainName);

            List<X509SecurityToken> signingTokens;
            using (HttpResponseMessage responseMessage = new HttpResponseMessage())
            {

                if (!TryRetrieveToken(request, out jwtToken))
                {
                    return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                try
                {
                    // Get tenant information that's used to validate incoming jwt tokens
                    GetTenantInformation(stsMetadataAddress, out issuer, out signingTokens);
                }
                catch (WebException)
                {
                    return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
                }
                catch (InvalidOperationException)
                {
                    return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));
                }

                JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler()
                {
                    // for demo purposes certificate validation is turned off. Please note that this shouldn't be done in production code.
                    CertificateValidator = X509CertificateValidator.None
                };

                TokenValidationParameters validationParameters = new TokenValidationParameters
                {
                    AllowedAudience = Audience,
                    ValidIssuer = issuer,
                    SigningTokens = signingTokens
                };


                try
                {
                    // Validate token
                    ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken,
                                                                                 validationParameters);

                    //set the ClaimsPrincipal on the current thread.
                    Thread.CurrentPrincipal = claimsPrincipal;

                    // set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
                    if (HttpContext.Current != null)
                    {
                        HttpContext.Current.User = claimsPrincipal;
                    }

                    return base.SendAsync(request, cancellationToken);
                }
                catch (SecurityTokenValidationException)
                {
                    responseMessage.StatusCode = HttpStatusCode.Unauthorized;
                    return Task.FromResult(responseMessage);
                }
                catch (SecurityTokenException)
                {
                    responseMessage.StatusCode = HttpStatusCode.Unauthorized;
                    return Task.FromResult(responseMessage);
                }
                catch (ArgumentException)
                {
                    responseMessage.StatusCode = HttpStatusCode.Unauthorized;
                    return Task.FromResult(responseMessage);
                }
                catch (FormatException)
                {
                    responseMessage.StatusCode = HttpStatusCode.Unauthorized;
                    return Task.FromResult(responseMessage);
                }
            }
        }

        // Reads the token from the authorization header on the incoming request
        static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;

            if (!request.Headers.Contains("Authorization"))
            {
                return false;
            }

            string authzHeader = request.Headers.GetValues("Authorization").First<string>();

            // Verify Authorization header contains 'Bearer' scheme
            token = authzHeader.StartsWith("Bearer ", StringComparison.Ordinal) ? authzHeader.Split(' ')[1] : null;

            if (null == token)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Parses the federation metadata document and gets issuer Name and Signing Certificates
        /// </summary>
        /// <param name="metadataAddress">URL of the Federation Metadata document</param>
        /// <param name="issuer">Issuer Name</param>
        /// <param name="signingTokens">Signing Certificates in the form of X509SecurityToken</param>
        static void GetTenantInformation(string metadataAddress, out string issuer, out List<X509SecurityToken> signingTokens)
        {
            signingTokens = new List<X509SecurityToken>();

            // The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.            
            if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
                || string.IsNullOrEmpty(_issuer)
                || _signingTokens == null)
            {
                MetadataSerializer serializer = new MetadataSerializer()
                {
                    // turning off certificate validation for demo. Don't use this in production code.
                    CertificateValidationMode = X509CertificateValidationMode.None
                };
                MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(metadataAddress));

                EntityDescriptor entityDescriptor = (EntityDescriptor)metadata;

                // get the issuer name
                if (!string.IsNullOrWhiteSpace(entityDescriptor.EntityId.Id))
                {
                    _issuer = entityDescriptor.EntityId.Id;
                }

                // get the signing certs
                _signingTokens = ReadSigningCertsFromMetadata(entityDescriptor);

                _stsMetadataRetrievalTime = DateTime.UtcNow;
            }

            issuer = _issuer;
            signingTokens = _signingTokens;
        }

        static List<X509SecurityToken> ReadSigningCertsFromMetadata(EntityDescriptor entityDescriptor)
        {
            List<X509SecurityToken> stsSigningTokens = new List<X509SecurityToken>();

            SecurityTokenServiceDescriptor stsd = entityDescriptor.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

            if (stsd != null && stsd.Keys != null)
            {
                IEnumerable<X509RawDataKeyIdentifierClause> x509DataClauses = stsd.Keys.Where(key => key.KeyInfo != null && (key.Use == KeyType.Signing || key.Use == KeyType.Unspecified)).
                                                             Select(key => key.KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First());

                stsSigningTokens.AddRange(x509DataClauses.Select(clause => new X509SecurityToken(new X509Certificate2(clause.GetX509RawData()))));
            }
            else
            {
                throw new InvalidOperationException("There is no RoleDescriptor of type SecurityTokenServiceType in the metadata");
            }

            return stsSigningTokens;
        }
    }

Once this code is in place, you can decorate ApiControllers and methods as per normal with the [Authorize] attribute to force the authentication requirement.

You can access the identity information from the User object at runtime, e.g. if you threw a standard out-of-the-box values controller into the sample: (I added this to a local copy, it is not part of the official sample)

namespace Microsoft.Samples.Adal.TelemetryServiceWebApi.Controllers
{
    [Authorize]
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return User.Identity.Name;
        }
}

Assuming that you have mapped the user’s UPN (user principal name) to “name” in the Relying Party claim rules, you’d see the user’s FQDN in the response when invoking this GET request and passing a valid ADFS-issued JWT.

Results

Here’s a PowerShell script successfully making a GET request with an ADFS issued JWT:

image

Note that in this particular case, I successfully tested the approach against ADFS vNext (4.0)!

The future approach may well lie in the following sample: https://github.com/Azure-Samples/active-directory-dotnet-daemon which is listed as the future direction.

Tips on Troubleshooting

Always check the ADFS configuration, and ensure that your endpoints are correct.  Keep an eye on the ADFS event logs, as RP misconfigurations usually end up as failed requests there.

image

Even something as simple as a trailing forward slash in the RP identifier can ruin the token validation (above).

Ensure appropriate ADFS endpoints are enabled, and if you can, try to secure your identifiers using HTTPS for best results.  In our test lab we use internal DNS and an Enterprise CA over self-signed certificates to handle site bindings.

Lastly, don’t be afraid to debug the Web API (locally or remotely), remembering that you can configure an RP in ADFS to be localhost Smile

Summary

Well, I hope this has been an informative article for you.  I’m aiming to reproduce this solution in its entirety later this week in a Greenfields environment, so this article may be subject to further edits.

I was quite happy to see a complete end-to-end scenario working perfectly in our Development environment.  In theory, this approach should work without too much configuration overhead, but the usual disclaimers apply: it works on my machine.

Feel free to post comments if you have questions or would like to know more.

Further Reading

It took a lot of reading to get this far.  Here are some very helpful articles/links which provided much needed hints and pointers.

Introductions/Approaches:

JWT in ADFS overview: http://blogs.technet.com/b/maheshu/archive/2015/05/26/json-web-token-jwt-support-in-adfs.aspx

Helpful Links: