AAD.fs


The sample

The example implements authorization using Azure Application Roles. The sample application can be found in your Azure Active Directory once provisioned:

For simplicity, the sample uses service principals as identities for the Requesting Party. The registration creates following items in the AD:

Note: All the identitifiers will have a random suffix added.

aad-sample will have following AppRoles defined:

Once provisioned you can find these roles in the application's manifest, it will look something like this:

appRoles

Requesting Party service principals will have the corresponding roles assigned, which you can check in the Enterprise Application's Users and Groups blade:

assignments

Authoring the roles

The role values can be anything you need. The example here demonstrates the capability of the library to deal with patterns and wildcards:

A "pattern" means that we can expect a structure in the value, like "items/r" or "items.r" - / is the default separator, but the character is configurable - use PartProtector.mkNew instead of mkDefault if you need to change it.
Use * wildcard in any segment of the role value to make any demand match that part of the pattern, for example items/* in the role value will match the demands like items/r, items/w, etc.
*/* means that the identity with that claim in the token will have access to APIs that demand items, fiddles/play, cats/herd, etc with or w/o any 2nd (verb) segment.

Complex demands can be expressed by combining the patterns with Any and All, for example:

Implementing Resource Server

The full source code for this sample is in AAD.tasks.Test/ResourceServers.fs, but here are the important ingredients:

task {
    let! protector : PartProtector = 
        PartProtector.mkDefault httpClient audience authority
        
    let read : HttpHandler =
        protector.Verify (fun ctx -> Task.FromResult <| Pattern ["items"; "r"])
                         (fun token -> text "Read!")

    let write : HttpHandler =
        protector.Verify (fun ctx -> Task.FromResult <| Pattern ["items"; "w"])
                         (fun token -> text "Written!")
        
    return 
        choose [
          HEAD >=> route "/" >=> Successful.NO_CONTENT
          GET >=> route "/" >=> read
          PUT >=> route "/" >=> write
          RequestErrors.NOT_FOUND ""
        ]
}

As you can see:

And as mentioned above, Admin with its */* value will meet both of those demands.

Requesting the token

On the Requesting Party side, we need to tell the AD that we need the appRoles assigned to our principals mapped to the token claims. We do that by specifying our Application URI as a scope, in case of the registerd sample it will look something like: api://{clientid}/.default, where the /.default suffix is a special identifier that tells AD to figure which role(s) the user has in the application instead of requesting one specific role. The samples use MSAL to obtain and refresh the token as needed.

Passing the token with a call

Considering that the token is refreshed periodically, AsyncRequestor or TaskRequestor can be useful to ensure a token is obtained and associated it with each request.

namespace System
Multiple items
namespace FSharp

--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Control

--------------------
namespace Microsoft.FSharp.Control
namespace FSharp.Control.Tasks
module V2

from FSharp.Control.Tasks
module ContextInsensitive

from FSharp.Control.Tasks.V2
namespace Giraffe
namespace AAD
val httpClient : Net.Http.HttpClient
namespace System.Net
namespace System.Net.Http
Multiple items
type HttpClient =
  inherit HttpMessageInvoker
  new : unit -> unit + 2 overloads
  member CancelPendingRequests : unit -> unit
  member DeleteAsync : requestUri: string -> Task<HttpResponseMessage> + 3 overloads
  member Dispose : disposing: bool -> unit
  member GetAsync : requestUri: string -> Task<HttpResponseMessage> + 7 overloads
  member GetByteArrayAsync : requestUri: string -> Task<byte []> + 3 overloads
  member GetStreamAsync : requestUri: string -> Task<Stream> + 3 overloads
  member GetStringAsync : requestUri: string -> Task<string> + 3 overloads
  member PatchAsync : requestUri: string * content: HttpContent -> Task<HttpResponseMessage> + 3 overloads
  ...

--------------------
Net.Http.HttpClient() : Net.Http.HttpClient
Net.Http.HttpClient(handler: Net.Http.HttpMessageHandler) : Net.Http.HttpClient
Net.Http.HttpClient(handler: Net.Http.HttpMessageHandler, disposeHandler: bool) : Net.Http.HttpClient
val audience : string list
val authority : Uri
Multiple items
type Uri =
  interface ISerializable
  new : serializationInfo: SerializationInfo * streamingContext: StreamingContext -> unit + 6 overloads
  member Canonicalize : unit -> unit
  member CheckSecurity : unit -> unit
  member Equals : comparand: obj -> bool
  member Escape : unit -> unit
  member GetComponents : components: UriComponents * format: UriFormat -> string
  member GetHashCode : unit -> int
  member GetLeftPart : part: UriPartial -> string
  member GetObjectData : serializationInfo: SerializationInfo * streamingContext: StreamingContext -> unit
  ...

--------------------
Uri(uriString: string) : Uri
Uri(uriString: string, uriKind: UriKind) : Uri
Uri(baseUri: Uri, relativeUri: string) : Uri
Uri(baseUri: Uri, relativeUri: Uri) : Uri
val task : FSharp.Control.Tasks.TaskBuilder.TaskBuilderV2
Multiple items
module PartProtector

from AAD

--------------------
val mkDefault : httpClient:Net.Http.HttpClient -> audiences:#seq<Audience> -> authority:Uri -> Threading.Tasks.Task<PartProtector>
type HttpHandler = HttpFunc -> HttpFunc
union case Demand.Pattern: string list -> Demand
val text : str:string -> HttpHandler
val choose : handlers:HttpHandler list -> next:HttpFunc -> HttpFunc
val HEAD : HttpHandler
module Successful

from Giraffe.HttpStatusCodeHandlers
val NO_CONTENT : HttpHandler
val GET : HttpHandler
val PUT : HttpHandler
module RequestErrors

from Giraffe.HttpStatusCodeHandlers
(c) Microsoft Corporation. All rights reserved.