Introduction
A while back ago we already discussed how to consume SharePoint Online (SPO) REST in PowerShell. Here is a brief recap:
- it was stated that Invoke-RestMethod cmdlet could not be utilized for performing REST requests since it does not support claims-based authentication
- a custom Invoke-RestSPO function has been implemented that sends HTTPS requests to SPO RESTful web services. A SharePointOnlineCredentials class from SharePoint Client Component SDK was utilized for authentication which represents an object that provides credentials to access SharePoint Online resources.
This time I would like demonstrate another approach, in particular how PowerShell can gain authorization to SharePoint resources by passing an access token to SharePoint with each HTTP request. To issue an access token from Microsoft Azure Access Control Service (ACS) that allows the app access to the resources in the SharePoint tenancy we will implement the corresponding PowerShell function. Let’s get started.
Getting Access Token from Microsoft Azure Access Control Service
The Get-SPOAccessToken function demonstrates how to obtain the access token from a Microsoft Azure Access Control Service (ACS) account that is associated with the customer’s Microsoft Office 365 tenancy:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Obtain an app-only access token from ACS. | |
.DESCRIPTION | |
Retrieves an app-only access token from ACS to call the specified principal | |
at the specified targetHost. The targetHost must be registered for target principal. If specified realm is | |
null, the "Realm" setting in web.config will be used instead | |
.EXAMPLE | |
Get-SPOAccessToken -Url "https://contoso.sharepoint.com/_api/web" -ClientId "" -ClientSecret "" | |
#> | |
Function Get-SPOAccessToken([string]$ClientId,[string]$ClientSecret,[Uri]$WebUri){ | |
$SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000" | |
$realm = GetRealmFromTargetUrl –TargetApplicationUri $WebUri | |
$accessToken = GetAppOnlyAccessToken –ClientId $ClientId –ClientSecret $ClientSecret –TargetPrincipalName $SharePointPrincipal –TargetHost $WebUri.Authority –TargetRealm $realm | |
return $accessToken.access_token | |
} | |
function GetRealmFromTargetUrl([Uri]$TargetApplicationUri) | |
{ | |
$url = $WebUrl + "/_vti_bin/client.svc" | |
$headers = @{} | |
$headers.Add('Authorization','Bearer') | |
try { | |
$response = Invoke-WebRequest –Uri $TargetApplicationUri –Headers $headers –Method Get | |
} | |
catch [Net.WebException] { | |
$authResponseHeader = $_.Exception.Response.Headers["WWW-Authenticate"] | |
#$bearerKey = "Bearer realm=" | |
$bearer = $authResponseHeader.Split(",")[0] | |
$targetRealm = $bearer.Split("=")[1] | |
return $targetRealm.Substring(1,$targetRealm.Length–2) | |
} | |
return $null | |
} | |
Function GetAppOnlyAccessToken([string]$ClientId,[string]$ClientSecret,[string]$TargetPrincipalName,[string]$TargetHost,[string]$TargetRealm) | |
{ | |
$resource = GetFormattedPrincipal –PrincipalName $TargetPrincipalName –HostName $TargetHost –Realm $TargetRealm | |
$ClientId = GetFormattedPrincipal –PrincipalName $ClientId –Realm $TargetRealm | |
$contentType = 'application/x-www-form-urlencoded' | |
$stsUrl = GetSecurityTokenServiceUrl –Realm $TargetRealm | |
$oauth2Request = CreateAccessTokenRequestWithClientCredentials –ClientId $ClientId –ClientSecret $ClientSecret –Scope $resource | |
$oauth2Response = Invoke-RestMethod –Method Post –Uri $stsUrl –ContentType $contentType –Body $oauth2Request | |
return $oauth2Response | |
} | |
Function GetSecurityTokenServiceUrl([string]$Realm) | |
{ | |
return "https://accounts.accesscontrol.windows.net/$Realm/tokens/OAuth/2" | |
} | |
Function CreateAccessTokenRequestWithClientCredentials([string]$ClientId,[string]$ClientSecret,[string]$Scope) | |
{ | |
$oauth2Request = @{ | |
'grant_type' = 'client_credentials'; | |
'client_id' = $ClientId; | |
'client_secret' = $ClientSecret; | |
'scope' = $Scope; | |
'resource' = $Scope | |
} | |
return $oauth2Request | |
} | |
function GetFormattedPrincipal([string]$PrincipalName, [string]$HostName, [string]$Realm) | |
{ | |
if ($HostName) | |
{ | |
return "$PrincipalName/$HostName@$Realm" | |
} | |
return "$PrincipalName@$Realm" | |
} |
Get-SPOAccessToken function is intended for requesting an access token from Azure ACS, it accepts Client Id and Client Secret parameters that are generated while App registration with Azure ACS (see “How to register App” for a more details).
Using Invoke-RestMethod in Office 365
Invoke-SPORestMethod function demonstrates how to include the access token to make a REST API call to SharePoint, passing the OAuth access token in the HTTP Authorization header:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.\Get-SPOAccessToken.ps1 | |
<# | |
.Synopsis | |
Sends an HTTP or HTTPS request to a SharePoint Online REST-compliant web service. | |
.DESCRIPTION | |
This function sends an HTTP or HTTPS request to a Representational State | |
Transfer (REST)-compliant ("RESTful") SharePoint Online web service. | |
.EXAMPLE | |
Invoke-SPORestMethod -Uri "https://contoso.sharepoint.com/_api/web" -AccessToken $accessToken | |
#> | |
Function Invoke-SPORestMethod() | |
{ | |
Param( | |
[Uri]$Uri, | |
[Object]$Body, | |
[Hashtable]$Headers, | |
[Microsoft.PowerShell.Commands.WebRequestMethod]$Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get, | |
[string]$AccessToken | |
) | |
$contentType = 'application/json;odata=verbose' | |
if(-not $Headers) { | |
$Headers = @{} | |
} | |
$Headers["Accept"] = "application/json;odata=verbose" | |
$Headers.Add('Authorization','Bearer ' + $AccessToken) | |
Invoke-RestMethod –Method $Method –Uri $Uri –ContentType $contentType –Headers $Headers –Body $Body | |
} |
Examples
The following example demonstrates how to retrieve List resource properties:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
param ( | |
$ClientId, | |
$ClientSecret, | |
$WebUri, | |
$ListTitle | |
) | |
.\Get-SPOAccessToken.ps1 | |
Function Get-SPOList([Uri]$WebUri,[string]$ClientId,[string]$ClientSecret,[Uri]$ListTitle) | |
{ | |
$accessToken = Get-SPOAccessToken –ClientId $ClientId –ClientSecret $ClientSecret –WebUri $WebUri | |
$endpointUri = "$WebUri/_api/web/lists/getbytitle('$ListTitle')" | |
$data = Invoke-SPORestMethod –Uri $endpointUri –AccessToken $accessToken | |
return $data.d | |
} | |
Get-SPOList –WebUri $WebUri –ClientId $ClientId –ClientSecret $ClientSecret –ListTitle $ListTitle |
But before running the specified script we need to perform one more step in order to grant permissions to the app principal otherwise the unauthorized error will occur as shown on picture below:
- Navigate to http://<SharePointWebsite>/_layouts/15/AppInv.aspx
- Look up the app based on the Client ID that you just generated and click Lookup, it will find the app principal. Then paste the AppPermissionRequests XML into the Permissions text box and click Create
Once you click Create, the Trust dialog will appear, click Trust
That’s it. Now, after executing the specified script, the output will look like shown below
How to register App
Below is provided a step by step instruction how to register an App, for a complete guide follow this article:
- To create the app identity, navigate to http://<SharePointWebsite>/_layouts/15/AppRegNew.aspx on the tenancy or farm
- Enter values for the form fields as shown below on picture
where
App ID: App ID, also known as client ID, is a GUID that can be generated (when you click Generate) or pasted into AppRegNew.aspx. The value must be unique for each app, and must be lower case
App Secret: The app secret, also known as the client secret, is an opaque string. It is generated on the AppRegNew.aspx page by using the Generate button. The following is an example of an app secret: Ywjaoz7DJBGhoLQ2t0IbVCA5pfqqI722ZIVt+ENLk0g=
Title: Choose your own user-friendly title; for example, PowerShell Console
App Domain: The host name of the remote component of the app for SharePoint
Redirect URI: The endpoint in your remote application or service to which ACS sends an authentication code - Click Create on the form. The page will reload and show a confirmation of the values you entered as shown on picture below
- Save Client Id and Client Secret values. After that you could verify whether Get-SPOAccessToken function returns access token. The picture below shows the output after executing the command:
Get-SPOAccessToken -ClientId “1523cf05-b437-4e73-9ad1-a652da8f2ae5” -ClientSecret “Ywjaoz7DJBGhoLQ2t0IbVCA5pfqqI722ZIVt+ENLk0g=” -WebUri “https://contoso.sharepoint.com/”