Inovking a RESTful API with PowerShell

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"
    }
  }
]

How to contribute

One of the easiest ways to contribute is to participate in discussions. You can also contribute by submitting pull requests.

General feedback and discussions?

Do you have questions or feedback on this article? Please create an issue on the Repositories issue tracker.

Something is wrong or missing?

There may be something wrong or missing in this article. If you want to help fixing it, then please make a Pull Request to this file.