In this article we will take a look at invoking a RESTful API with PowerShell. We are
going to use the Invoke-RestMethod
cmdlet and learn about its parameters. Maybe it's
useful to someone to get going quickly.
The Problem
There was a bug in the ODataAuthorization library, and instead of using Postman I thought it would be nice to learn a little PowerShell and execute requests using a Script.
So basically, I want to make a HTTP Post Request to obtain a Json Web Token (JWT) from an Auth Endpoint and execute some HTTP GET requests to OData-enabled endpoints.
The Solution
PowerShell comes with the built-in Invoke-RestMethod
cmdlet to call RESTful APIs:
The idea for each request is to basically define the parameters for Invoke-RestMethod
like this:
$authRequestParameters = @{
Method = "POST"
Uri = "some-url"
Body = ($someBodyForPost | ConvertTo-Json)
ContentType = "application/json"
# More Parameters here ...
}
The request to a given $AuthUrl
is going to return a JSON
object, where the token
property contains the bearer token,
that we'll need to send in the Authorization
header.
$AuthEmail = "..."
$AuthPassword = "..."
# More variables ...
$authRequestBody = @{
Email = $AuthEmail
Password = $AuthPassword
RequestedScopes = $RequestedScopes
}
$authRequestParameters = @{
Method = "POST"
Uri = $AuthUrl
Body = ($authRequestBody | ConvertTo-Json)
ContentType = "application/json"
}
# Invoke the Rest API
$authRequestResponse = Invoke-RestMethod @authRequestParameters
# Extract JWT from the JSON Response
$authToken = $authRequestResponse.token
# The Auth Header needs to be sent for any additional OData request
$authHeader = @{
Authorization = "Bearer $authToken"
}
The $authHeader
can then be passed as the Headers
Parameter to Invoke-RestMethod
. You
can also set a variable name, that Invoke-RestMethod
should write the parameter to
($statusCode
in the example).
Write-Host "[REQ]"
Write-Host "[REQ] OData Request"
Write-Host "[REQ]"
Write-Host "[REQ] Description: $Description"
Write-Host "[REQ] URL: $Endpoint"
Write-Host "[REQ] Scopes: $RequestedScopes"
Write-Host "[REQ]"
$odataRequestParameters = @{
Method = "GET"
Uri = $Endpoint
Headers = $authHeader
StatusCodeVariable = 'statusCode'
}
try {
$oDataResponse = Invoke-RestMethod @odataRequestParameters
$oDataResponseValue = $oDataResponse.value | ConvertTo-Json
Write-Host "[RES] HTTP Status: $statusCode" -ForegroundColor Green
Write-Host "[RES] Body: $oDataResponseValue" -ForegroundColor Green
} catch {
Write-Host "[ERR] Request failed with StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
}
We will then put all logic for querying the OData Endpoints into a function
Send-ODataRequest
, which can then be called with sample OData Requests. We
end up with the following PowerShell Script:
<#
.SYNOPSIS
Example Script for restricting Navigation Properties using the
ODataAuthorization library.
.DESCRIPTION
This script obtains a valid token and proceeds to perform requests to
the API.
.NOTES
File Name : ODataQueries.ps1
Author : Philipp Wagner
Prerequisite : PowerShell
Copyright 2023 - MIT License
#>
function Send-ODataRequest {
param (
[Parameter(Mandatory)]
[string]$AuthUrl,
[Parameter(Mandatory)]
[string]$AuthEmail,
[Parameter(Mandatory)]
[string]$AuthPassword,
[Parameter(Mandatory)]
[string]$Description,
[Parameter(Mandatory)]
[string]$Endpoint,
[Parameter(Mandatory)]
[string]$RequestedScopes
)
# Perform /Auth/login to obtain the JWT with Requested Scopes
$authRequestBody = @{
Email = $AuthEmail
Password = $AuthPassword
RequestedScopes = $RequestedScopes
}
$authRequestParameters = @{
Method = "POST"
Uri = $AuthUrl
Body = ($authRequestBody | ConvertTo-Json)
ContentType = "application/json"
}
# Invoke the Rest API
$authRequestResponse = Invoke-RestMethod @authRequestParameters
# Extract JWT from the JSON Response
$authToken = $authRequestResponse.token
# The Auth Header needs to be sent for any additional OData request
$authHeader = @{
Authorization = "Bearer $authToken"
}
Write-Host "[REQ]"
Write-Host "[REQ] OData Request"
Write-Host "[REQ]"
Write-Host "[REQ] Description: $Description"
Write-Host "[REQ] URL: $Endpoint"
Write-Host "[REQ] Scopes: $RequestedScopes"
Write-Host "[REQ]"
$odataRequestParameters = @{
Method = "GET"
Uri = $Endpoint
Headers = $authHeader
StatusCodeVariable = 'statusCode'
}
try {
$oDataResponse = Invoke-RestMethod @odataRequestParameters
$oDataResponseValue = $oDataResponse.value | ConvertTo-Json
Write-Host "[RES] HTTP Status: $statusCode" -ForegroundColor Green
Write-Host "[RES] Body: $oDataResponseValue" -ForegroundColor Green
} catch {
Write-Host "[ERR] Request failed with StatusCode:" $_.Exception.Response.StatusCode.value__ -ForegroundColor Red
}
}
$authUrl = "http://localhost:5124/Auth/login"
$authEmail = "admin@admin.com"
$authPassword = "123456"
$requests =
@{
AuthUrl = $authUrl
AuthEmail = $authEmail
AuthPassword = $authPassword
Description = "Get all Products without 'Address' expanded"
Endpoint = "http://localhost:5124/odata/Products"
RequestedScopes = "Products.Read Products.ReadByKey"
},
@{
AuthUrl = $authUrl
AuthEmail = $authEmail
AuthPassword = $authPassword
Description = "Get all Products with 'Address' expanded. Missing Scope: 'Products.ReadAddress'"
Endpoint = "http://localhost:5124/odata/Products?`$expand=Address"
RequestedScopes = "Products.Read Products.ReadByKey"
},
@{
AuthUrl = $authUrl
AuthEmail = $authEmail
AuthPassword = $authPassword
Description = "Get all Products with 'Address' expanded. Valid Required Scopes."
Endpoint = "http://localhost:5124/odata/Products?`$expand=Address"
RequestedScopes = "Products.Read Products.ReadByKey Products.ReadAddress"
}
foreach ( $request in $requests )
{
Send-ODataRequest @request
}
And we end up with the following output (it will be nicely colored in the Terminal):
PS C:\Users\philipp\source\repos\bytefish\ODataAuthorization\samples\JwtAuthenticationExample\PowershellScripts> .\ODataQueries.ps1 [REQ] [REQ] OData Request [REQ] [REQ] Description: Get all Products without 'Address' expanded [REQ] URL: http://localhost:5124/odata/Products [REQ] Scopes: Products.Read Products.ReadByKey [REQ] [RES] HTTP Status: 200 [RES] Body: [ { "Id": 1, "Name": "Macbook M1", "Price": 3000, "AddressId": 1 }, { "Id": 2, "Name": "Macbook M2", "Price": 3500, "AddressId": 1 }, { "Id": 3, "Name": "iPhone 14", "Price": 1400, "AddressId": 1 } ] [REQ] [REQ] OData Request [REQ] [REQ] Description: Get all Products with 'Address' expanded. Missing Scope: 'Products.ReadAddress' [REQ] URL: http://localhost:5124/odata/Products?$expand=Address [REQ] Scopes: Products.Read Products.ReadByKey [REQ] [ERR] Request failed with StatusCode: 403 [REQ] [REQ] OData Request [REQ] [REQ] Description: Get all Products with 'Address' expanded. Valid Required Scopes. [REQ] URL: http://localhost:5124/odata/Products?$expand=Address [REQ] Scopes: Products.Read Products.ReadByKey Products.ReadAddress [REQ] [RES] HTTP Status: 200 [RES] Body: [ { "Id": 1, "Name": "Macbook M1", "Price": 3000, "AddressId": 1, "Address": { "Id": 1, "Country": "USA", "City": "California" } }, { "Id": 2, "Name": "Macbook M2", "Price": 3500, "AddressId": 1, "Address": { "Id": 1, "Country": "USA", "City": "California" } }, { "Id": 3, "Name": "iPhone 14", "Price": 1400, "AddressId": 1, "Address": { "Id": 1, "Country": "USA", "City": "California" } } ]