Skip to content

Commit

Permalink
Created PowerShell module for AAD registrations (#59)
Browse files Browse the repository at this point in the history
There is now a powershell module for AAD registrations and deployment instructions have been updated accordingly.

Closes #55
  • Loading branch information
hansenms authored and johnstairs committed Sep 25, 2018
1 parent 1528b10 commit 674b8dd
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 99 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ A .NET Core implementation of health storage based on the FHIR standard.

[![CI Status](https://microsofthealthoss.vsrm.visualstudio.com/_apis/public/Release/badge/7621b231-1a7d-4364-935b-2f72b911c43d/1/1)](https://microsofthealthoss.visualstudio.com/FhirServer/_releases2)

## Deployment

Please see the [deployment instructions](docs/DefaultDeployment.md) for detailed instructions.

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand All @@ -16,4 +20,4 @@ provided by the bot. You will only need to do this once across all repos using o

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [[email protected]](mailto:[email protected]) with any additional questions or comments.
contact [[email protected]](mailto:[email protected]) with any additional questions or comments.
137 changes: 39 additions & 98 deletions docs/DefaultDeployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,99 +3,37 @@ Microsoft Open Source FHIR Server Deployment

This document describes how to deploy the [Microsoft Open Source FHIR Server](https://github.com/Microsoft/fhir-server). The deployment is a two-step process:

1. (Optional) Create and Azure Active Directory (AAD) application registration to secure access to the FHIR server.
1. (Optional) Create and Azure Active Directory (AAD) Application registration to secure access to the FHIR server.
2. Deploy Cosmos DB, Azure Web App, and source code using an Azure Resource Manager template.

Azure Active Directory Application Registration
Azure Active Directory Application (AAD) Registration
-----------------------------------------------

The FHIR server supports token based (JWT) authorization. If authorization is enabled, a user or an application accessing the server must present a token from a specified authority and with a specified audience.
The FHIR server supports token based (JWT) authorization. If authorization is enabled, a client (e.g. a user) accessing the server must present a token from a specified authority and with a specified audience.

To use the FHIR server, two Azure Active Directory applications must be registered, one for the FHIR server itself and one for a client application accessing the FHIR server. Please refer to the Azure Active Directory documentation for details on the [Web application to Web API scenario](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-scenarios#web-application-to-web-api).
To use the FHIR server with AAD authentication, two Azure Active Directory (AAD) Applications must be registered, one for the FHIR server itself and one for each client accessing the FHIR server. Please refer to the AAD documentation for details on the [Web application to Web API scenario](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-scenarios#web-application-to-web-api).

Both Azure Active Directory applications can be registered using the Azure Portal. Please refer to the [Azure Active Directory application registration documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v1-integrate-apps-with-azure-ad).
Both AAD Applications can be registered using the Azure Portal. Please refer to the [AAD Application registration documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v1-integrate-apps-with-azure-ad).

You can also register the applications using the [AzureAD PowerShell module](https://docs.microsoft.com/en-us/powershell/module/azuread/). First register an app for the FHIR server API:
You can also register the AAD Applications using the [AzureAD PowerShell module](https://docs.microsoft.com/en-us/powershell/module/azuread/). This repository includes a PowerShell module with some wrapper functions that help with the AAD Application registration process.

```PowerShell
# Make sure you are connected to Azure AD:
Connect-AzureAd
# FHIR audience URL.
# A good choice is the URL of your FHIR service, e.g:
$fhirServiceName = "myfhirservice"
# Note: use "https://${fhirServiceName}.azurewebsites.us" for US Government
$fhirApiAudience = "https://${fhirServiceName}.azurewebsites.net"
# Create the App Registration:
$apiAppReg = New-AzureADApplication -DisplayName $fhirApiAudience -IdentifierUris $fhirApiAudience
New-AzureAdServicePrincipal -AppId $apiAppReg.AppId
First import the module with the wrapper functions:

# Gather some information for the deployment:
$aadEndpoint = (Get-AzureADCurrentSessionInfo).Environment.Endpoints["ActiveDirectory"]
$aadTenantId = (Get-AzureADCurrentSessionInfo).Tenant.Id.ToString()
$securityAuthenticationAuthority = "${aadEndpoint}${aadTenantId}"
$securityAuthenticationAudience = $fhirApiAudience
# Display deployment information
Write-Host @"
FHIR Service Name (serviceName): ${fhirServiceName}
Authority (securityAuthenticationAuthority): ${securityAuthenticationAuthority}
Audience (securityAuthenticationAudience): ${securityAuthenticationAudience}
"@
```PowerShell
Import-Module .\samples\scripts\PowerShell\FhirServer\FhirServer.psd1
```

To access the FHIR server from a client application, you will also need a client app registration with a client secret. This client application registration will need to have appropriate application permissions and reply URLs configured. Here is how to register a client app for use with [Postman](https://getpostman.com):
Register an AAD Application for the FHIR server API:

```PowerShell
# First specify which permissions this app should have.
# Required App permission for Azure AD sign-in
$reqAad = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$reqAad.ResourceAppId = "00000002-0000-0000-c000-000000000000"
$reqAad.ResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" `
-ArgumentList "311a71cc-e848-46a1-bdf8-97ff7156d8e6","Scope"
# Required App Permission for the API application registration.
$reqApi = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$reqApi.ResourceAppId = $apiAppReg.AppId #From API App registration above
# Just add the first scope (user impersonation)
$reqApi.ResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" `
-ArgumentList $apiAppReg.Oauth2Permissions[0].id,"Scope"
# Application registration for API
$postmanReplyUrl="https://www.getpostman.com/oauth2/callback"
$clientAppReg = New-AzureADApplication -DisplayName "${fhirServiceName}-postman" `
-IdentifierUris "https://${fhirServiceName}-postman" `
-RequiredResourceAccess $reqAad,$reqApi -ReplyUrls $postmanReplyUrl
# Create a client secret
$clientAppPassword = New-AzureADApplicationPasswordCredential -ObjectId $clientAppReg.ObjectId
# Create Service Principal
New-AzureAdServicePrincipal -AppId $clientAppReg.AppId
# Write some information needed to use client app
$clientId = $clientAppReg.AppId
$clientSecret = $clientAppPassword.Value
$replyUrl = $clientAppReg.ReplyUrls[0]
$authUrl = "${securityAuthenticationAuthority}/oauth2/authorize?resource=${securityAuthenticationAudience}"
$tokenUrl = "${securityAuthenticationAuthority}/oauth2/token"
Write-Host @"
=== Settings for Postman OAuth2 authentication ===
$fhirServiceName = "myfhirservice"
$apiAppReg = New-FhirServerApiApplicationRegistration -FhirServiceName $fhirServiceName
```

Callback URL: ${replyUrl}
Auth URL: ${authUrl}
Access Token URL: ${tokenUrl}
Client ID: ${clientId}
Client Secret: ${clientSecret}
To access the FHIR server from a client, you will also need a client AAD Application registration with a client secret. This client AAD Application registration will need to have appropriate application permissions and reply URLs configured. Here is how to register a client AAD Application for use with [Postman](https://getpostman.com):

"@
```PowerShell
$clientAppReg = New-FhirServerClientApplicationRegistration -ApiAppId $apiAppReg.AppId -DisplayName "myfhirclient" -ReplyUrl "https://www.getpostman.com/oauth2/callback"
```

Deploying the FHIR Server Template
Expand All @@ -118,9 +56,10 @@ $rg = New-AzureRmResourceGroup -Name "RG-NAME" -Location westus2
New-AzureRmResourceGroupDeployment `
-TemplateUri "https://raw.githubusercontent.com/Microsoft/fhir-server/master/samples/templates/default-azuredeploy.json" `
-ResourceGroupName $rg.ResourceGroupName -serviceName $fhirServiceName `
-securityAuthenticationAuthority $securityAuthenticationAuthority `
-securityAuthenticationAudience $securityAuthenticationAudience
-ResourceGroupName $rg.ResourceGroupName `
-serviceName $fhirServiceName `
-securityAuthenticationAuthority $apiAppReg.Authority `
-securityAuthenticationAudience $apiAppReg.Audience
```

To deploy without Authentication/Authorization:
Expand All @@ -133,29 +72,31 @@ New-AzureRmResourceGroupDeployment `
-ResourceGroupName $rg.ResourceGroupName -serviceName $fhirServiceName
```

Cleaning up Azure AD App Registrations
--------------------------------------

To remove the AAD Application registrations:

```PowerShell
Remove-FhirServerApplicationRegistration -AppId $clientAppReg.AppId
Remove-FhirServerApplicationRegistration -AppId $apiAppReg.AppId
```

Testing FHIR Server with Postman
--------------------------------

You can use [Postman](https://getpostman.com) to test the FHIR server. If you have deployed with Authentication/Authorization enabled, you will need to [configure Azure AD authorization](https://blog.jongallant.com/2017/03/azure-active-directory-access-tokens-postman/
) to obtain a token. Use type "OAuth 2.0" and set the following parameters:

*Grant Type*: `Auhorization Code`

*Callback URL*: `https://www.getpostman.com/oauth2/callback`

*Auth URL*: `https://login.microsoftonline.com/{TENANT-ID}/oauth2/authorize?resource={AUDIENCE}`

*Access Token URL*: `https://login.microsoftonline.com/{TENANT-ID}/oauth2/token`

*Client ID*: `CLIENT-APP-ID` (see above)

*Client Secret*: `SECRET` (see above)

*Scope*: `Ignored` (not used for Azure AD v1.0 endpoints)

*State*: e.g., `12345`

*Client Authentication*: `Send client credentials in body`
* Grant Type: `Auhorization Code`
* Callback URL: `https://www.getpostman.com/oauth2/callback`
* Auth URL: `https://login.microsoftonline.com/{TENANT-ID}/oauth2/authorize?resource={AUDIENCE}`
* Access Token URL: `https://login.microsoftonline.com/{TENANT-ID}/oauth2/token`
* Client ID: `CLIENT-APP-ID` (`$clientAppReg.AppId`)
* Client Secret: `SECRET` (`$clientAppReg.AppSecret`)
* Scope: `Ignored` (not used for Azure AD v1.0 endpoints)
* State: e.g., `12345`
* Client Authentication: `Send client credentials in body`

Verify that the following requests return status `200 OK`:

Expand Down
16 changes: 16 additions & 0 deletions samples/scripts/PowerShell/FhirServer/FhirServer.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# Module manifest for module 'FhirServer'
#
@{
RootModule = 'FhirServer.psm1'
ModuleVersion = '0.0.1'
GUID = '8d82e68c-0121-478c-9e81-62bced8d2a68'
Author = 'Microsoft Healthcare NExT'
CompanyName = 'https://microsoft.com'
Description = 'PowerShell Module for managing Azure Active Directory registrations and users for Microsoft FHIR Server.'
PowerShellVersion = '3.0'
FunctionsToExport = 'Remove-FhirServerApplicationRegistration', 'New-FhirServerClientApplicationRegistration', 'New-FhirServerApiApplicationRegistration'
CmdletsToExport = @()
AliasesToExport = @()
}

10 changes: 10 additions & 0 deletions samples/scripts/PowerShell/FhirServer/FhirServer.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
$Public = @( Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" )
$Private = @( Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" )

@($Public + $Private) | ForEach-Object {
Try {
. $_.FullName
} Catch {
Write-Error -Message "Failed to import function $($_.FullName): $_"
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
function New-FhirServerApiApplicationRegistration {
<#
.SYNOPSIS
Create an AAD Application registration for a FHIR server instance.
.DESCRIPTION
Create a new AAD Application registration for a FHIR server instance.
A FhirServiceName or FhirServiceAudience must be supplied.
.EXAMPLE
New-FhirServerApiApplicationRegistration -FhirServiceName "myfhiservice"
.EXAMPLE
New-FhirServerApiApplicationRegistration -FhirServiceAudience "https://myfhirservice.azurewebsites.net"
.PARAMETER FhirServiceName
Name of the FHIR service instance.
.PARAMETER FhirServiceAudience
Full URL of the FHIR service.
.PARAMETER WebAppSuffix
Will be appended to FHIR service name to form the FhirServiceAudience if one is not supplied,
e.g., azurewebsites.net or azurewebsites.us (for US Government cloud)
#>
[CmdletBinding(DefaultParameterSetName='ByFhirServiceName')]
param(
[Parameter(Mandatory = $true, ParameterSetName = 'ByFhirServiceName' )]
[string]$FhirServiceName,

[Parameter(Mandatory = $true, ParameterSetName = 'ByFhirServiceAudience' )]
[string]$FhirServiceAudience,

[Parameter(Mandatory = $false, ParameterSetName = 'ByFhirServiceName' )]
[String]$WebAppSuffix = "azurewebsites.net"
)

# Get current AzureAd context
try {
$session = Get-AzureADCurrentSessionInfo -ErrorAction Stop
}
catch {
Write-Host "Please log in to Azure AD with Connect-AzureAD cmdlet before proceeding"
Break
}

if ([string]::IsNullOrEmpty($FhirServiceAudience)) {
$FhirServiceAudience = "https://${FhirServiceName}.${WebAppSuffix}"
}

# Create the App Registration
$apiAppReg = New-AzureADApplication -DisplayName $FhirServiceAudience -IdentifierUris $FhirServiceAudience
$ignored = New-AzureAdServicePrincipal -AppId $apiAppReg.AppId

$aadEndpoint = (Get-AzureADCurrentSessionInfo).Environment.Endpoints["ActiveDirectory"]
$aadTenantId = (Get-AzureADCurrentSessionInfo).Tenant.Id.ToString()

#Return Object
@{
AppId = $apiAppReg.AppId;
TenantId = $aadTenantId;
Authority = "${aadEndpoint}${aadTenantId}";
Audience = $FhirServiceAudience;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
function New-FhirServerClientApplicationRegistration {
<#
.SYNOPSIS
Create an AAD Application registration for a client application.
.DESCRIPTION
Create a new AAD Application registration for a client application that consumes an API.
.EXAMPLE
New-FhirServerClientApplicationRegistration -DisplayName "clientapplication" -ApiAppId 9125e524-1509-XXXX-XXXX-74137cc75422
.PARAMETER ApiAppId
API AAD Application registration Id
.PARAMETER DisplayName
Display name for the client AAD Application registration
.PARAMETER ReplyUrl
Reply URL for the client AAD Application registration
.PARAMETER IdentifierUri
Identifier URI for the client AAD Application registration
#>
param(
[Parameter(Mandatory = $true)]
[string]$ApiAppId,

[Parameter(Mandatory = $true)]
[string]$DisplayName,

[Parameter(Mandatory = $false)]
[string]$ReplyUrl = "https://www.getpostman.com/oauth2/callback",

[Parameter(Mandatory = $false)]
[string]$IdentifierUri = "https://${DisplayName}"
)

# Get current AzureAd context
try {
$session = Get-AzureADCurrentSessionInfo -ErrorAction Stop
}
catch {
Write-Host "Please log in to Azure AD with Connect-AzureAD cmdlet before proceeding"
Break
}

$apiAppReg = Get-AzureADApplication -Filter "AppId eq '${ApiAppId}'"

# Some GUID values for Azure Active Directory
# https://blogs.msdn.microsoft.com/aaddevsup/2018/06/06/guid-table-for-windows-azure-active-directory-permissions/
# Windows AAD Resource ID:
$windowsAadResourceId = "00000002-0000-0000-c000-000000000000"
# 'Sign in and read user profile' permission (scope)
$signInScope = "311a71cc-e848-46a1-bdf8-97ff7156d8e6"

# Required App permission for Azure AD sign-in
$reqAad = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$reqAad.ResourceAppId = $windowsAadResourceId
$reqAad.ResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $signInScope, "Scope"

# Required App Permission for the API application registration.
$reqApi = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$reqApi.ResourceAppId = $apiAppReg.AppId #From API App registration above

# Just add the first scope (user impersonation)
$reqApi.ResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $apiAppReg.Oauth2Permissions[0].id, "Scope"

$clientAppReg = New-AzureADApplication -DisplayName $DisplayName -IdentifierUris $IdentifierUri -RequiredResourceAccess $reqAad, $reqApi -ReplyUrls $ReplyUrl

# Create a client secret
$clientAppPassword = New-AzureADApplicationPasswordCredential -ObjectId $clientAppReg.ObjectId

# Create Service Principal
$ignored = New-AzureAdServicePrincipal -AppId $clientAppReg.AppId

$securityAuthenticationAudience = $apiAppReg.IdentifierUris[0]
$aadEndpoint = (Get-AzureADCurrentSessionInfo).Environment.Endpoints["ActiveDirectory"]
$aadTenantId = (Get-AzureADCurrentSessionInfo).Tenant.Id.ToString()
$securityAuthenticationAuthority = "${aadEndpoint}${aadTenantId}"

@{
AppId = $clientAppReg.AppId;
AppSecret = $clientAppPassword.Value;
ReplyUrl = $clientAppReg.ReplyUrls[0]
AuthUrl = "${securityAuthenticationAuthority}/oauth2/authorize?resource=${securityAuthenticationAudience}"
TokenUrl = "${securityAuthenticationAuthority}/oauth2/token"
}
}
Loading

0 comments on commit 674b8dd

Please sign in to comment.