diff --git a/.github/workflows/ci-powershell.yml b/.github/workflows/ci-powershell.yml
index adc4cd1f2..900d1806b 100644
--- a/.github/workflows/ci-powershell.yml
+++ b/.github/workflows/ci-powershell.yml
@@ -51,8 +51,18 @@ jobs:
- name: Run Pester Tests
shell: powershell
run: |
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
- Invoke-Build Test
+ # Check if the runner is in debug mode
+ if ($env:RUNNER_DEBUG -eq '1') {
+ $debug = $true
+ } else {
+ $debug = $false
+ }
+
+ if ($debug) {
+ Invoke-Build Test -PesterVerbosity Diagnostic
+ } else {
+ Invoke-Build Test
+ }
- name: Build Packages
shell: powershell
diff --git a/.github/workflows/ci-pwsh7_5.yml b/.github/workflows/ci-pwsh7_5.yml
index 1bf1704ea..9b0362d73 100644
--- a/.github/workflows/ci-pwsh7_5.yml
+++ b/.github/workflows/ci-pwsh7_5.yml
@@ -72,7 +72,18 @@ jobs:
- name: Run Pester Tests
shell: pwsh
run: |
- Invoke-Build Test
+ # Check if the runner is in debug mode
+ if ($env:RUNNER_DEBUG -eq '1') {
+ $debug = $true
+ } else {
+ $debug = $false
+ }
+
+ if ($debug) {
+ Invoke-Build Test -PesterVerbosity Diagnostic
+ } else {
+ Invoke-Build Test
+ }
- name: Build Packages
shell: pwsh
diff --git a/.github/workflows/ci-pwsh_lts.yml b/.github/workflows/ci-pwsh_lts.yml
index 70ab3d0d3..de2e79856 100644
--- a/.github/workflows/ci-pwsh_lts.yml
+++ b/.github/workflows/ci-pwsh_lts.yml
@@ -71,7 +71,18 @@ jobs:
- name: Run Pester Tests
shell: pwsh
run: |
- Invoke-Build Test
+ # Check if the runner is in debug mode
+ if ($env:RUNNER_DEBUG -eq '1') {
+ $debug = $true
+ } else {
+ $debug = $false
+ }
+
+ if ($debug) {
+ Invoke-Build Test -PesterVerbosity Diagnostic
+ } else {
+ Invoke-Build Test
+ }
- name: Build Packages
shell: pwsh
diff --git a/.github/workflows/ci-pwsh_preview.yml b/.github/workflows/ci-pwsh_preview.yml
index 43198540f..60c2e9c0d 100644
--- a/.github/workflows/ci-pwsh_preview.yml
+++ b/.github/workflows/ci-pwsh_preview.yml
@@ -71,7 +71,18 @@ jobs:
- name: Run Pester Tests
shell: pwsh
run: |
- Invoke-Build Test
+ # Check if the runner is in debug mode
+ if ($env:RUNNER_DEBUG -eq '1') {
+ $debug = $true
+ } else {
+ $debug = $false
+ }
+
+ if ($debug) {
+ Invoke-Build Test -PesterVerbosity Diagnostic
+ } else {
+ Invoke-Build Test
+ }
- name: Build Packages
shell: pwsh
diff --git a/.gitignore b/.gitignore
index 6938c5f15..081dfe316 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ docs/[Ff]unctions/
examples/state.json
examples/issue-*
examples/issues/
+examples/State/
pkg/
deliverable/
.vs/
@@ -266,6 +267,13 @@ examples/PetStore/data/PetData.json
packers/choco/pode.nuspec
packers/choco/tools/ChocolateyInstall.ps1
docs/Getting-Started/Samples.md
+examples/HelloService/*_svcsettings.json
+examples/HelloService/svc_settings
# Dump Folder
Dump
+examples/certs/*-public.pem
+examples/certs/*-private.pem
+tests/certs/*
+/examples/certs
+examples/Authentication/certs/*
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b5be5ef8e..4dd3cd280 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -38,5 +38,13 @@
"javascript.format.insertSpaceBeforeFunctionParenthesis": false,
"[yaml]": {
"editor.tabSize": 2
+ },
+ "markdownlint.config": {
+ "default": true,
+ "MD045": false,
+ "MD033": false,
+ "MD026": {
+ "punctuation": ".,;:"
+ }
}
}
\ No newline at end of file
diff --git a/Pode.sln b/Pode.sln
index 66eb3805a..02438e28a 100644
--- a/Pode.sln
+++ b/Pode.sln
@@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{41F81369-868
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pode", "src\Listener\Pode.csproj", "{772D5C9F-1B25-46A7-8977-412A5F7F77D1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PodeMonitor", "src\PodeMonitor\PodeMonitor.csproj", "{A927D6A5-A2AC-471A-9ABA-45916B597EB6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/README.md b/README.md
index 3ffdf9f6f..30dcb52fe 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
-
+
[](https://raw.githubusercontent.com/Badgerati/Pode/master/LICENSE.txt)
[](https://badgerati.github.io/Pode)
@@ -53,36 +53,37 @@ Then navigate to `http://127.0.0.1:8000` in your browser.
## 🚀 Features
-* Cross-platform using PowerShell Core (with support for PS5)
-* Docker support, including images for ARM/Raspberry Pi
-* Azure Functions, AWS Lambda, and IIS support
-* OpenAPI specification version 3.0.x and 3.1.0
-* OpenAPI documentation with Swagger, Redoc, RapidDoc, StopLight, OpenAPI-Explorer and RapiPdf
-* Listen on a single or multiple IP(v4/v6) address/hostnames
-* Cross-platform support for HTTP(S), WS(S), SSE, SMTP(S), and TCP(S)
-* Host REST APIs, Web Pages, and Static Content (with caching)
-* Support for custom error pages
-* Request and Response compression using GZip/Deflate
-* Multi-thread support for incoming requests
-* Inbuilt template engine, with support for third-parties
-* Async timers for short-running repeatable processes
-* Async scheduled tasks using cron expressions for short/long-running processes
-* Supports logging to CLI, Files, and custom logic for other services like LogStash
-* Cross-state variable access across multiple runspaces
-* Restart the server via file monitoring, or defined periods/times
-* Ability to allow/deny requests from certain IP addresses and subnets
-* Basic rate limiting for IP addresses and subnets
-* Middleware and Sessions on web servers, with Flash message and CSRF support
-* Authentication on requests, such as Basic, Windows and Azure AD
-* Authorisation support on requests, using Roles, Groups, Scopes, etc.
-* Support for dynamically building Routes from Functions and Modules
-* Generate/bind self-signed certificates
-* Secret management support to load secrets from vaults
-* Support for File Watchers
-* In-memory caching, with optional support for external providers (such as Redis)
-* (Windows) Open the hosted server as a desktop application
-* FileBrowsing support
-* Localization (i18n) in Arabic, German, Spanish, France, Italian, Japanese, Korean, Polish, Portuguese, and Chinese
+- ✅ Cross-platform using PowerShell Core (with support for PS5)
+- ✅ Docker support, including images for ARM/Raspberry Pi
+- ✅ Azure Functions, AWS Lambda, and IIS support
+- ✅ OpenAPI specification version 3.0.x and 3.1.0
+- ✅ OpenAPI documentation with Swagger, Redoc, RapidDoc, StopLight, OpenAPI-Explorer and RapiPdf
+- ✅ Listen on a single or multiple IP(v4/v6) addresses/hostnames
+- ✅ Cross-platform support for HTTP(S), WS(S), SSE, SMTP(S), and TCP(S)
+- ✅ Host REST APIs, Web Pages, and Static Content (with caching)
+- ✅ Support for custom error pages
+- ✅ Request and Response compression using GZip/Deflate
+- ✅ Multi-thread support for incoming requests
+- ✅ Inbuilt template engine, with support for third-parties
+- ✅ Async timers for short-running repeatable processes
+- ✅ Async scheduled tasks using cron expressions for short/long-running processes
+- ✅ Supports logging to CLI, Files, and custom logic for other services like LogStash
+- ✅ Cross-state variable access across multiple runspaces
+- ✅ Restart the server via file monitoring, or defined periods/times
+- ✅ Ability to allow/deny requests from certain IP addresses and subnets
+- ✅ Basic rate limiting for IP addresses and subnets
+- ✅ Middleware and Sessions on web servers, with Flash message and CSRF support
+- ✅ Authentication on requests, such as Basic, Windows and Azure AD
+- ✅ Authorisation support on requests, using Roles, Groups, Scopes, etc.
+- ✅ Enhanced authentication support, including Basic, Bearer (with JWT), Certificate, Digest, Form, OAuth2, and ApiKey (with JWT).
+- ✅ Support for dynamically building Routes from Functions and Modules
+- ✅ Generate/bind self-signed certificates
+- ✅ Secret management support to load secrets from vaults
+- ✅ Support for File Watchers
+- ✅ In-memory caching, with optional support for external providers (such as Redis)
+- ✅ (Windows) Open the hosted server as a desktop application
+- ✅ FileBrowsing support
+- ✅ Localization (i18n) in Arabic, German, Spanish, France, Italian, Japanese, Korean, Polish, Portuguese,Dutch and Chinese
## 📦 Install
diff --git a/Version.json b/Version.json
new file mode 100644
index 000000000..13efd672d
--- /dev/null
+++ b/Version.json
@@ -0,0 +1,4 @@
+{
+ "Version": "2.13.0",
+ "Prerelease": "alpha.3"
+}
\ No newline at end of file
diff --git a/docs/Getting-Started/Migrating/0X-to-1X.md b/docs/Getting-Started/Migrating/0X-to-1X.md
index 8760d6d78..be6423609 100644
--- a/docs/Getting-Started/Migrating/0X-to-1X.md
+++ b/docs/Getting-Started/Migrating/0X-to-1X.md
@@ -154,7 +154,7 @@ Request and Error logging are inbuilt logging types that can be enabled using [`
| [`Disable-PodeRequestLogging`](../../../Functions/Logging/Disable-PodeRequestLogging) |
| [`Disable-PodeErrorLogging`](../../../Functions/Logging/Disable-PodeErrorLogging) |
| [`Remove-PodeLogger`](../../../Functions/Logging/Remove-PodeLogger) |
-| [`Clear-PodeLoggers`](../../../Functions/Logging/Clear-PodeLoggers) |
+| [`Clear-PodeLogger`](../../../Functions/Logging/Clear-PodeLogger) |
### Writing Logs
diff --git a/docs/Hosting/PortsBelow1024.md b/docs/Hosting/PortsBelow1024.md
new file mode 100644
index 000000000..c5a1c31fa
--- /dev/null
+++ b/docs/Hosting/PortsBelow1024.md
@@ -0,0 +1,55 @@
+# Using Ports Below 1024
+
+#### Introduction
+
+Traditionally in Linux, binding to ports below 1024 requires root privileges. This is a security measure, as these low-numbered ports are considered privileged. However, running applications as the root user poses significant security risks. This article explores methods to use these privileged ports with PowerShell (`pwsh`) in Linux, without running it as the root user.
+There are different methods to achieve the goals.
+Reverse Proxy is the right approach for a production environment, primarily if the server is connected directly to the internet.
+The other solutions are reasonable after an in-depth risk analysis.
+
+#### Using a Reverse Proxy
+
+A reverse proxy like Nginx can listen on the privileged port and forward requests to your application running on an unprivileged port.
+
+**Configuration:**
+
+* Configure Nginx to listen on port 443 and forward requests to the port where your PowerShell script is listening.
+* This method is widely used in web applications for its additional benefits like load balancing and SSL termination.
+
+#### iptables Redirection
+
+Using iptables, you can redirect traffic from a privileged port to a higher, unprivileged port.
+
+**Implementation:**
+
+* Set up an iptables rule to redirect traffic from, say, port 443 to a higher port where your PowerShell script is listening.
+* `sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080`
+
+**Benefits:**
+
+* This approach doesn't require changing the privileges of the PowerShell executable or script.
+
+#### Using `setcap` Command
+
+The `setcap` utility can grant specific capabilities to an executable, like `pwsh`, enabling it to bind to privileged ports.
+
+**How it Works:**
+
+* Run `sudo setcap 'cap_net_bind_service=+ep' $(which pwsh)`. This command sets the `CAP_NET_BIND_SERVICE` capability on the PowerShell executable, allowing it to bind to any port below 1024.
+
+**Security Consideration:**
+
+* This method enhances security by avoiding running PowerShell as root, but it still grants significant privileges to the PowerShell process.
+
+#### Utilizing Authbind
+
+Authbind is a tool that allows a non-root user to bind to privileged ports.
+
+**Setup:**
+
+* Install Authbind, configure it to allow the desired port, and then start your PowerShell script using Authbind.
+* For instance, `authbind --deep pwsh yourscript.ps1` allows the script to bind to a privileged port.
+
+**Advantages:**
+
+* It provides a finer-grained control over port access and doesn't require setting special capabilities on the PowerShell binary itself.
diff --git a/docs/Hosting/RunAsService.md b/docs/Hosting/RunAsService.md
index 2872e7d0f..734a7fbb8 100644
--- a/docs/Hosting/RunAsService.md
+++ b/docs/Hosting/RunAsService.md
@@ -1,141 +1,275 @@
-# Service
+# Using Pode as a Service
-Rather than having to manually invoke your Pode server script each time, it's best if you can have it start automatically when your computer/server starts. Below you'll see how to set your script to run as either a Windows or a Linux service.
+Pode provides built-in functions to easily manage services across platforms (Windows, Linux, macOS). These functions allow you to register, start, stop, suspend, resume, query, and unregister Pode services in a cross-platform way.
-!!! Note
- When running Pode as a service, it is recommended to use `Start-PodeServer` with the `-Daemon` parameter. This ensures the server operates in a detached and background-friendly mode suitable for long-running processes. The `-Daemon` parameter optimizes Pode's behavior for service execution by suppressing interactive output and allowing the process to run seamlessly in the background.
+---
-## Windows
+## Registering a Service
-To run your Pode server as a Windows service, we recommend using the [`NSSM`](https://nssm.cc) tool. To install on Windows you can use Chocolatey:
+The `Register-PodeService` function creates the necessary service files and configurations for your system.
+
+#### Example:
```powershell
-choco install nssm -y
+Register-PodeService -Name "HelloService" -Description "Example Pode Service" -ParameterString "-Verbose" -Start
```
-Once installed, you'll need to set the location of the `pwsh` or `powershell` executables as a variable:
+This registers a service named "HelloService" and starts it immediately after registration. The service runs your Pode script with the specified parameters.
-```powershell
-$exe = (Get-Command pwsh.exe).Source
+### `Register-PodeService` Parameters
-# or
+The `Register-PodeService` function offers several parameters to customize your service registration:
-$exe = (Get-Command powershell.exe).Source
-```
+- **`-Name`** *(string)*:
+ The name of the service to register.
+ **Mandatory**.
+
+- **`-Description`** *(string)*:
+ A brief description of the service. Defaults to `"This is a Pode service."`.
+
+- **`-DisplayName`** *(string)*:
+ The display name for the service (Windows only). Defaults to `"Pode Service($Name)"`.
+
+- **`-StartupType`** *(string)*:
+ Specifies the startup type of the service (`'Automatic'` or `'Manual'`). Defaults to `'Automatic'`.
+
+- **`-ParameterString`** *(string)*:
+ Additional parameters to pass to the worker script when the service is run. Defaults to an empty string.
+
+- **`-LogServicePodeHost`** *(switch)*:
+ Enables logging for the Pode service host.
+
+- **`-ShutdownWaitTimeMs`** *(int)*:
+ Maximum time in milliseconds to wait for the service to shut down gracefully before forcing termination. Defaults to `30,000 ms`.
+
+- **`-StartMaxRetryCount`** *(int)*:
+ Maximum number of retries to start the PowerShell process before giving up. Defaults to `3`.
+
+- **`-StartRetryDelayMs`** *(int)*:
+ Delay in milliseconds between retry attempts to start the PowerShell process. Defaults to `5,000 ms`.
+
+- **`-WindowsUser`** *(string)*:
+ Specifies the username under which the service will run. Defaults to the current user (Windows only).
+
+- **`-LinuxUser`** *(string)*:
+ Specifies the username under which the service will run. Defaults to the current user (Linux Only).
+
+- **`-Agent`** *(switch)*:
+ Create an Agent instead of a Daemon in MacOS (MacOS Only).
-Next, define the name of the Windows service; as well as the full file path to your Pode server script, and the arguments to be supplied to PowerShell:
+- **`-Start`** *(switch)*:
+ Starts the service immediately after registration.
+
+- **`-Password`** *(securestring)*:
+ A secure password for the service account (Windows only). If omitted, the service account will be `'NT AUTHORITY\SYSTEM'`.
+
+- **`-SecurityDescriptorSddl`** *(string)*:
+ A security descriptor in SDDL format specifying the permissions for the service (Windows only).
+
+- **`-SettingsPath`** *(string)*:
+ Directory to store the service configuration file (`_svcsettings.json`). Defaults to a directory under the script path.
+
+- **`-LogPath`** *(string)*:
+ Path for the service log files. Defaults to a directory under the script path.
+
+---
+
+## Starting a Service
+
+You can start a registered service using the `Start-PodeService` function.
+
+#### Example:
```powershell
-$name = 'Pode Web Server'
-$file = 'C:\Pode\Server.ps1'
-$arg = "-ExecutionPolicy Bypass -NoProfile -Command `"$($file)`""
+Start-PodeService -Name "HelloService"
```
-Finally, install and start the service:
+This returns `$true` if the service starts successfully, `$false` otherwise.
+
+---
+
+## Stopping a Service
+
+To stop a running service, use the `Stop-PodeService` function.
+
+#### Example:
```powershell
-nssm install $name $exe $arg
-nssm start $name
+Stop-PodeService -Name "HelloService"
```
-!!! info
- You can now navigate to your server, ie: `http://localhost:8080`.
+This returns `$true` if the service stops successfully, `$false` otherwise.
+
+---
+
+## Suspending a Service
-To stop (or remove) the service afterwards, you can use the following:
+Suspend a running service (Windows only) with the `Suspend-PodeService` function.
+
+#### Example:
```powershell
-nssm stop $name
-nssm remove $name confirm
+Suspend-PodeService -Name "HelloService"
```
-## Linux
+This pauses the service, returning `$true` if successful.
+
+---
+
+## Resuming a Service
+
+Resume a suspended service (Windows only) using the `Resume-PodeService` function.
-To run your Pode server as a Linux service you just need to create a `.service` file at `/etc/systemd/system`. The following is example content for an example `pode-server.service` file, which run PowerShell Core (`pwsh`), as well as you script:
+#### Example:
-```bash
-sudo vim /etc/systemd/system/pode-server.service
+```powershell
+Resume-PodeService -Name "HelloService"
```
-```bash
-[Unit]
-Description=Pode Web Server
-After=network.target
+This resumes the service, returning `$true` if successful.
+
+---
+
+## Querying a Service
-[Service]
-ExecStart=/usr/bin/pwsh -c /usr/src/pode/server.ps1 -nop -ep Bypass
-Restart=always
+To check the status of a service, use the `Get-PodeService` function.
-[Install]
-WantedBy=multi-user.target
-Alias=pode-server.service
+#### Example:
+
+```powershell
+Get-PodeService -Name "HelloService"
```
-Finally, start the service:
+This returns a hashtable with the service details:
+
+```powershell
+Name Value
+---- -----
+Status Running
+Pid 17576
+Name HelloService
+Sudo True
+```
+
+---
+
+## Restarting a Service
+
+Restart a running service using the `Restart-PodeService` function.
+
+#### Example:
```powershell
-sudo systemctl start pode-server
+Restart-PodeService -Name "HelloService"
```
-!!! info
- You can now navigate to your server, ie: `http://localhost:8080`.
+This stops and starts the service, returning `$true` if successful.
+
+---
+
+## Unregistering a Service
-To stop the service afterwards, you can use the following:
+When you no longer need a service, unregister it with the `Unregister-PodeService` function.
+
+#### Example:
```powershell
-sudo systemctl stop pode-server
+Unregister-PodeService -Name "HelloService" -Force
```
-### Using Ports Below 1024
-#### Introduction
+This forcefully stops and removes the service, returning `$true` if successful.
+
+---
+
+## Alternative Methods for Windows and Linux
+
+If the Pode functions are unavailable or you prefer manual management, you can use traditional methods to configure Pode as a service.
+
+### Windows (NSSM)
+
+To use NSSM for Pode as a Windows service:
+
+1. Install NSSM using Chocolatey:
+
+ ```powershell
+ choco install nssm -y
+ ```
+
+2. Configure the service:
+
+ ```powershell
+ $exe = (Get-Command pwsh.exe).Source
+ $name = 'Pode Web Server'
+ $file = 'C:\Pode\Server.ps1'
+ $arg = "-ExecutionPolicy Bypass -NoProfile -Command `"$($file)`""
+ nssm install $name $exe $arg
+ nssm start $name
+ ```
-Traditionally in Linux, binding to ports below 1024 requires root privileges. This is a security measure, as these low-numbered ports are considered privileged. However, running applications as the root user poses significant security risks. This article explores methods to use these privileged ports with PowerShell (`pwsh`) in Linux, without running it as the root user.
-There are different methods to achieve the goals.
-Reverse Proxy is the right approach for a production environment, primarily if the server is connected directly to the internet.
-The other solutions are reasonable after an in-depth risk analysis.
+3. Stop or remove the service:
-#### Using a Reverse Proxy
+ ```powershell
+ nssm stop $name
+ nssm remove $name confirm
+ ```
-A reverse proxy like Nginx can listen on the privileged port and forward requests to your application running on an unprivileged port.
+---
-**Configuration:**
+### Linux (systemd)
-* Configure Nginx to listen on port 443 and forward requests to the port where your PowerShell script is listening.
-* This method is widely used in web applications for its additional benefits like load balancing and SSL termination.
+To configure Pode as a Linux service:
-#### iptables Redirection
+1. Create a service file:
-Using iptables, you can redirect traffic from a privileged port to a higher, unprivileged port.
+ ```bash
+ sudo vim /etc/systemd/system/pode-server.service
+ ```
-**Implementation:**
+2. Add the following configuration:
-* Set up an iptables rule to redirect traffic from, say, port 443 to a higher port where your PowerShell script is listening.
-* `sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080`
+ ```bash
+ [Unit]
+ Description=Pode Web Server
+ After=network.target
-**Benefits:**
+ [Service]
+ ExecStart=/usr/bin/pwsh -c /usr/src/pode/server.ps1 -nop -ep Bypass
+ Restart=always
-* This approach doesn't require changing the privileges of the PowerShell executable or script.
+ [Install]
+ WantedBy=multi-user.target
+ Alias=pode-server.service
+ ```
-#### Using `setcap` Command
+3. Start and stop the service:
-The `setcap` utility can grant specific capabilities to an executable, like `pwsh`, enabling it to bind to privileged ports.
+ ```bash
+ sudo systemctl start pode-server
+ sudo systemctl stop pode-server
+ ```
-**How it Works:**
+---
-* Run `sudo setcap 'cap_net_bind_service=+ep' $(which pwsh)`. This command sets the `CAP_NET_BIND_SERVICE` capability on the PowerShell executable, allowing it to bind to any port below 1024.
+## Using Ports Below 1024
-**Security Consideration:**
+For privileged ports, consider:
-* This method enhances security by avoiding running PowerShell as root, but it still grants significant privileges to the PowerShell process.
+1. **Reverse Proxy:** Use Nginx to forward traffic from port 443 to an unprivileged port.
-#### Utilizing Authbind
+2. **iptables Redirection:** Redirect port 443 to an unprivileged port:
-Authbind is a tool that allows a non-root user to bind to privileged ports.
+ ```bash
+ sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
+ ```
-**Setup:**
+3. **setcap Command:** Grant PowerShell permission to bind privileged ports:
-* Install Authbind, configure it to allow the desired port, and then start your PowerShell script using Authbind.
-* For instance, `authbind --deep pwsh yourscript.ps1` allows the script to bind to a privileged port.
+ ```bash
+ sudo setcap 'cap_net_bind_service=+ep' $(which pwsh)
+ ```
-**Advantages:**
+4. **Authbind:** Configure Authbind to allow binding to privileged ports:
-* It provides a finer-grained control over port access and doesn't require setting special capabilities on the PowerShell binary itself.
+ ```bash
+ authbind --deep pwsh yourscript.ps1
+ ```
diff --git a/docs/Tutorials/Authentication/Methods/ApiKey.md b/docs/Tutorials/Authentication/Methods/ApiKey.md
index 2167867cf..6d754daa0 100644
--- a/docs/Tutorials/Authentication/Methods/ApiKey.md
+++ b/docs/Tutorials/Authentication/Methods/ApiKey.md
@@ -26,13 +26,13 @@ Start-PodeServer {
}
```
-By default, Pode will look for an `X-API-KEY` header in the request. You can change this to Cookie or Query by using the `-Location` parameter. To change the name of what Pode looks for, you can use `-LocationName`.
+By default, Pode will look for an `X-API-KEY` header in the request. You can change this to Cookie or Query by using the `-ApiKeyLocation` parameter. To change the name of what Pode looks for, you can use `-LocationName`.
For example, to look for an `appId` query value:
```powershell
Start-PodeServer {
- New-PodeAuthScheme -ApiKey -Location Query -LocationName 'appId' | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ New-PodeAuthScheme -ApiKey -ApiKeyLocation Query -LocationName 'appId' | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
param($key)
# check if the key is valid, and get user
diff --git a/docs/Tutorials/Authentication/Methods/Bearer.md b/docs/Tutorials/Authentication/Methods/Bearer.md
index 77f92abbe..be388f8ae 100644
--- a/docs/Tutorials/Authentication/Methods/Bearer.md
+++ b/docs/Tutorials/Authentication/Methods/Bearer.md
@@ -6,13 +6,30 @@ Bearer authentication lets you authenticate a user based on a token, with option
Authorization: Bearer
```
+!!! note
+ **`New-PodeAuthScheme -Bearer` is deprecated.** Please use **`New-PodeAuthBearerScheme`**.
+
## Setup
-To start using Bearer authentication in Pode you can use `New-PodeAuthScheme -Bearer`, and then pipe the returned object into [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth). The parameter supplied to the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function's ScriptBlock is the `$token` from the Authorization token:
+To start using Bearer authentication in Pode, call **`New-PodeAuthBearerScheme`**, and then pipe the returned object into [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth). The parameter supplied to the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function's **ScriptBlock** is the `$token` from the Authorization header.
+
+```powershell
+Start-PodeServer {
+ New-PodeAuthBearerScheme | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # check if the token is valid, and get user
+
+ return @{ User = $user }
+ }
+}
+```
+
+By default, Pode will look for a token in the **`Authorization`** header, verifying that it starts with the **`Bearer`** tag. You can customize this tag via **`-HeaderTag`**. You can also change the token extraction location to the **query string** using **`-Location Query`**. For the **`-Location query`** the standard tag is **`access_token`**:
```powershell
Start-PodeServer {
- New-PodeAuthScheme -Bearer | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ New-PodeAuthBearerScheme -Location Query | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
param($token)
# check if the token is valid, and get user
@@ -22,13 +39,129 @@ Start-PodeServer {
}
```
-By default, Pode will check if the request's header contains an `Authorization` key, and whether the value of that key starts with `Bearer` tag. The `New-PodeAuthScheme -Bearer` function can be supplied parameters to customise the tag using `-HeaderTag`.
+**Note:** Per [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750), using the Authorization header is recommended for sending bearer tokens. Query parameters should only be used when headers are not feasible, as query strings may be logged in URLs, potentially exposing sensitive information.
+
+## JWT Support
+
+`New-PodeAuthBearerScheme` supports **JWT authentication** with various security levels and algorithms. Set **`-AsJWT`** to enable JWT validation. Depending on the chosen algorithm, you can specify:
+
+- **HMAC**-based secret keys (`-Secret`)
+- **Certificate**-based parameters (`-Certificate`, `-CertificateThumbprint`, `-CertificateName`, `-X509Certificate`, `-SelfSigned`)
+- The **RSA padding scheme** (`-RsaPaddingScheme`)
+- The **JWT verification mode** (`-JwtVerificationMode`)
+
+### JwtVerificationMode
+
+Defines how aggressively JWT claims should be checked:
+
+- **Strict**: Requires all standard claims:
+ - `exp` (Expiration Time)
+ - `nbf` (Not Before)
+ - `iat` (Issued At)
+ - `iss` (Issuer)
+ - `aud` (Audience)
+ - `jti` (JWT ID)
+
+- **Moderate**: Allows missing `iss` (Issuer) and `aud` (Audience) but still checks expiration (`exp`).
+- **Lenient**: Ignores missing `iss` and `aud`, only verifies expiration (`exp`), not-before (`nbf`), and issued-at (`iat`).
+
+### HMAC Example
+
+Here’s an example using **HMAC** (HS256) JWT validation:
+
+```powershell
+Start-PodeServer {
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -Algorithm 'HS256' `
+ -Secret (ConvertTo-SecureString "MySecretKey" -AsPlainText -Force) `
+ -JwtVerificationMode 'Strict' |
+ Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # validate and decode JWT, then extract user details
+
+ return @{ User = $user }
+ }
+}
+```
+
+### Certificate-Based Example
+
+For **RSA/ECDSA** JWT validation, you can specify a **certificate** or **thumbprint** instead of a secret key. Pode will infer the appropriate signing algorithms (e.g., RS256, ES256) from the certificate. For instance, using a local **PFX** certificate file:
+
+```powershell
+Start-PodeServer {
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -Algorithm 'RS256' `
+ -Certificate "C:\path\to\cert.pfx" `
+ -CertificatePassword (ConvertTo-SecureString "CertPass" -AsPlainText -Force) `
+ -JwtVerificationMode 'Moderate' |
+ Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # validate JWT and extract user
+
+ return @{ User = $user }
+ }
+}
+```
+
+### Self-Signed Certificate Example
+
+For testing purposes or internal deployments, you can use the **`-SelfSigned`** parameter, which automatically generates an **ephemeral self-signed ECDSA certificate** (ES384) for JWT signing. This avoids the need to manually create and manage certificate files.
+
+#### Example:
+
+```powershell
+Start-PodeServer {
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -SelfSigned |
+ Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # validate JWT and extract user
+
+ return @{ User = $user }
+ }
+}
+```
+
+This is equivalent to manually generating a self-signed ECDSA certificate and passing it via `-X509Certificate`:
+
+```powershell
+Start-PodeServer {
+ $x509Certificate = New-PodeSelfSignedCertificate `
+ -CommonName 'JWT Signing Certificate' `
+ -KeyType ECDSA `
+ -KeyLength 384 `
+ -CertificatePurpose CodeSigning `
+ -Ephemeral
+
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -X509Certificate $x509Certificate |
+ Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # validate JWT and extract user
+
+ return @{ User = $user }
+ }
+}
+```
+
+Using `-SelfSigned` simplifies setup by automatically handling certificate creation and disposal, making it a convenient choice for local development and testing scenarios.
+
+## Scope Validation
-You can also optionally return a `Scope` property alongside the `User`. If you specify any scopes with [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme) then it will be validated in the Bearer's post validator - a 403 will be returned if the scope is invalid.
+You can optionally include `-Scope` when creating the scheme. Pode will validate any returned `Scope` from your auth **ScriptBlock** against the scheme’s required scopes. If the scope is invalid, Pode will return 403 (Forbidden).
```powershell
Start-PodeServer {
- New-PodeAuthScheme -Bearer -Scope 'write' | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ New-PodeAuthBearerScheme -Scope 'write' | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
param($token)
# check if the token is valid, and get user
@@ -38,11 +171,13 @@ Start-PodeServer {
}
```
+
+
## Middleware
-Once configured you can start using Bearer authentication to validate incoming requests. You can either configure the validation to happen on every Route as global Middleware, or as custom Route Middleware.
+Once configured, you can instruct Pode to validate every request with Bearer authentication by using **Global Middleware**, or you can require it on individual Routes.
-The following will use Bearer authentication to validate every request on every Route:
+**Global Middleware Example** – Validate **every** incoming request:
```powershell
Start-PodeServer {
@@ -50,7 +185,7 @@ Start-PodeServer {
}
```
-Whereas the following example will use Bearer authentication to only validate requests on specific a Route:
+**Route-Specific Example** – Validate only on a certain Route:
```powershell
Start-PodeServer {
@@ -60,46 +195,42 @@ Start-PodeServer {
}
```
-## JWT
-
-You can supply a JWT using Bearer authentication, for more details [see here](../JWT).
-
## Full Example
-The following full example of Bearer authentication will setup and configure authentication, validate the token, and then validate on a specific Route:
+Below is a complete example demonstrating Bearer authentication with JWT. It configures a server, sets up JWT validation with a shared secret, and validates requests on one route (`/cpu`) while leaving another (`/memory`) open:
```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
- # setup bearer authentication to validate a user
- New-PodeAuthScheme -Bearer | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
- param($token)
-
- # here you'd check a real storage, this is just for example
- if ($token -eq 'test-token') {
- return @{
- User = @{
- 'ID' ='M0R7Y302'
- 'Name' = 'Morty'
- 'Type' = 'Human'
+ # Setup Bearer authentication to validate a user via JWT
+ New-PodeAuthBearerScheme -AsJWT -Algorithm 'HS256' -Secret (ConvertTo-SecureString "MySecretKey" -AsPlainText -Force) -JwtVerificationMode 'Lenient' |
+ Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ param($token)
+
+ # Example: in real usage, you would decode/verify the JWT fully
+ if ($token -eq 'test-token') {
+ return @{
+ User = @{
+ 'ID' = 'M0R7Y302'
+ 'Name' = 'Morty'
+ 'Type' = 'Human'
+ }
+ # Scope = 'read'
}
- # Scope = 'read'
}
- }
- # authentication failed
- return $null
- }
+ # authentication failed
+ return $null
+ }
- # check the request on this route against the authentication
+ # Validate against the authentication on this route
Add-PodeRoute -Method Get -Path '/cpu' -Authentication 'Authenticate' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'cpu' = 82 }
}
- # this route will not be validated against the authentication
+ # Open route, no auth required
Add-PodeRoute -Method Get -Path '/memory' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'memory' = 14 }
}
}
-```
diff --git a/docs/Tutorials/Authentication/Methods/Digest.md b/docs/Tutorials/Authentication/Methods/Digest.md
index 95e1e9570..c9cfca84e 100644
--- a/docs/Tutorials/Authentication/Methods/Digest.md
+++ b/docs/Tutorials/Authentication/Methods/Digest.md
@@ -1,14 +1,16 @@
# Digest
-Digest authentication lets you authenticate a user without actually sending the password to the server. Instead the a request is made to the server, and a challenge issued back for credentials. The authentication is then done by comparing hashes generated by the client and server using the user's password as a secret key.
+Digest authentication allows secure user authentication without sending the password to the server. Instead, the client receives a challenge from the server and responds with a hash-based authentication response. The server then verifies the hash using the stored password as a secret key.
+
+**Pode's Digest Authentication is compliant with [RFC 7616](https://datatracker.ietf.org/doc/html/rfc7616)**, ensuring compatibility with standard authentication mechanisms.
## Setup
-To setup and start using Digest authentication in Pode you use the `New-PodeAuthScheme -Digest` function, and then pipe this into the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function. The parameters supplied to the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function's ScriptBlock are the `$username`, and a HashTable containing the parameters from the Authorization header:
+To configure Digest authentication in Pode, use the `New-PodeAuthScheme -Digest` function and pass it to [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth). The parameters supplied to the `Add-PodeAuth` function's ScriptBlock include the `$username` and a hashtable containing the authentication parameters extracted from the `Authorization` header:
```powershell
Start-PodeServer {
- New-PodeAuthScheme -Digest | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ New-PodeAuthScheme -Digest -Algorithm "SHA-256" -QualityOfProtection "auth-int" | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
param($username, $params)
# check if the user is valid
@@ -18,28 +20,70 @@ Start-PodeServer {
}
```
-Unlike other forms of authentication where you only need return the User on success. Digest requires you to also return the Password of the user as a separate property. This password is what is used as the secret key to generate the client's response hash, and allows the server to re-generate the hash for validation. (Not returning the password will result in an HTTP 401 challenge response).
+Unlike other authentication methods, where only a user object is returned on success, Digest authentication **requires returning the password** (or hash) as a separate property. The password acts as the secret key to regenerate the client’s hash response for verification. Not returning the password results in an HTTP `401 Unauthorized` challenge response.
+
+### RFC 7616 Compliance
+
+Pode’s Digest authentication implementation adheres to **RFC 7616**, ensuring:
+
+- Use of **nonce-based challenge-response authentication**
+- Support for **multiple hashing algorithms** beyond MD5
+- Support for **Quality of Protection (QoP)**, including `auth` and `auth-int`
+- Correct formatting of **WWW-Authenticate** headers on authentication failure
+
+!!! note
+ SHA-384 is **not** part of RFC 7616 but has been added for consistency with other modern cryptographic algorithms and to provide additional security options.
+
+### Supported Algorithms
+
+Pode now supports multiple algorithms for Digest authentication. The `-Algorithm` parameter allows selecting one or more of the following:
+
+- `MD5`
+- `SHA-1`
+- `SHA-256`
+- `SHA-384`
+- `SHA-512`
+- `SHA-512/256`
+
+Pode automatically includes all supported algorithms in the `WWW-Authenticate` challenge header, allowing clients to select the strongest available option.
+
+### Quality of Protection (QoP)
+
+The `-QualityOfProtection` parameter (`-qop`) allows choosing between:
+
+- `"auth"` (authentication only)
+- `"auth-int"` (authentication with message integrity protection)
+
+If `auth-int` is used, the client includes a hash of the request body in the authentication response, ensuring the request content has not been altered.
-By default, Pode will check if the Request's header contains an `Authorization` key, and whether the value of that key starts with `Digest` tag. The `New-PodeAuthScheme -Digest` function can be supplied parameters to customise the tag using `-HeaderTag`. Pode will also gather the rest of the parameters in the header such as the Nonce, NonceCount, etc. An HTTP 401 challenge will be sent back if the Authorization header is invalid.
+## Handling Authentication Requests
-The HashTable of parameters sent to the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function's ScriptBlock are the following:
+By default, Pode checks if the request contains an `Authorization` header with the `Digest` scheme. The `New-PodeAuthScheme -Digest` function can be customized using the `-HeaderTag` parameter to modify the tag used in the request header. Pode also extracts all required parameters from the header, including the nonce, nonce count, and QoP options.
-| Parameter | Description |
-| --------- | ----------- |
-| cnonce | A nonce value generated by the client |
-| nc | The count of time the client has used the server nonce |
-| nonce | A nonce value generated by the server |
-| qop | Fixed to 'auth' |
-| realm | The realm description from the server's HTTP 401 challenge |
-| response | The generated hash value of all these parameters from the client |
-| uri | The URI path that needs authentication |
-| username | The username of the user that needs authenticating |
+If the `Authorization` header is missing or invalid, Pode returns an HTTP `401 Unauthorized` response with a `WWW-Authenticate` challenge.
+
+### Digest Authentication Parameters
+
+The hashtable of parameters passed to the `Add-PodeAuth` function’s ScriptBlock includes the following:
+
+| Parameter | Description |
+|------------|--------------|
+| `cnonce` | A nonce value generated by the client. |
+| `nc` | The count of times the client has used the server nonce. |
+| `nonce` | A nonce value generated by the server. |
+| `qop` | The quality of protection requested (`auth` or `auth-int`). |
+| `realm` | The authentication realm from the server's challenge. |
+| `response` | The hash generated by the client for authentication. |
+| `uri` | The URI path that requires authentication. |
+| `username` | The username provided for authentication. |
## Middleware
-Once configured you can start using Digest authentication to validate incoming Requests. You can either configure the validation to happen on every Route as global Middleware, or as custom Route Middleware.
+Digest authentication can be applied globally to all requests using `Add-PodeAuthMiddleware` or to specific routes via the `-Authentication` parameter.
-The following will use Digest authentication to validate every request on every Route:
+### Global Middleware
+
+To apply Digest authentication globally to all routes:
```powershell
Start-PodeServer {
@@ -47,7 +91,9 @@ Start-PodeServer {
}
```
-Whereas the following example will use Digest authentication to only validate requests on specific a Route:
+### Per-Route Middleware
+
+To enforce Digest authentication only on specific routes:
```powershell
Start-PodeServer {
@@ -59,40 +105,116 @@ Start-PodeServer {
## Full Example
-The following full example of Digest authentication will setup and configure authentication, validate that a user's username is valid, and then validate on a specific Route:
+The following example sets up Digest authentication with SHA-256 and `auth-int`, validates a user, and applies authentication to a specific route:
```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
- # setup digest authentication to validate a user
- New-PodeAuthScheme -Digest | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
+ # Setup Digest authentication with SHA-256 and auth-int
+ New-PodeAuthScheme -Digest -Algorithm "SHA-256" -QualityOfProtection "auth-int" | Add-PodeAuth -Name 'Authenticate' -Sessionless -ScriptBlock {
param($username, $params)
- # here you'd check a real user storage, this is just for example
+ # Example user validation
if ($username -eq 'morty') {
return @{
User = @{
- 'ID' ='M0R7Y302'
- 'Name' = 'Morty';
- 'Type' = 'Human';
+ 'ID' = 'M0R7Y302'
+ 'Name' = 'Morty'
+ 'Type' = 'Human'
}
Password = 'pickle'
}
}
- # authentication failed
+ # Authentication failed
return $null
}
- # check the request on this route against the authentication
+ # Protect the /cpu route with Digest authentication
Add-PodeRoute -Method Get -Path '/cpu' -Authentication 'Authenticate' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'cpu' = 82 }
}
- # this route will not be validated against the authentication
+ # The /memory route is accessible without authentication
Add-PodeRoute -Method Get -Path '/memory' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'memory' = 14 }
}
}
```
+
+### **Windows-Specific Limitations and the Pode Client Module**
+
+Windows' built-in Digest authentication has several **critical limitations** that restrict its compatibility with modern security practices:
+
+- **Limited to MD5:** Windows does not support stronger hashing algorithms like SHA-256 or SHA-512.
+- **No Support for `auth-int`:** Integrity protection (`auth-int`) is not available, making it less secure.
+- **Fails with Multiple Algorithms:** If the `WWW-Authenticate` header lists multiple algorithms, Windows' built-in implementation fails to negotiate properly.
+- **Lack of Algorithm Negotiation:** Windows cannot automatically select the strongest supported algorithm from a list.
+
+### **Overcoming Windows Limitations with the Pode Client Module**
+
+To bypass these **Windows client limitations**, Pode provides a custom **client module** that supports full RFC 7616-compliant Digest authentication. This module allows PowerShell scripts to authenticate using modern algorithms, multiple QoP modes, and cross-platform compatibility.
+
+The **client module** is available under:
+
+```powershell
+Import-Module ./examples/Authentication/Modules/Invoke-Digest.psm1
+```
+
+By using this module, you can perform **secure Digest authentication** in PowerShell, even on Windows, without being restricted to **MD5-only authentication**.
+
+The module includes the following functions:
+
+### **Invoke-WebRequestDigest**
+
+A replacement for `Invoke-WebRequest` that supports Digest authentication.
+
+#### **Example Usage**
+
+```powershell
+Import-Module './examples/Authentication/Modules/Invoke-Digest.psm1'
+
+# Define the URI and credentials
+$uri = 'http://localhost:8081/users'
+$username = 'morty'
+$password = 'pickle'
+
+# Convert the password to a SecureString and create a credential object
+$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
+$credential = [System.Management.Automation.PSCredential]::new($username, $securePassword)
+
+# Make a GET request using Digest authentication
+$response = Invoke-WebRequestDigest -Uri $uri -Method 'GET' -Credential $credential
+
+# Display response headers and content
+$response.Headers | Format-List
+Write-Output $response.Content
+```
+
+---
+
+### **Invoke-RestMethodDigest**
+
+A replacement for `Invoke-RestMethod` that supports Digest authentication.
+
+#### **Example Usage**
+
+```powershell
+Import-Module './examples/Authentication/Modules/Invoke-Digest.psm1'
+
+# Define the URI and credentials
+$uri = 'http://localhost:8081/users'
+$username = 'morty'
+$password = 'pickle'
+
+# Convert the password to a SecureString and create a credential object
+$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
+$credential = [System.Management.Automation.PSCredential]::new($username, $securePassword)
+
+# Make a GET request and automatically parse JSON response
+$response = Invoke-RestMethodDigest -Uri $uri -Method 'GET' -Credential $credential
+
+# Output the parsed response
+$response
+```
\ No newline at end of file
diff --git a/docs/Tutorials/Authentication/Methods/JWT.md b/docs/Tutorials/Authentication/Methods/JWT.md
index 9a24416ff..3e2453921 100644
--- a/docs/Tutorials/Authentication/Methods/JWT.md
+++ b/docs/Tutorials/Authentication/Methods/JWT.md
@@ -1,112 +1,299 @@
-# JWT
+# Create a JWT
-Pode has inbuilt JWT parsing for either [Bearer](../Bearer) or [API Key](../ApiKey) authentications. Pode will attempt to validate and parse the token/key as a JWT, and if successful the JWT's payload will be passed as the parameter to [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth), instead of the token/key.
+Pode provides a [`ConvertTo-PodeJwt`](../../../../Functions/Authentication/ConvertTo-PodeJwt) command that builds and signs a JWT for you. You can provide:
-For more information on JWTs, see the [official website](https://jwt.io).
+- **`-Header`**: A hashtable defining fields like `alg`, `typ`, etc.
+- **`-Payload`**: A hashtable for JWT claims (e.g., `sub`, `exp`, `nbf`, and other custom claims).
+- **`-Secret`**/**`-Certificate`**/**`-CertificateThumbprint`**, etc.: If you want to sign the JWT (for HS*, RS*, ES*, PS* algorithms).
+- **`-IgnoreSignature`**: If you want a token with no signature (alg = none).
+- **`-Authentication`**: To reference an existing named authentication scheme, automatically pulling its parameters (algorithm, secret, certificate, etc.) so the generated JWT is recognized by that scheme.
-## Setup
+## Customizing the Header/Payload
-To start using JWT authentication, you can supply the `-AsJWT` switch with either the `-Bearer` or `-ApiKey` switch on [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme). You can also supply an optional `-Secret` that the JWT signature uses so Pode can validate the JWT:
+When generating a JWT using **`ConvertTo-PodeJwt`**, you can specify parameters that either:
+
+1. **Manually** define the header/payload using `-Header` and `-Payload`, or
+2. **Automatically** set standard claims via shortcut parameters like `-Expiration`, `-Issuer`, `-Audience`, etc.
+
+You can also combine these approaches—Pode merges everything into the final token unless you use **`-NoStandardClaims`** to disable automatic claims.
+
+Below are the **primary parameters** you can pass to **`ConvertTo-PodeJwt`**:
+
+### Header and Payload
+
+- **`-Header`**: A hashtable for JWT header fields (e.g., `alg`, `typ`).
+- **`-Payload`**: A hashtable for arbitrary/custom claims (e.g., `role`, `scope`, etc.).
+- **`-NoStandardClaims`**: If specified, **no** standard claims are auto-generated (e.g., no `exp`, `nbf`, `iat`, etc.). This is useful if you want full control over claims in `-Payload`.
+
+#### Standard Claims Parameters
+
+These automatically populate or override common JWT claims:
+
+- **`-Expiration`** (`int`, default 3600)
+ - Sets the `exp` (expiration time) to the current time + `Expiration` (in seconds).
+ - For example, **3600** means `exp` = now + 1 hour.
+
+- **`-NotBefore`** (`int`, default 0)
+ - Sets the `nbf` (not-before) to current time + `NotBefore` (in seconds).
+ - **0** = immediate validity; **60** = valid 1 minute from now, etc.
+
+- **`-IssuedAt`** (`int`, default 0)
+ - Sets the `iat` (issued-at) time.
+ - **0** means “use current time.” Any other integer is added to the current time as seconds.
+
+- **`-Issuer`** (`string`)
+ - Sets the `iss` (issuer) claim, e.g. `"auth.example.com"`.
+
+- **`-Subject`** (`string`)
+ - Sets the `sub` (subject) claim, e.g. `"user123"`.
+
+- **`-Audience`** (`string`)
+ - Sets the `aud` (audience) claim, e.g. `"myapi.example.com"`.
+
+- **`-JwtId`** (`string`)
+ - Sets the `jti` (JWT ID) claim, a unique identifier for the token.
+
+If you **also** supply the same claims in your `-Payload` hashtable, Pode typically defers to your explicit claim unless **`-NoStandardClaims`** is omitted, in which case these parameters can overwrite the payload-based claims.
+
+---
+
+### Example Usage
+
+Below is an example that **automatically** sets standard claims for expiration (1 hour from now), not-before (starts immediately), and an issuer, while also providing a custom header/payload:
```powershell
-# jwt with no signature:
-New-PodeAuthScheme -Bearer -AsJWT | Add-PodeAuth -Name 'Example' -Sessionless -ScriptBlock {
- param($payload)
+$header = @{
+ alg = 'HS256'
+ typ = 'JWT'
}
-# jwt with signature, signed with secret "abc":
-New-PodeAuthScheme -ApiKey -AsJWT -Secret 'abc' | Add-PodeAuth -Name 'Example' -Sessionless -ScriptBlock {
- param($payload)
+$payload = @{
+ role = 'admin'
+ customClaim = 'someValue'
}
+
+$jwt = ConvertTo-PodeJwt `
+ -Header $header `
+ -Payload $payload `
+ -Secret 'SuperSecretKey' `
+ -Expiration 3600 `
+ -NotBefore 0 `
+ -Issuer 'auth.example.com' `
+ -Subject 'user123' `
+ -Audience 'myapi.example.com' `
+ -JwtId 'unique-token-id'
+
+Write-PodeJsonResponse -Value @{ token = $jwt }
```
-The `$payload` will be a PSCustomObject of the converted JSON payload. For example, sending the following unsigned JWT in a request:
+This produces a JWT that includes:
-```plain
-eyJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6Im1vcnR5Iiwic3ViIjoiMTIzIn0.
-```
+- A header with `alg = HS256`, `typ = JWT`.
+- Standard claims: `exp`, `nbf`, `iat`, `iss`, `sub`, `aud`, `jti`.
+- Custom claims: `role`, `customClaim`.
-would produce a payload of:
+If you **don’t** want Pode to generate any standard claims at all (perhaps you want to define everything in `-Payload` yourself), include **`-NoStandardClaims`**:
-```plain
-sub: 123
-username: morty
+```powershell
+$jwt = ConvertTo-PodeJwt -NoStandardClaims -Payload @{ sub='user123'; customKey='abc' } -Secret 'SuperSecretKey'
```
-### Algorithms
+No `exp`, `nbf`, or `iat` will be automatically added.
-Pode supports the following algorithms for JWT signatures:
+Similarly, if you have a named scheme:
-* None
-* HS256
-* HS384
-* HS512
+```powershell
+New-PodeAuthBearerScheme -AsJWT -Algorithm 'RS256' -Certificate 'C:\cert.pfx' -CertificatePassword (ConvertTo-SecureString "CertPass" -AsPlainText -Force) |
+ Add-PodeAuth -Name 'ExampleApiKeyCert'
-For `none`, Pode expects there to be no signature with the JWT. For other algorithms, a `-Secret` is required, and a signature must be supplied with the JWT in requests.
+Add-PodeRoute -Method Post -Path '/login' -ScriptBlock {
+ $jwt = ConvertTo-PodeJwt `
+ -Authentication 'ExampleApiKeyCert' `
+ -Issuer 'auth.example.com' `
+ -Expiration 3600 `
+ -Subject 'user123'
-### Payload
+ Write-PodeJsonResponse -Value @{ token = $jwt }
+}
+```
-If the payload of the JWT contains a expiry (`exp`) or a not before (`nbf`) timestamp, Pode will validate it and return a 400 if the JWT is expired/not started.
+Here, Pode automatically applies the RS256 certificate from **`ExampleApiKeyCert`** and merges your standard-claims parameters, producing a token recognized by that same scheme upon verification.
-## Usage
+## Using `-Authentication`
-To send the JWT in a request, the JWT should be sent in place of where the usual bearer token/API key would have been. For example, for bearer it would be in the Authorization header:
+If you have already set up an authentication scheme, for instance:
-```plain
-Authorization: Bearer
+```powershell
+New-PodeAuthBearerScheme -AsJWT -Algorithm 'RS256' -Certificate 'C:\path\to\cert.pfx' -CertificatePassword (ConvertTo-SecureString "CertPass" -AsPlainText -Force) |
+ Add-PodeAuth -Name 'ExampleApiKeyCert'
```
-and for API keys, it would be in the location defined (header, cookie, or query string). For example, in the X-API-KEY header:
+then you can **reuse** this scheme’s configuration when creating a token by calling:
-```plain
-X-API-KEY:
-```
+```powershell
+$jwt = ConvertTo-PodeJwt -Authentication 'ExampleApiKeyCert'
-## Create JWT
+# e.g., return the new JWT to a client
+Write-PodeJsonResponse -StatusCode 200 -Value @{ jwt_token = $jwt }
+```
-Pode has a simple [`ConvertTo-PodeJwt`](../../../../Functions/Authentication/ConvertTo-PodeJwt) that will build a JWT for you. It accepts a hashtable for `-Header` and `-Payload`, as well as an optional `-Secret`.
+Pode automatically looks up the **`ExampleApiKeyCert`** auth scheme, retrieves its signing algorithm and key/certificate, and uses those to generate a valid JWT. This ensures that the JWT you create can later be **decoded and verified** by the same auth scheme without having to re-specify all parameters (secret, certificate, etc.).
-The function will run some simple validation, and them build the JWT for you.
+### Example
-For example:
+Below is a short example of how you might implement a **login** route that returns a signed JWT:
```powershell
-$header = @{
- alg = 'hs256'
- typ = 'JWT'
+Add-PodeRoute -Method Post -Path '/user/login' -ScriptBlock {
+ param()
+
+ # In a real scenario, you'd validate the incoming credentials from $WebEvent.data
+ $username = $WebEvent.Data['username']
+ $password = $WebEvent.Data['password']
+
+ # If valid, generate a JWT that matches the 'ExampleApiKeyCert' scheme
+ $jwt = ConvertTo-PodeJwt -Authentication 'ExampleApiKeyCert'
+
+ Write-PodeJsonResponse -StatusCode 200 -Value @{ jwt_token = $jwt }
}
+```
-$payload = @{
- sub = '123'
- name = 'John Doe'
- exp = ([System.DateTimeOffset]::Now.AddDays(1).ToUnixTimeSeconds())
+In this example, the **`-Authentication`** parameter ensures Pode uses the RS256 certificate-based configuration already defined by the `ExampleApiKeyCert` auth scheme, producing a token that is verifiable by that same scheme on future requests.
+
+
+Below is an **updated JWT Lifecycle guide** for Pode, clarifying that **Pode automatically validates the token** when you attach `-Authentication` to a route, and that **`ConvertFrom-PodeJwt`** is generally used for **inspecting** or **debugging** token contents.
+
+
+## Managing the JWT Lifecycle in Pode
+
+In many scenarios, you need more than just generating JWTs—you also need endpoints or logic for **renewing** and **inspecting** tokens. Pode’s built-in commands and authentication features enable these patterns quickly:
+
+1. **Creating a JWT**: Use [`ConvertTo-PodeJwt`](https://github.com/Badgerati/Pode/blob/develop/Functions/Authentication/ConvertTo-PodeJwt.ps1) to build and sign a JWT.
+2. **Automatic Validation**: Rely on Pode’s bearer auth if a route uses `-Authentication 'YourBearerScheme'`.
+3. **Decoding/Inspecting a JWT**: Use `ConvertFrom-PodeJwt` if you want to explicitly decode the JWT for debugging or extracting claims.
+4. **Renewing/Extending a JWT**: Use `Update-PodeJwt` to reissue a token with a new expiration.
+
+## 1. Creating a JWT
+
+See the [“Create a JWT” guide](#create-a-jwt) for details on using `ConvertTo-PodeJwt`. You can:
+
+- Define a scheme in Pode (e.g., `Bearer_JWT_ES512`) that holds your algorithm and certificates/secrets.
+- Generate tokens by referencing `-Authentication 'Bearer_JWT_ES512'`.
+- Optionally set custom claims, expiration, issuer, etc.
+
+This creation step often happens inside a **login** route, as shown in the example below:
+
+```powershell
+function Test-User {
+ param($username, $password)
+ if ($username -eq 'morty' -and $password -eq 'pickle') {
+ return @{
+ Id = 'M0R7Y302'
+ Username = 'morty.smith'
+ Name = 'Morty Smith'
+ Groups = 'Domain Users'
+ }
+ }
+ throw 'Invalid credentials'
}
-ConvertTo-PodeJwt -Header $header -Payload $payload -Secret 'abc'
+Add-PodeRoute -Method Post -Path '/auth/login' -ScriptBlock {
+ try {
+ $username = $WebEvent.Data.username
+ $password = $WebEvent.Data.password
+ $user = Test-User $username $password # Validate credentials in some real store
+
+ $payload = @{
+ sub = $user.Id
+ name = $user.Name
+ # ... more custom claims ...
+ }
+
+ # Generate JWT recognized by the scheme 'Bearer_JWT_ES512'
+ $jwt = ConvertTo-PodeJwt -Payload $payload -Authentication 'Bearer_JWT_ES512' -Expiration 600
+
+ Write-PodeJsonResponse -StatusCode 200 -Value @{
+ success = $true
+ user = $user
+ jwt = $jwt
+ }
+ }
+ catch {
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid credentials' }
+ }
+}
```
-This return the following JWT:
+## 2. Automatic Validation
+
+Once you have a named bearer scheme (e.g., `Bearer_JWT_ES512`), **any** route that includes `-Authentication 'Bearer_JWT_ES512'` is automatically protected. Pode will:
+
+- Extract the JWT from the HTTP `Authorization` header (or another location if specified).
+- Decode and verify the signature based on the scheme’s configuration.
+- Reject the request if invalid; otherwise, set `$WebEvent.Auth.User` with any relevant user/claims data.
-```plain
-eyJ0eXAiOiJKV1QiLCJhbGciOiJoczI1NiJ9.eyJleHAiOjE2MjI1NTMyMTQsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMyJ9.LP-O8OKwix91a-SZwVK35gEClLZQmsORbW0un2Z4RkY
+```powershell
+Add-PodeRoute -Method Get -Path '/secure' -Authentication 'Bearer_JWT_ES512' -ScriptBlock {
+ # If we get here, the token is valid
+ $user = $WebEvent.Auth.User
+ Write-PodeJsonResponse -Value @{ user = $user; message = 'Welcome!' }
+}
```
-## Parse JWT
+No need to manually call `ConvertFrom-PodeJwt`—Pode handles validation behind the scenes.
-Pode has a [`ConvertFrom-PodeJwt`](../../../../Functions/Authentication/ConvertFrom-PodeJwt) that can be used to parse a valid JWT. Only the algorithms at the top of this page are supported for verifying the signature. You can skip signature verification by passing `-IgnoreSignature`. On success, the payload of the JWT is returned.
+## 3. Decoding/Inspecting a JWT
-For example, if the created JWT was supplied:
+Sometimes you want to **inspect** a token or decode it for debugging. That’s where `ConvertFrom-PodeJwt` is handy. For example, you might have a route that **also** includes `-Authentication 'Bearer_JWT_ES512'` (so the user needs a valid token to get in), but within the route you call `ConvertFrom-PodeJwt` to see the raw contents or claims:
```powershell
-ConvertFrom-PodeJwt -Token 'eyJ0eXAiOiJKV1QiLCJhbGciOiJoczI1NiJ9.eyJleHAiOjE2MjI1NTMyMTQsIm5hbWUiOiJKb2huIERvZSIsInN1YiI6IjEyMyJ9.LP-O8OKwix91a-SZwVK35gEClLZQmsORbW0un2Z4RkY' -Secret 'abc'
+Add-PodeRoute -Method Post -Path '/auth/bearer/jwt/info' -Authentication 'Bearer_JWT_ES512' -ScriptBlock {
+ try {
+ # Although Pode already validated the token, we can decode it ourselves for debugging
+ $decoded = ConvertFrom-PodeJwt -Outputs 'Header,Payload,Signature' -HumanReadable
+ Write-PodeJsonResponse -Value $decoded
+ }
+ catch {
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid JWT token supplied' }
+ }
+}
```
-then the following would be returned:
+This route returns the **header, payload, and signature** in JSON, with timestamps (like `exp`, `nbf`, `iat`) converted to human-readable dates.
+
+## 4. Renewing/Extending a JWT with `Update-PodeJwt`
+
+Use `Update-PodeJwt` to **extend** an existing token’s lifetime. Typically, you create a `/renew` endpoint:
```powershell
-@{
- sub = '123'
- name = 'John Doe'
- exp = 1636657408
+Add-PodeRoute -Method Post -Path '/auth/bearer/jwt/renew' -Authentication 'Bearer_JWT_ES512' -ScriptBlock {
+ try {
+ # Reads the current valid JWT, reissues it with a fresh 'exp' claim
+ $newToken = Update-PodeJwt
+ Write-PodeJsonResponse -StatusCode 200 -Value @{ success = $true; jwt = $newToken }
+ }
+ catch {
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid JWT token supplied' }
+ }
}
```
+
+Pode fetches the token from `$WebEvent`, checks the original scheme (here, `Bearer_JWT_ES512`), and re-signs with updated expiration. The rest of the claims stay the same. The client can then discard the old token and use the newly returned token moving forward.
+
+---
+
+## Full Lifecycle Example
+
+**1.** **Login** (create token)
+**2.** **Make Authenticated Requests** (Pode automatically validates)
+**3.** **Renew** (use `Update-PodeJwt` if needed)
+**4.** **Debug** (optionally decode token with `ConvertFrom-PodeJwt`)
+
+This covers a typical JWT flow in Pode:
+
+- The user logs in at `/auth/login`, gets a JWT.
+- They pass that JWT in subsequent requests, which are auto-validated by `-Authentication 'Bearer_JWT_ES512'`.
+- If the token is about to expire, they can call `/auth/bearer/jwt/renew` to get a fresh one.
+- If you need to debug claims, you can build an endpoint that calls `ConvertFrom-PodeJwt` or look at `$WebEvent.Auth.User`.
+
+For more details, see the [Pode GitHub examples](https://github.com/Badgerati/Pode/tree/develop/examples/Authentication) or the relevant [`ConvertTo-PodeJwt`](https://github.com/Badgerati/Pode/blob/develop/Functions/Authentication/ConvertTo-PodeJwt.ps1) and [`Update-PodeJwt`](https://github.com/Badgerati/Pode/blob/develop/Functions/Authentication/Update-PodeJwt.ps1) source files.
\ No newline at end of file
diff --git a/docs/Tutorials/Basics.md b/docs/Tutorials/Basics.md
index 137aa8b29..f887da2e5 100644
--- a/docs/Tutorials/Basics.md
+++ b/docs/Tutorials/Basics.md
@@ -1,4 +1,5 @@
# Basics
+
!!! Warning
You can initiate only one server per PowerShell instance. To run multiple servers, start additional PowerShell, or pwsh, sessions. Each session can run its own server. This is fundamental to how Pode operates, so consider it when designing your scripts and infrastructure.
@@ -59,6 +60,7 @@ When you call [`Start-PodeServer`](../../Functions/Core/Start-PodeServer) direct
For example, the following is a file that contains the same scriptblock for the server at the top of this page. Following that are the two ways to run the server - the first is via another script, and the second is directly from the CLI:
* File.ps1
+
```powershell
{
# attach to port 8080 for http
@@ -70,12 +72,15 @@ For example, the following is a file that contains the same scriptblock for the
```
* Server.ps1 (start via script)
+
```powershell
Start-PodeServer -FilePath './File.ps1'
```
+
then use `./Server.ps1` on the CLI.
* CLI (start from CLI)
+
```powershell
PS> Start-PodeServer -FilePath './File.ps1'
```
diff --git a/docs/Tutorials/Certificates.md b/docs/Tutorials/Certificates.md
index be3a9e606..2aff12dae 100644
--- a/docs/Tutorials/Certificates.md
+++ b/docs/Tutorials/Certificates.md
@@ -1,121 +1,313 @@
# Certificates
-Pode has the ability to generate and bind self-signed certificates (for dev/testing), as well as the ability to bind existing certificates for HTTPS.
+Pode has the ability to generate and bind self-signed certificates (for dev/testing), as well as the ability to bind existing certificates for HTTPS or JWT.
-There are 8 ways to setup HTTPS on [`Add-PodeEndpoint`](../../Functions/Core/Add-PodeEndpoint):
+## Setting Up HTTPS in Pode
-1. Supplying just the `-Certificate`, which is the path to files such as a `.cer` or `.pem` file.
-2. Supplying both the `-Certificate` and `-CertificatePassword`, which is the path to a `.pfx` file and its password.
-3. Supplying both the `-Certificate` and `-CertificateKey`, which is the paths to certificate/key PEM file pairs.
-4. Supplying all of `-Certificate`, `-CertificateKey`, and `-CertificatePassword`, which is the paths to certificate/key PEM file pairs and the password for an encrypted key.
-5. Supplying a `-CertificateThumbprint` for a certificate installed at `Cert:\CurrentUser\My` on Windows.
-6. Supplying a `-CertificateName` for a certificate installed at `Cert:\CurrentUser\My` on Windows.
-7. Supplying `-X509Certificate` of type `X509Certificate`.
-8. Supplying the `-SelfSigned` switch, to generate a quick self-signed `X509Certificate`.
+Pode provides multiple ways to configure HTTPS on [`Add-PodeEndpoint`](../../Functions/Core/Add-PodeEndpoint):
-Note: for 5. and 6. you can change the certificate store used by supplying `-CertificateStoreName` and/or `-CertificateStoreLocation`.
+- **File-based certificates:**
+ - `-Certificate`: Path to a `.cer` or `.pem` file.
+ - `-Certificate` with `-CertificatePassword`: Path to a `.pfx` file and its password.
+ - `-Certificate` with `-CertificateKey`: Paths to a certificate/key PEM file pair.
+ - `-Certificate`, `-CertificateKey`, and `-CertificatePassword`: Paths to an encrypted PEM file pair and its password.
-## Usage
+- **Windows Certificate Store:**
+ - `-CertificateThumbprint`: Uses a certificate installed at `Cert:\CurrentUser\My`.
+ - `-CertificateName`: Uses a certificate installed at `Cert:\CurrentUser\My` by name.
-### File
+- **X.509 Certificates:**
+ - `-X509Certificate`: Provides a certificate object of type `X509Certificate2`.
+ - `-SelfSigned`: Generates a quick self-signed `X509Certificate` for development.
-#### PFX
+- **Custom Certificate Management:**
+ - Pode’s built-in functions allow better control over certificate creation, import, and export.
-To bind a certificate PFX file, you use the `-Certificate` parameter, along with the `-CertificatePassword` parameter for the PFX certificate. The following example supplies the path to some `.pfx` to enable HTTPS support:
+## Generating a Self-Signed Certificate
+
+Pode provides the **`New-PodeSelfSignedCertificate`** function for creating self-signed X.509 certificates for development and testing purposes.
+
+### Features of `New-PodeSelfSignedCertificate`
+
+- ✅ Creates a **self-signed certificate** for HTTPS, JWT, or other use cases.
+- ✅ Supports **RSA** and **ECDSA** keys with configurable key sizes.
+- ✅ Can include **multiple Subject Alternative Names (SANs)** (e.g., `localhost`, IP addresses).
+- ✅ Allows setting **certificate purposes (ServerAuth, ClientAuth, etc.).**
+- ✅ Provides **ephemeral certificates** (in-memory only, not stored on disk).
+- ✅ Supports **exportable certificates** that can be saved for later use.
+
+### Usage Examples
+
+#### 1️⃣ Generate a Self-Signed Certificate for HTTPS
```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8090 -Protocol Https -Certificate './cert.pfx' -CertificatePassword 'Hunter2'
-}
+$cert = New-PodeSelfSignedCertificate -DnsName "example.com" -CertificatePurpose ServerAuth
```
-#### PEM
+- Creates a **self-signed RSA certificate** for `example.com`.
+- The certificate is valid for HTTPS (`ServerAuth`).
-Pode has support for binding certificate/key PEM file pairs, on PowerShell 7+ this works out-of-the-box. However, for PowerShell 5/6 you are required to have OpenSSL installed.
+#### 2️⃣ Generate a Self-Signed Certificate for Local Development
-To bind a certificate/key PEM file pairs generated via LetsEncrypt or OpenSSL, you supply their paths to the `-Certificate` and `-CertificateKey` parameters.
+```powershell
+$cert = New-PodeSelfSignedCertificate -Loopback
+```
+
+- Automatically includes common loopback addresses:
+ - `127.0.0.1`
+ - `::1`
+ - `localhost`
+ - The machine’s hostname
+
+#### 3️⃣ Generate an ECDSA Certificate
-For example, if you generate the certificate/key using the following:
-```bash
-openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
+```powershell
+$cert = New-PodeSelfSignedCertificate -DnsName "test.local" -KeyType "ECDSA" -KeyLength 384
```
-Then your endpoint would be created as:
+- Creates a **self-signed ECDSA certificate** with a **384-bit** key.
+
+#### 4️⃣ Generate a Certificate That Exists Only in Memory (Ephemeral)
+
+```powershell
+$cert = New-PodeSelfSignedCertificate -DnsName "temp.local" -Ephemeral
+```
+
+- The private key is **not stored on disk**, and the certificate only exists **in-memory**.
+
+#### 5️⃣ Generate an Exportable Certificate
+
+```powershell
+$cert = New-PodeSelfSignedCertificate -DnsName "secureapp.local" -Exportable
+```
+
+- The certificate is **exportable** and can be saved as a `.pfx` or `.pem` file later.
+
+#### 6️⃣ Bind a Self-Signed Certificate to an HTTPS Endpoint
+
```powershell
Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8090 -Protocol Https -Certificate './cert.pem' -CertificateKey './key.pem'
+ $cert = New-PodeSelfSignedCertificate -DnsName "example.com" -CertificatePurpose ServerAuth
+ Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -X509Certificate $cert
}
```
-However, if you generate the certificate/key and encrypt the key with a passphrase:
-```bash
-openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
+- Creates an HTTPS endpoint using a self-signed certificate.
+
+---
+
+## Generating a Certificate Signing Request (CSR)
+
+To generate a Certificate Signing Request (CSR) along with a private key, use the **`New-PodeCertificateRequest`** function:
+
+```powershell
+$csr = New-PodeCertificateRequest -DnsName "example.com" -CommonName "example.com" -KeyType "RSA" -KeyLength 2048
```
-Then the endpoint is created as follows:
+This will create a CSR file and a private key file in the current directory. You can specify additional parameters such as organization details and certificate purposes.
+
+### Using a CSR to Obtain a Certificate
+
+Once you have generated a CSR, you need to submit it to a **Certificate Authority (CA)** (such as Let's Encrypt, DigiCert, or a private CA) to receive a signed certificate. The process typically involves:
+
+1. Uploading or providing the `.csr` file to the CA.
+2. Completing domain validation steps (if required).
+3. Receiving the signed certificate (`.cer`, `.pem`, or `.pfx`) from the CA.
+4. Importing the signed certificate into Pode for use.
+
+Example: Importing the signed certificate after receiving it from the CA:
+
```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8090 -Protocol Https -Certificate './cert.pem' -CertificateKey './key.pem' -CertificatePassword ''
+$cert = Import-PodeCertificate -Path "C:\Certs\signed-cert.pfx" -CertificatePassword (ConvertTo-SecureString "MyPass" -AsPlainText -Force)
+if (-not (Test-PodeCertificate -Certificate $cert -ErrorAction Stop)) {
+ throw 'Certificate not valid'
}
```
-Depending on how you generated the certificate, especially if you used the above openssl, you might have to install the certificate to your local certificate store for it to be trusted. If you're using `Invoke-WebRequest` or `Invoke-RestMethod` on PowerShell 6+ you can supply the `-SkipCertificateCheck` switch.
+Alternatively, you can use:
+
+```powershell
+Test-PodeCertificate -Certificate $cert -ErrorAction Stop | Out-Null
+```
+
+to force an exception if the certificate fails validation.
+**Refer to the `Test-PodeCertificate` documentation for any parameter details.**
+
+---
+
+## Exporting a Certificate
+
+Pode allows exporting certificates in various formats such as PFX and PEM. To export a certificate:
+
+```powershell
+Export-PodeCertificate -Certificate $cert -FilePath "C:\Certs\mycert" -Format "PFX" -CertificatePassword (ConvertTo-SecureString "MyPass" -AsPlainText -Force)
+```
+
+or as a PEM file with a separate private key:
-### Thumbprint
+```powershell
+Export-PodeCertificate -Certificate $cert -FilePath "C:\Certs\mycert" -Format "PEM" -IncludePrivateKey
+```
+
+## Checking a Certificate’s Purpose
+
+A certificate's **purpose** is defined by its **Enhanced Key Usage (EKU)** attributes, which specify what the certificate is allowed to be used for. Common EKU values include:
-On Windows only, you can use a certificate that is installed at `Cert:\CurrentUser\My` using its thumbprint:
+- `ServerAuth` – Used for server authentication in HTTPS.
+- `ClientAuth` – Used for client authentication in mutual TLS setups.
+- `CodeSigning` – Used for digitally signing software and scripts.
+- `EmailSecurity` – Used for securing email communication.
+
+Pode can extract the EKU of a certificate to determine its intended purposes:
```powershell
+$purposes = Get-PodeCertificatePurpose -Certificate $cert
+$purposes
+```
+
+### Enforcing Certificate Purpose
+
+When Pode validates a certificate, it ensures that the certificate’s EKU matches the expected usage. If a certificate is used for an endpoint but lacks the required EKU (e.g., using a `CodeSigning` certificate for `ServerAuth`), Pode will reject the certificate and fail to bind it to the endpoint.
+
+For example, if an HTTPS endpoint is created, the certificate **must** include `ServerAuth`:
+
+```powershell
+$cert = New-PodeSelfSignedCertificate -DnsName "example.com" -CertificatePurpose ServerAuth
+
Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8090 -Protocol Https -CertificateThumbprint '2A623A8DC46ED42A13B27DD045BFC91FDDAEB957'
+ Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -X509Certificate $cert
}
```
-Note: You can change the certificate store used by supplying `-CertificateStoreName` and/or `-CertificateStoreLocation`.
+If the certificate lacks the correct EKU, Pode will return an error when attempting to bind it.
-### Name
+## Importing an Existing Certificate
-On Windows only, you can use a certificate that is installed at `Cert:\CurrentUser\My` using its subject name:
+To import a certificate from a file or the Windows certificate store:
```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8090 -Protocol Https -CertificateName '*.example.com'
+$cert = Import-PodeCertificate -Path "C:\Certs\mycert.pfx" -CertificatePassword (ConvertTo-SecureString "MyPass" -AsPlainText -Force)
+```
+
+If you import a certificate without validating it, you should then call **`Test-PodeCertificate`** to verify that the certificate is valid:
+
+```powershell
+if (-not (Test-PodeCertificate -Certificate $cert -ErrorAction Stop)) {
+ throw 'Certificate not valid'
}
```
-Note: You can change the certificate store used by supplying `-CertificateStoreName` and/or `-CertificateStoreLocation`.
+Alternatively, you can force an exception by piping the output:
+
+```powershell
+Test-PodeCertificate -Certificate $cert -ErrorAction Stop | Out-Null
+```
+
+Refer to the **Test-PodeCertificate** section below for more details on all available parameters.
+
+---
+
+## Testing a Certificate’s Validity
+
+Pode provides the **`Test-PodeCertificate`** function to validate an **X.509 certificate** and ensure it meets security and usage requirements.
-### X509
+### Features of `Test-PodeCertificate`
-The following will instead create an X509Certificate, and pass that to the endpoint instead:
+- ✅ Checks if the certificate is **within its validity period** (`NotBefore` and `NotAfter`).
+- ✅ **Builds the certificate chain** to verify its trust.
+- ✅ Supports **online and offline revocation checking** (OCSP/CRL).
+- ✅ Allows **optional enforcement of strong cryptographic algorithms**.
+- ✅ Provides an option to **reject self-signed certificates**.
+- ✅ Optionally checks that the certificate’s Enhanced Key Usage (EKU) matches an **ExpectedPurpose** (with an optional **Strict** mode).
+
+### Usage Examples
+
+#### Basic Certificate Validation
```powershell
-$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new('./certs/example.cer')
-Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -X509Certificate $cert
+Test-PodeCertificate -Certificate $cert
```
-### Self-Signed
+- Checks if the certificate is currently valid.
+- Does **not** check revocation status.
-If you are developing/testing a site on HTTPS then Pode can generate and bind quick self-signed certificates. To do this you can pass the `-SelfSigned` switch:
+#### Validate Certificate with Online Revocation Checking
```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned
-}
+Test-PodeCertificate -Certificate $cert -CheckRevocation
+```
+
+- Uses **OCSP/CRL lookup** to check if the certificate is revoked.
+
+#### Validate Certificate with Offline (Cached CRL) Revocation Check
+
+```powershell
+Test-PodeCertificate -Certificate $cert -CheckRevocation -OfflineRevocation
```
-You might get a warning in the browser about the certificate, and this is fine. If you're using `Invoke-WebRequest` or `Invoke-RestMethod` on PowerShell 6+ you can supply the `-SkipCertificateCheck` switch.
+- Uses **only locally cached CRLs**, making it suitable for air-gapped environments.
-## SSL Protocols
+#### Allow Certificates with Weak Algorithms
-The default allowed SSL protocols are SSL3 and TLS1.2 (or just TLS1.2 on MacOS), but you can change these to any of: SSL2, SSL3, TLS, TLS11, TLS12, TLS13. This is specified in your `server.psd1` configuration file:
+```powershell
+Test-PodeCertificate -Certificate $cert -AllowWeakAlgorithms
+```
+
+- Allows the use of certificates with **SHA1, MD5, or RSA-1024**.
+
+#### Reject Self-Signed Certificates
+
+```powershell
+Test-PodeCertificate -Certificate $cert -DenySelfSigned
+```
+
+- Fails validation if the certificate **is self-signed**.
+
+#### Enforce Expected Certificate Purpose
+
+```powershell
+Test-PodeCertificate -Certificate $cert -ExpectedPurpose CodeSigning -Strict
+```
+
+- Validates that the certificate is explicitly authorized for **CodeSigning**.
+- In strict mode, if any unknown EKUs are present, the validation fails.
+
+---
+
+## Using Certificates for JWT Authentication
+
+Pode supports using X.509 certificates for JWT authentication. You can specify a certificate for signing and verifying JWTs by providing `-X509Certificate` when creating a bearer authentication scheme:
```powershell
-@{
- Server = @{
- Ssl= @{
- Protocols = @('TLS', 'TLS11', 'TLS12')
- }
+$cert = Import-PodeCertificate -Path "C:\Certs\jwt-signing-cert.pfx" -CertificatePassword (ConvertTo-SecureString "MyPass" -AsPlainText -Force)
+
+Start-PodeServer {
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -X509Certificate $cert |
+ Add-PodeAuth -Name 'JWTAuth' -Sessionless -ScriptBlock {
+ param($token)
+
+ # Validate and extract user details
+ return @{ User = $user }
}
}
```
+
+Alternatively, you can use a self-signed certificate for development and testing:
+
+```powershell
+Start-PodeServer {
+ New-PodeAuthBearerScheme `
+ -AsJWT `
+ -SelfSigned |
+ Add-PodeAuth -Name 'JWTAuth' -Sessionless -ScriptBlock {
+ param($token)
+
+ # Validate and extract user details
+ return @{ User = $user }
+ }
+}
+```
+
+Using certificates for JWT authentication provides enhanced security by enabling asymmetric signing (RSA/ECDSA) rather than using a shared secret.
diff --git a/docs/Tutorials/Configuration.md b/docs/Tutorials/Configuration.md
index f6a8720fa..b50149717 100644
--- a/docs/Tutorials/Configuration.md
+++ b/docs/Tutorials/Configuration.md
@@ -92,6 +92,8 @@ A "path" like `Server.Ssl.Protocols` looks like the below in the file:
| Server.FileMonitor | Defines configuration for restarting the server based on file updates | [link](../Restarting/Types/FileMonitoring) |
| Server.ReceiveTimeout | Define the amount of time a Receive method call will block waiting for data | [link](../Endpoints/Basic/StaticContent/#server-timeout) |
| Server.DefaultFolders | Set the Default Folders paths | [link](../Routes/Utilities/StaticContent/#changing-the-default-folders) |
+| Server.Logging.QueueLimit | Set the maximum number of logs allowed in the queue | [link](../Logging/Overview) |
+| Server.Logging.Masking.Patterns | Regular expressions congiguration to mask sensitive logs information | [link](../Logging/Overview) |
| Server.Console | Set the Console settings | [link](../Getting-Started/Console) |
| Web.OpenApi.DefaultDefinitionTag | Define the primary tag name for OpenAPI ( `default` is the default) | [link](../OpenAPI/Overview) |
| Web.OpenApi.UsePodeYamlInternal | Force the use of the internal YAML converter (`False` is the default) | |
diff --git a/docs/Tutorials/Endpoints/Basics.md b/docs/Tutorials/Endpoints/Basics.md
index 888a529ba..1060a5bb8 100644
--- a/docs/Tutorials/Endpoints/Basics.md
+++ b/docs/Tutorials/Endpoints/Basics.md
@@ -188,3 +188,39 @@ To set this property, include it in `server.psd1` configuration file as shown be
}
}
```
+
+## Favicons
+
+Pode allows you to customize or disable the favicon for HTTP/HTTPS endpoints. By default, Pode serves a built-in `favicon.ico`, but you can override this behavior using the `-Favicon` and `-NoFavicon` parameters.
+
+- **`-Favicon` (byte[])**: Allows you to specify a custom favicon as a byte array.
+- **`-NoFavicon` (switch)**: Disables the favicon, preventing browsers from requesting it.
+
+### **Favicon Format and Specifications**
+
+Favicons are typically stored in the `.ico` format, which is a container that can hold multiple image sizes and color depths. This ensures compatibility with different browsers and devices. Some modern browsers also support `.png` and `.svg` favicons.
+
+For more details on favicon formats and specifications, refer to the [Favicon specification](https://en.wikipedia.org/wiki/Favicon) and [RFC 5988](https://datatracker.ietf.org/doc/html/rfc5988).
+
+### **Favicon Size Recommendations**
+
+Favicons should include multiple resolutions for optimal display across different devices. Recommended sizes include:
+
+- **16x16** → Used in browser tabs, bookmarks, and address bars.
+- **32x32** → Used in browser tabs on higher-resolution displays.
+- **48x48** → Used by some older browsers and web applications.
+- **64x64+** → Generally not used by browsers but can be helpful for scalability in web apps.
+- **256x256** → Mainly for **Windows app icons** (not typically used as a favicon in browsers).
+
+### **Usage Example**
+
+```powershell
+# Load a custom favicon from file
+$iconBytes = [System.IO.File]::ReadAllBytes("C:\path\to\custom.ico")
+Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http -Favicon $iconBytes
+
+# Disable favicon for an endpoint
+Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http -NoFavicon
+```
+
+Using a favicon enhances the user experience by providing a recognizable site icon in browser tabs and bookmarks.
diff --git a/docs/Tutorials/Logging/Methods/Custom.md b/docs/Tutorials/Logging/Methods/Custom.md
index 8fe03a7b1..a6cde3cc4 100644
--- a/docs/Tutorials/Logging/Methods/Custom.md
+++ b/docs/Tutorials/Logging/Methods/Custom.md
@@ -1,35 +1,76 @@
# Custom
-Sometimes you don't want to log to a file, or the terminal; instead you want to log to something better, like LogStash, Splunk, Athena, or any other central logging platform. Although Pode doesn't have these inbuilt (yet!) it is possible to create a custom logging method, where you define a ScriptBlock with logic to send logs to these platforms.
+Sometimes you may want to log to platforms other than a file or the terminal, such as LogStash, Splunk, Athena, or other central logging platforms. Although Pode doesn't have these integrations built-in (yet!), it is possible to create a custom logging method by defining a ScriptBlock with the logic to send logs to these platforms.
-These custom method can be used for any log type - Requests, Error, or Custom.
+Custom methods can be used for any log type: Requests, Error, or Custom.
-The ScriptBlock you create will be supplied two arguments:
+The ScriptBlock you create will receive two arguments:
-1. The item to be logged. This could be a string (from Requests/Errors), or any custom type.
-2. The options you supplied on [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod).
+1. The item to be logged. This could be a string (from Requests/Errors) or any custom type.
+
+2. The options you supplied to [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod).
+
+Additionally, custom logging methods can be run in their own runspace by using the `-UseRunspace` parameter, ensuring isolation and efficiency.
## Examples
### Send to S3 Bucket
-This example will take whatever item is supplied to it, convert it to a string, and then send it off to some S3 bucket in AWS. In this case, it will be logging Requests:
+This example takes the supplied item, converts it to a string, and sends it to an S3 bucket in AWS. In this case, it will log Requests:
+#### Legacy (No Runspace)
```powershell
$s3_options = @{
AccessKey = $AccessKey
SecretKey = $SecretKey
}
-$s3_logging = New-PodeLoggingType -Custom -ArgumentList $s3_options -ScriptBlock {
+$s3_logging = New-PodeLoggingMethod -Custom -ArgumentList $s3_options -ScriptBlock {
param($item, $s3_opts)
- Write-S3Object `
- -BucketName '' `
- -Content $item.ToString() `
- -AccessKey $s3_opts.AccessKey `
+ Write-S3Object \`
+ -BucketName '' \`
+ -Content $item.ToString() \`
+ -AccessKey $s3_opts.AccessKey \`
-SecretKey $s3_opts.SecretKey
}
+$s3_logging | Enable-PodeRequestLogging
+```
+
+#### With Runspace
+
+```powershell
+$s3_options = @{
+ AccessKey = $AccessKey
+ SecretKey = $SecretKey
+}
+
+$s3_logging = New-PodeLoggingMethod -Custom -UseRunspace -CustomOptions $s3_options -ScriptBlock {
+ # No param() allowed here
+ Write-S3Object \`
+ -BucketName '' \`
+ -Content $Item.ToString() \`
+ -AccessKey $Options.AccessKey \`
+ -SecretKey $Options.SecretKey
+}
$s3_logging | Enable-PodeRequestLogging
```
+
+
+In this example, the `-UseRunspace` parameter ensures that the custom logging method runs in its own runspace, providing better isolation and performance.
+
+##### Variable available inside the ScriptBlock
+
+| Variable | Type | Description |
+| ----------------------- | ----------------------------- | ------------------------------------------------ |
+| Item | string | Log message content |
+| Options | hashtable | The options supplied to the logging method |
+| Options.FailureAction | string (Ignore, Report, Halt) | Defines the behavior in case of failure. |
+| Options.DataFormat | string | The date format to use for the log entries. |
+| Options.AsUTC | boolean | the time is logged in UTC instead of local time. |
+| Options. | PSObject | Any key passed using `-CustomOptions` parameter |
+| RawItem | hashtable | Log message in raw format |
+
+
+By leveraging custom logging methods, you can extend Pode's logging capabilities to integrate with a wide range of external platforms, providing flexibility and control over your logging strategy.
\ No newline at end of file
diff --git a/docs/Tutorials/Logging/Methods/File.md b/docs/Tutorials/Logging/Methods/File.md
index 84b28df21..946923a79 100644
--- a/docs/Tutorials/Logging/Methods/File.md
+++ b/docs/Tutorials/Logging/Methods/File.md
@@ -39,3 +39,42 @@ By default Pode puts all logs in the `./logs` directory. You can use a custom pa
```powershell
New-PodeLoggingMethod -File -Name 'requests' -Path 'E:/logs' | Enable-PodeRequestLogging
```
+
+### Format
+
+The Format parameter allows you to specify the format of the log entries. Available options are:
+
+- RFC3164
+- RFC5424
+- Simple
+- Default (default option)
+
+The Simple format uses the following structure: timestamp level source message. The Default format uses the legacy Pode format.
+
+```powershell
+New-PodeLoggingMethod -File -Name 'requests' -Format 'Simple' | Enable-PodeRequestLogging
+```
+A log entry using the Simple format might look like this:
+
+```arduino
+2024-08-01T12:00:00Z INFO MyApp "Request received"
+```
+
+### Custom Separator
+When using the Simple format, you can specify a custom separator for log entries:
+
+```powershell
+New-PodeLoggingMethod -File -Name 'requests' -Format 'Simple' -Separator ',' | Enable-PodeRequestLogging
+```
+
+A log entry using the Simple format with a comma separator might look like this:
+```arduino
+2024-08-01T12:00:00Z,INFO,MyApp,"Request received"
+```
+
+### Maximum Log Entry Length
+The MaxLength parameter sets the maximum length of log entries. The default value is -1, which means no limit.
+
+```powershell
+New-PodeLoggingMethod -File -Name 'requests' -MaxLength 500 | Enable-PodeRequestLogging
+```
\ No newline at end of file
diff --git a/docs/Tutorials/Logging/Methods/Syslog.md b/docs/Tutorials/Logging/Methods/Syslog.md
new file mode 100644
index 000000000..eba6db8d2
--- /dev/null
+++ b/docs/Tutorials/Logging/Methods/Syslog.md
@@ -0,0 +1,56 @@
+
+# Syslog
+
+Pode supports logging items to a Syslog server using the inbuilt Syslog logging method. This method allows you to define various parameters such as the Syslog server address, port, transport protocol, and more. The logging method will convert any item to a string and send it to the configured Syslog server.
+
+By default, Pode will use UDP as the transport protocol and RFC5424 as the Syslog protocol. You can customize these settings based on your Syslog server requirements.
+
+## Examples
+
+### Basic
+
+The following example will setup the Syslog logging method for logging requests:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' | Enable-PodeRequestLogging
+```
+
+### Custom Port
+
+The following example will configure Syslog logging to use a custom port. The default port is 514, but you can specify a different port if needed:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Port 1514 | Enable-PodeRequestLogging
+```
+
+### Secure Connection with TLS
+
+The following example will configure Syslog logging to use TLS for a secure connection. You can also specify the TLS protocol version to use:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Transport 'TLS' -TlsProtocol 'TLS1.2' | Enable-PodeRequestLogging
+```
+
+### Custom Syslog Protocol
+
+The following example will configure Syslog logging to use a different Syslog protocol. The default protocol is RFC5424, but you can specify RFC3164 if needed:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -SyslogProtocol 'RFC3164' | Enable-PodeRequestLogging
+```
+
+### Skip Certificate Validation
+
+The following example will configure Syslog logging to skip certificate validation for TLS connections. This is useful for testing purposes but not recommended for production environments:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Transport 'TLS' -SkipCertificateCheck | Enable-PodeRequestLogging
+```
+
+### Custom Encoding
+
+The following example will configure Syslog logging to use a different encoding for the Syslog messages. The default encoding is UTF8:
+
+```powershell
+New-PodeLoggingMethod -Syslog -Server '192.168.1.1' -Encoding 'ASCII' | Enable-PodeRequestLogging
+```
\ No newline at end of file
diff --git a/docs/Tutorials/Logging/Overview.md b/docs/Tutorials/Logging/Overview.md
index ac6527c05..36fb528dd 100644
--- a/docs/Tutorials/Logging/Overview.md
+++ b/docs/Tutorials/Logging/Overview.md
@@ -1,15 +1,17 @@
# Overview
-There are two aspects to logging in Pode: Methods and Types.
+Logging in Pode consists of two main components: Methods and Types.
-* Methods define how log items should be recorded, such as to a file, terminal, or event viewer.
-* Types define how items to log are transformed, and what should be supplied to the Method.
+- **Methods**: Define how log items should be recorded, such as to a file, terminal, or event viewer. Each logging method operates in its own runspace, providing isolation and efficiency. The exception to this is the Custom method, which by default runs in the same runspace as the log dispatcher unless the `-UseRunspace` parameter is specified.
-For example when you supply an Exception to [`Write-PodeErrorLog`](../../../Functions/Logging/Write-PodeErrorLog), this Exception is first supplied to Pode's inbuilt Error logging type. This type transforms any Exception (or Error Record) into a string which can then be supplied to the File logging method.
+- **Types**: Define how log items are transformed and what data should be supplied to the Method.
-In Pode you can use File, Terminal, Event Viewer, or a Custom method. As well as Request, Error, or a Custom type.
+When you supply an Exception to [`Write-PodeErrorLog`](../../../Functions/Logging/Write-PodeErrorLog), the Exception is first processed by Pode's built-in Error logging type. This type transforms the Exception (or Error Record) into a string format, which can then be recorded by the logging method (e.g., File).
+
+Pode supports various logging methods, including File, Terminal, Event Viewer, Syslog, Restful, or Custom methods. Additionally, you can utilize different logging types such as Request, Error, or Custom types.
+
+This flexibility allows you to create a custom logging method that can output logs to various platforms, such as an S3 bucket, Splunk, or any other logging service.
-This means you could write a logging method to output to an S3 bucket, Splunk, or any other logging platform.
## Masking Values
@@ -109,3 +111,50 @@ Instead of writing logs one-by-one, the above will keep transformed log items in
This means that the method's scriptblock will receive an array of items, rather than a single item.
You can also sent a `-BatchTimeout` value, in seconds, so that if your batch size it 10 but only 5 log items are added, then after the timeout value the logs items will be sent to your method.
+
+
+
+## Configuring Failure Actions for Log Writing
+
+Defines the behavior in case of failure to write a log. This can happen if the disk is full, the Syslog server is offline, or if the number of logs in the queue reaches the maximum allowed. The options are:
+- **Ignore** : Does nothing and continues execution. **(Default)**
+- **Report** : Writes a message to the console for any failure.
+- **Halt** : Writes a message to the console and shuts down the Pode server.
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'errors' -FailureAction 'Report' | Enable-PodeRequestLogging
+```
+
+## QueueLimit
+Defines the maximum number of logs allowed in the queue before throwing an event.
+The default value is 500. The exception is handled based on the `-FailureAction` parameter.
+
+```powershell
+@{
+ Server = @{
+ Logging = @{
+ QueueLimit = 1000
+ }
+ }
+}
+```
+
+## DataFormat
+The date format to use for the log entries. The default format is `'dd/MMM/yyyy:HH:mm:ss zzz'`.
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'access' -DataFormat 'yyyy-MM-dd HH:mm:ss' | Enable-PodeErrorLogging
+```
+
+## ISO8601
+If set, the date format will be ISO 8601 compliant (equivalent to `-DataFormat 'yyyy-MM-ddTHH:mm:ssK'`). This parameter is mutually exclusive with DataFormat.
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'access' -ISO8601 | Enable-PodeErrorLogging
+```
+
+## AsUTC
+If set, the time will be logged in UTC instead of local time.
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'access' -AsUTC -ISO8601 | Enable-PodeErrorLogging
\ No newline at end of file
diff --git a/docs/Tutorials/Logging/Types/General.md b/docs/Tutorials/Logging/Types/General.md
new file mode 100644
index 000000000..1fc2daee2
--- /dev/null
+++ b/docs/Tutorials/Logging/Types/General.md
@@ -0,0 +1,70 @@
+
+# General Logging
+
+Pode supports general logging, allowing you to define custom logging methods and log levels. This feature enables you to write logs based on specified methods, ensuring flexibility and control over logging outputs.
+
+To enable general logging, use the `Add-PodeLoggingMethod` function. This function takes a hashtable defining the logging method, including a ScriptBlock for log output. You can specify various log levels to be enabled, such as Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, and Debug.
+
+## Enabling General Logging
+
+To enable general logging, use the `Add-PodeLoggingMethod` function, supplying the necessary parameters:
+
+- `Method`: The hashtable defining the logging method, including the ScriptBlock for log output.
+- `Levels`: An array of log levels to be enabled for the logging method (default includes Error, Emergency, Alert, Critical, Warning, Notice, Informational, Info, Verbose, Debug).
+- `Name`: The name of the logging method to be enabled.
+- `Raw`: If set, the raw log data will be included in the logging output.
+
+### Example
+
+```powershell
+$method = New-PodeLoggingMethod -syslog -Server 127.0.0.1 -Transport UDP
+$method | Add-PodeLoggingMethod -Name "mysyslog"
+```
+
+## Disabling General Logging
+
+To disable a general logging method, use the `Remove-PodeLoggingMethod` function with the `Name` parameter:
+
+### Example
+
+```powershell
+Remove-PodeLoggingMethod -Name 'mysyslog'
+```
+
+With these functions, Pode ensures robust and customizable logging capabilities, allowing you to manage logs effectively based on your specific requirements.
+
+## Writing to General Logs
+
+Pode allows you to write logs to configured custom or inbuilt logging methods using the `Write-PodeLog` function. This function supports both custom and inbuilt logging methods, enabling structured logging with various log levels and messages.
+
+### Writing to General Logs
+
+To write logs, you can use the `Write-PodeLog` function with different parameters to specify the logging method, log level, message, and other details.
+
+#### Example Usage
+
+##### Logging an Object
+
+To write an object to a configured logging method:
+
+```powershell
+$logItem = @{
+ Date = [datetime]::Now
+ Level = 'Informational'
+ Server = 'MyServer'
+ Category = 'General'
+ Message = 'This is a log message'
+ StackTrace = ''
+}
+$logItem | Write-PodeLog -Name 'mysyslog'
+```
+
+##### Logging with Custom Levels and Messages
+
+To log a custom message with a specific log level:
+
+```powershell
+Write-PodeLog -Name 'mysyslog' -Level 'Error' -Message 'An error occurred.' -Tag 'MyApp'
+```
+
+In these examples, `Write-PodeLog` is used to write structured log items or custom messages to the specified logging methods, helping you maintain organized and detailed logs.
\ No newline at end of file
diff --git a/docs/Tutorials/Logging/Types/Requests.md b/docs/Tutorials/Logging/Types/Requests.md
index 60a812d62..96ba2d805 100644
--- a/docs/Tutorials/Logging/Types/Requests.md
+++ b/docs/Tutorials/Logging/Types/Requests.md
@@ -6,7 +6,10 @@ Pode has inbuilt Request logging logic, that will parse and return a valid log i
To enable and use the Request logging you use the [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging) function, supplying a logging method from [`New-PodeLoggingMethod`](../../../../Functions/Logging/New-PodeLoggingMethod).
-The Request type logic will format a string using [Combined Log Format](https://httpd.apache.org/docs/1.3/logs.html#combined). This string is then supplied to the logging method's scriptblock. If you're using a Custom logging method and want the raw hashtable instead, you can supply `-Raw` to [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging).
+The Request type logic will format a string using [Combined Log Format](https://httpd.apache.org/docs/1.3/logs.html#combined).
+This string is then supplied to the logging method's scriptblock. You can customize the log format using the `-LogFormat` parameter with options like `Extended`, `Common`, `Combined`, and `JSON`.
+
+If you're using a Custom logging method and want the raw hashtable instead, you can supply `-Raw` to [`Enable-PodeRequestLogging`](../../../../Functions/Logging/Enable-PodeRequestLogging).
## Examples
@@ -18,6 +21,23 @@ The following example simply enables Request logging, and will output all items
New-PodeLoggingMethod -Terminal | Enable-PodeRequestLogging
```
+### Log Format
+
+#### Extended Log Format
+The following example enables Request logging using the Extended Log Format:
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'requests' | Enable-PodeRequestLogging -LogFormat 'Extended'
+```
+
+#### JSON Format
+The following example enables Request logging using JSON Format:
+
+```powershell
+New-PodeLoggingMethod -File -Path './logs' -Name 'requests' | Enable-PodeRequestLogging -LogFormat 'Json'
+```
+
+
### Using Raw Item
The following example uses a Custom logging method, and sets Request logging to return and supply the raw hashtable to the Custom method's scriptblock. The Custom method simply logs the Host an StatusCode to the terminal (but could be to something like an S3 bucket):
@@ -70,4 +90,4 @@ The raw Request hashtable that will be supplied to any Custom logging methods wi
Size = '9001'
}
}
-```
+```
\ No newline at end of file
diff --git a/docs/Tutorials/Routes/Overview.md b/docs/Tutorials/Routes/Overview.md
index a9acd7efe..aba0f2b6d 100644
--- a/docs/Tutorials/Routes/Overview.md
+++ b/docs/Tutorials/Routes/Overview.md
@@ -39,96 +39,21 @@ The scriptblock for the route will have access to the `$WebEvent` variable which
You can add your routes straight into the [`Start-PodeServer`](../../../Functions/Core/Start-PodeServer) scriptblock, or separate them into different files. These files can then be dot-sourced, or you can use [`Use-PodeRoutes`](../../../Functions/Routes/Use-PodeRoutes) to automatically load all ps1 files within a `/routes` directory at the root of your server.
-## Payloads
+## Retrieving Client Parameters
-The following is an example of using data from a request's payload - ie, the data in the body of POST request. To retrieve values from the payload you can use the `.Data` property on the `$WebEvent` variable to a route's logic.
+When working with REST calls, data can be passed by the client using various methods, including Cookies, Headers, Paths, Queries, and Body. Each of these methods has specific ways to retrieve the data:
-Depending the the Content-Type supplied, Pode has inbuilt body-parsing logic for JSON, XML, CSV, and Form data.
+- **Cookies**: Cookies sent by the client can be accessed using the `$WebEvent.Cookies` property or the `Get-PodeCookie` function for more advanced handling. For more details, refer to the [Cookies Documentation](./Parameters/Cookies.md).
-This example will get the `userId` and "find" user, returning the users data:
+- **Headers**: Headers can be retrieved using the `$WebEvent.Request.Headers` property or the `Get-PodeHeader` function, which provides additional deserialization options. Learn more in the [Headers Documentation](./Parameters/Headers.md).
-```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+- **Paths**: Parameters passed through the URL path can be accessed using the `$WebEvent.Parameters` property or the `Get-PodePathParameter` function. Detailed information can be found in the [Path Parameters Documentation](./Parameters/Paths.md).
- Add-PodeRoute -Method Post -Path '/users' -ScriptBlock {
- # get the user
- $user = Get-DummyUser -UserId $WebEvent.Data.userId
-
- # return the user
- Write-PodeJsonResponse -Value @{
- Username = $user.username
- Age = $user.age
- }
- }
-}
-```
-
-The following request will invoke the above route:
-
-```powershell
-Invoke-WebRequest -Uri 'http://localhost:8080/users' -Method Post -Body '{ "userId": 12345 }' -ContentType 'application/json'
-```
+- **Queries**: Query parameters from the URL can be accessed via `$WebEvent.Query` or retrieved using the `Get-PodeQueryParameter` function for deserialization support. Check the [Query Parameters Documentation](./Parameters/Queries.md).
-!!! important
- The `ContentType` is required as it informs Pode on how to parse the requests payload. For example, if the content type were `application/json`, then Pode will attempt to parse the body of the request as JSON - converting it to a hashtable.
+- **Body**: Data sent in the request body, such as in POST requests, can be retrieved using the `$WebEvent.Data` property or the `Get-PodeBodyData` function for enhanced deserialization capabilities. See the [Body Data Documentation](./Parameters/Body.md) for more information.
-!!! important
- On PowerShell 5 referencing JSON data on `$WebEvent.Data` must be done as `$WebEvent.Data.userId`. This also works in PowerShell 6+, but you can also use `$WebEvent.Data['userId']` on PowerShell 6+.
-
-## Query Strings
-
-The following is an example of using data from a request's query string. To retrieve values from the query string you can use the `.Query` property from the `$WebEvent` variable. This example will return a user based on the `userId` supplied:
-
-```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
-
- Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {
- # get the user
- $user = Get-DummyUser -UserId $WebEvent.Query['userId']
-
- # return the user
- Write-PodeJsonResponse -Value @{
- Username = $user.username
- Age = $user.age
- }
- }
-}
-```
-
-The following request will invoke the above route:
-
-```powershell
-Invoke-WebRequest -Uri 'http://localhost:8080/users?userId=12345' -Method Get
-```
-
-## Parameters
-
-The following is an example of using values supplied on a request's URL using parameters. To retrieve values that match a request's URL parameters you can use the `.Parameters` property from the `$WebEvent` variable. This example will get the `:userId` and "find" user, returning the users data:
-
-```powershell
-Start-PodeServer {
- Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
-
- Add-PodeRoute -Method Get -Path '/users/:userId' -ScriptBlock {
- # get the user
- $user = Get-DummyUser -UserId $WebEvent.Parameters['userId']
-
- # return the user
- Write-PodeJsonResponse -Value @{
- Username = $user.username
- Age = $user.age
- }
- }
-}
-```
-
-The following request will invoke the above route:
-
-```powershell
-Invoke-WebRequest -Uri 'http://localhost:8080/users/12345' -Method Get
-```
+Each link provides detailed usage and examples to help you retrieve and manipulate the parameters passed by the client effectively.
## Script from File
diff --git a/docs/Tutorials/Routes/Parameters/Body.md b/docs/Tutorials/Routes/Parameters/Body.md
new file mode 100644
index 000000000..b620d3a54
--- /dev/null
+++ b/docs/Tutorials/Routes/Parameters/Body.md
@@ -0,0 +1,100 @@
+# Body Payloads
+
+The following is an example of using data from a request's payload—i.e., the data in the body of a POST request. To retrieve values from the payload, you can use the `.Data` property on the `$WebEvent` variable in a route's logic.
+
+Alternatively, you can use the `Get-PodeBodyData` function to retrieve the body data, with additional support for deserialization.
+
+Depending on the Content-Type supplied, Pode has built-in body-parsing logic for JSON, XML, CSV, and Form data.
+
+This example will get the `userId` and "find" the user, returning the user's data:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Post -Path '/users' -ScriptBlock {
+ # get the user
+ $user = Get-DummyUser -UserId $WebEvent.Data.userId
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+The following request will invoke the above route:
+
+```powershell
+Invoke-WebRequest -Uri 'http://localhost:8080/users' -Method Post -Body '{ "userId": 12345 }' -ContentType 'application/json'
+```
+
+!!! important
+ The `ContentType` is required as it informs Pode on how to parse the request's payload. For example, if the content type is `application/json`, Pode will attempt to parse the body of the request as JSON—converting it to a hashtable.
+
+!!! important
+ On PowerShell 5, referencing JSON data on `$WebEvent.Data` must be done as `$WebEvent.Data.userId`. This also works in PowerShell 6+, but you can also use `$WebEvent.Data['userId']` on PowerShell 6+.
+
+### Using Get-PodeBodyData
+
+Alternatively, you can use the `Get-PodeBodyData` function to retrieve the body data. This function works similarly to the `.Data` property on `$WebEvent` and supports the same content types.
+
+Here is the same example using `Get-PodeBodyData`:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Post -Path '/users' -ScriptBlock {
+ # get the body data
+ $body = Get-PodeBodyData
+
+ # get the user
+ $user = Get-DummyUser -UserId $body.userId
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+### Deserialization with Get-PodeBodyData
+
+Typically the request body is encoded in Json,Xml or Yaml but if it's required the `Get-PodeBodyData` function can also deserialize body data from requests, allowing for more complex data handling scenarios where the only allowed ContentTypes are `application/x-www-form-urlencoded` or `multipart/form-data`. This feature can be especially useful when dealing with serialized data structures that require specific interpretation styles.
+
+To enable deserialization, use the `-Deserialize` switch along with the following options:
+
+- **`-NoExplode`**: Prevents deserialization from exploding arrays in the body data. This is useful when dealing with comma-separated values where array expansion is not desired.
+- **`-Style`**: Defines the deserialization style (`'Simple'`, `'Label'`, `'Matrix'`, `'Form'`, `'SpaceDelimited'`, `'PipeDelimited'`, `'DeepObject'`) to interpret the body data correctly. The default style is `'Form'`.
+- **`-KeyName`**: Specifies the key name to use when deserializing, allowing accurate mapping of the body data. The default value for `KeyName` is `'id'`.
+
+### Example with Deserialization
+
+This example demonstrates deserialization of body data using specific styles and options:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Post -Path '/items' -ScriptBlock {
+ # retrieve and deserialize the body data
+ $body = Get-PodeBodyData -Deserialize -Style 'Matrix' -NoExplode
+
+ # get the item based on the deserialized data
+ $item = Get-DummyItem -ItemId $body.id
+
+ # return the item details
+ Write-PodeJsonResponse -Value @{
+ Name = $item.name
+ Quantity = $item.quantity
+ }
+ }
+}
+```
+
+In this example, `Get-PodeBodyData` is used to deserialize the body data with the `'Matrix'` style and prevent array explosion (`-NoExplode`). This approach provides flexible and precise handling of incoming body data, enhancing the capability of your Pode routes to manage complex payloads.
\ No newline at end of file
diff --git a/docs/Tutorials/Routes/Parameters/Cookies.md b/docs/Tutorials/Routes/Parameters/Cookies.md
new file mode 100644
index 000000000..f1222aa2e
--- /dev/null
+++ b/docs/Tutorials/Routes/Parameters/Cookies.md
@@ -0,0 +1,107 @@
+
+# Cookies
+
+The following is an example of using values supplied in a request's cookies. To retrieve values from the cookies, you can use the `Cookies` property from the `$WebEvent` variable.
+
+Alternatively, you can use the `Get-PodeCookie` function to retrieve the cookie data, with additional support for deserialization and secure handling.
+
+This example will get the `SessionId` cookie and use it to authenticate the user, returning a success message:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/authenticate' -ScriptBlock {
+ # get the session ID from the cookie
+ $sessionId = $WebEvent.Cookies['SessionId']
+
+ # authenticate the session
+ $isAuthenticated = Authenticate-Session -SessionId $sessionId
+
+ # return the result
+ Write-PodeJsonResponse -Value @{
+ Authenticated = $isAuthenticated
+ }
+ }
+}
+```
+
+The following request will invoke the above route:
+
+```powershell
+Invoke-WebRequest -Uri 'http://localhost:8080/authenticate' -Method Get -Headers @{ Cookie = 'SessionId=abc123' }
+```
+
+## Using Get-PodeCookie
+
+Alternatively, you can use the `Get-PodeCookie` function to retrieve the cookie data. This function works similarly to the `Cookies` property on `$WebEvent`, but it provides additional options for deserialization and secure cookie handling.
+
+Here is the same example using `Get-PodeCookie`:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/authenticate' -ScriptBlock {
+ # get the session ID from the cookie
+ $sessionId = Get-PodeCookie -Name 'SessionId'
+
+ # authenticate the session
+ $isAuthenticated = Authenticate-Session -SessionId $sessionId
+
+ # return the result
+ Write-PodeJsonResponse -Value @{
+ Authenticated = $isAuthenticated
+ }
+ }
+}
+```
+
+### Deserialization with Get-PodeCookie
+
+The `Get-PodeCookie` function can also deserialize cookie values, allowing for more complex handling of serialized data sent in cookies. This feature is particularly useful when cookies contain encoded or structured content that needs specific parsing.
+
+To enable deserialization, use the `-Deserialize` switch along with the following options:
+
+- **`-NoExplode`**: Prevents deserialization from exploding arrays in the cookie value. This is useful when handling comma-separated values where array expansion is not desired.
+- **`-Deserialize`**: Indicates that the retrieved cookie value should be deserialized, interpreting the content based on the provided deserialization style and options.
+
+
+
+#### Supported Deserialization Styles
+
+| Style | Explode | URI Template | Primitive Value (id = 5) | Array (id = [3, 4, 5]) | Object (id = {"role": "admin", "firstName": "Alex"}) |
+|-------|---------|--------------|--------------------------|------------------------|------------------------------------------------------|
+| form* | true* | | Cookie: id=5 | | |
+| form | false | id={id} | Cookie: id=5 | Cookie: id=3,4,5 | Cookie: id=role,admin,firstName,Alex |
+
+\* Default serialization method
+
+### Example with Deserialization
+
+This example demonstrates deserialization of a cookie value:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/deserialize-cookie' -ScriptBlock {
+ # retrieve and deserialize the 'Session' cookie
+ $sessionData = Get-PodeCookie -Name 'Session' -Deserialize -NoExplode
+
+ # process the deserialized cookie data
+ # (example processing logic here)
+
+ # return the processed cookie data
+ Write-PodeJsonResponse -Value @{
+ SessionData = $sessionData
+ }
+ }
+}
+```
+
+In this example, `Get-PodeCookie` is used to deserialize the `Session` cookie, interpreting it according to the provided deserialization options. The `-NoExplode` switch ensures that any arrays within the cookie value are not expanded during deserialization.
+
+For further information regarding serialization, please refer to the [RFC6570](https://tools.ietf.org/html/rfc6570).
+
+For further information on general usage and retrieving cookies, please refer to the [Headers Documentation](Cookies.md).
\ No newline at end of file
diff --git a/docs/Tutorials/Routes/Parameters/Headers.md b/docs/Tutorials/Routes/Parameters/Headers.md
new file mode 100644
index 000000000..913b860bf
--- /dev/null
+++ b/docs/Tutorials/Routes/Parameters/Headers.md
@@ -0,0 +1,102 @@
+# Headers
+
+The following is an example of using values supplied in a request's headers. To retrieve values from the headers, you can use the `Headers` property from the `$WebEvent.Request` variable. Alternatively, you can use the `Get-PodeHeader` function to retrieve the header data.
+
+This example will get the Authorization header and validate the token, returning a success message:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/validate' -ScriptBlock {
+ # get the token
+ $token = $WebEvent.Request.Headers['Authorization']
+
+ # validate the token
+ $isValid = Test-PodeJwt -payload $token
+
+ # return the result
+ Write-PodeJsonResponse -Value @{
+ Success = $isValid
+ }
+ }
+}
+```
+
+The following request will invoke the above route:
+
+```powershell
+Invoke-WebRequest -Uri 'http://localhost:8080/validate' -Method Get -Headers @{ Authorization = 'Bearer some_token' }
+```
+
+## Using Get-PodeHeader
+
+Alternatively, you can use the `Get-PodeHeader` function to retrieve the header data. This function works similarly to the `Headers` property on `$WebEvent.Request`.
+
+Here is the same example using `Get-PodeHeader`:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/validate' -ScriptBlock {
+ # get the token
+ $token = Get-PodeHeader -Name 'Authorization'
+
+ # validate the token
+ $isValid = Test-PodeJwt -payload $token
+
+ # return the result
+ Write-PodeJsonResponse -Value @{
+ Success = $isValid
+ }
+ }
+}
+```
+
+### Deserialization with Get-PodeHeader
+
+The `Get-PodeHeader` function can also deserialize header values, enabling more advanced handling of serialized data sent in headers. This feature is useful when dealing with complex data structures or when headers contain encoded or serialized content.
+
+To enable deserialization, use the `-Deserialize` switch along with the following options:
+
+- **`-Explode`**: Specifies whether the deserialization process should explode arrays in the header value. This is useful when handling comma-separated values within the header.
+- **`-Deserialize`**: Indicates that the retrieved header value should be deserialized, interpreting the content based on the deserialization style and options.
+
+#### Supported Deserialization Styles
+
+| Style | Explode | URI Template | Primitive Value (X-MyHeader = 5) | Array (X-MyHeader = [3, 4, 5]) | Object (X-MyHeader = {"role": "admin", "firstName": "Alex"}) |
+|---------|---------|--------------|----------------------------------|--------------------------------|--------------------------------------------------------------|
+| simple* | false* | {id} | X-MyHeader: 5 | X-MyHeader: 3,4,5 | X-MyHeader: role,admin,firstName,Alex |
+| simple | true | {id*} | X-MyHeader: 5 | X-MyHeader: 3,4,5 | X-MyHeader: role=admin,firstName=Alex |
+
+\* Default serialization method
+
+### Example with Deserialization
+
+This example demonstrates deserialization of a header value:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/deserialize' -ScriptBlock {
+ # retrieve and deserialize the 'X-SerializedHeader' header
+ $headerData = Get-PodeHeader -Name 'X-SerializedHeader' -Deserialize -Explode
+
+ # process the deserialized header data
+ # (example processing logic here)
+
+ # return the processed header data
+ Write-PodeJsonResponse -Value @{
+ HeaderData = $headerData
+ }
+ }
+}
+```
+
+In this example, `Get-PodeHeader` is used to deserialize the `X-SerializedHeader` header, interpreting it according to the provided deserialization options. The `-Explode` switch ensures that any arrays within the header value are properly expanded during deserialization.
+
+For further information regarding serialization, please refer to the [RFC6570](https://tools.ietf.org/html/rfc6570).
+
+For further information on general usage and retrieving headers, please refer to the [Headers Documentation](Headers.md).
diff --git a/docs/Tutorials/Routes/Parameters/Paths.md b/docs/Tutorials/Routes/Parameters/Paths.md
new file mode 100644
index 000000000..e1a3d3e5a
--- /dev/null
+++ b/docs/Tutorials/Routes/Parameters/Paths.md
@@ -0,0 +1,108 @@
+
+# Paths
+
+The following is an example of using values supplied on a request's URL using parameters. To retrieve values that match a request's URL parameters, you can use the `Parameters` property from the `$WebEvent` variable.
+
+Alternatively, you can use the `Get-PodePathParameter` function to retrieve the parameter data.
+
+This example will get the `:userId` and "find" user, returning the user's data:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/users/:userId' -ScriptBlock {
+ # get the user
+ $user = Get-DummyUser -UserId $WebEvent.Parameters['userId']
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+The following request will invoke the above route:
+
+```powershell
+Invoke-WebRequest -Uri 'http://localhost:8080/users/12345' -Method Get
+```
+
+### Using Get-PodePathParameter
+
+Alternatively, you can use the `Get-PodePathParameter` function to retrieve the parameter data. This function works similarly to the `Parameters` property on `$WebEvent` but provides additional options for deserialization when needed.
+
+Here is the same example using `Get-PodePathParameter`:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/users/:userId' -ScriptBlock {
+ # get the parameter data
+ $userId = Get-PodePathParameter -Name 'userId'
+
+ # get the user
+ $user = Get-DummyUser -UserId $userId
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+#### Deserialization with Get-PodePathParameter
+
+The `Get-PodePathParameter` function can handle deserialization of parameters passed in the URL path, query string, or body, using specific styles to interpret the data correctly. This is useful when dealing with more complex data structures or encoded parameter values.
+
+To enable deserialization, use the `-Deserialize` switch along with the following options:
+
+- **`-Explode`**: Specifies whether to explode arrays when deserializing, useful when parameters contain comma-separated values.
+- **`-Style`**: Defines the deserialization style (`'Simple'`, `'Label'`, or `'Matrix'`) to interpret the parameter value correctly. The default style is `'Simple'`.
+- **`-KeyName`**: Specifies the key name to use when deserializing, allowing you to map the parameter data accurately. The default value for `KeyName` is `'id'`.
+
+#### Supported Deserialization Styles
+
+| Style | Explode | URI Template | Primitive Value (id = 5) | Array (id = [3, 4, 5]) | Object (id = {"role": "admin", "firstName": "Alex"}) |
+|---------|---------|---------------|--------------------------|------------------------|------------------------------------------------------|
+| simple* | false* | /users/{id} | /users/5 | /users/3,4,5 | /users/role,admin,firstName,Alex |
+| simple | true | /users/{id*} | /users/5 | /users/3,4,5 | /users/role=admin,firstName=Alex |
+| label | false | /users/{.id} | /users/.5 | /users/.3,4,5 | /users/.role,admin,firstName,Alex |
+| label | true | /users/{.id*} | /users/.5 | /users/.3.4.5 | /users/.role=admin.firstName=Alex |
+| matrix | false | /users/{;id} | /users/;id=5 | /users/;id=3,4,5 | /users/;id=role,admin,firstName,Alex |
+| matrix | true | /users/{;id*} | /users/;id=5 | /users/;id=3;id=4;id=5 | /users/;role=admin;firstName=Alex |
+
+\* Default serialization method
+
+#### Example with Deserialization
+
+This example demonstrates deserialization of a parameter that is styled and exploded as part of the request:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/items/:itemId' -ScriptBlock {
+ # retrieve and deserialize the 'itemId' parameter
+ $itemId = Get-PodePathParameter -Name 'itemId' -Deserialize -Style 'Label' -Explode
+
+ # get the item based on the deserialized data
+ $item = Get-DummyItem -ItemId $itemId
+
+ # return the item details
+ Write-PodeJsonResponse -Value @{
+ Name = $item.name
+ Quantity = $item.quantity
+ }
+ }
+}
+```
+
+In this example, the `Get-PodePathParameter` function is used to deserialize the `itemId` parameter, interpreting it according to the specified style (`Label`) and handling arrays if present (`-Explode`). The default `KeyName` is `'id'`, but it can be customized as needed. This approach allows for dynamic and precise handling of incoming request data, making your Pode routes more versatile and resilient.
+
+For further information regarding serialization, please refer to the [RFC6570](https://tools.ietf.org/html/rfc6570).
\ No newline at end of file
diff --git a/docs/Tutorials/Routes/Parameters/Queries.md b/docs/Tutorials/Routes/Parameters/Queries.md
new file mode 100644
index 000000000..41b839b98
--- /dev/null
+++ b/docs/Tutorials/Routes/Parameters/Queries.md
@@ -0,0 +1,107 @@
+# Queries
+
+The following is an example of using data from a request's query string. To retrieve values from the query parameters, you can use the `Query` property on the `$WebEvent` variable in a route's logic.
+
+Alternatively, you can use the `Get-PodeQueryParameter` function to retrieve the query parameter data, with additional support for deserialization.
+
+This example will return a user based on the `userId` supplied:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {
+ # get the user
+ $user = Get-DummyUser -UserId $WebEvent.Query['userId']
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+The following request will invoke the above route:
+
+```powershell
+Invoke-WebRequest -Uri 'http://localhost:8080/users?userId=12345' -Method Get
+```
+
+### Using Get-PodeQueryParameter
+
+Alternatively, you can use the `Get-PodeQueryParameter` function to retrieve the query data. This function works similarly to the `Query` property on `$WebEvent` but provides additional options for deserialization when needed.
+
+Here is the same example using `Get-PodeQueryParameter`:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/users' -ScriptBlock {
+ # get the query data
+ $userId = Get-PodeQueryParameter -Name 'userId'
+
+ # get the user
+ $user = Get-DummyUser -UserId $userId
+
+ # return the user
+ Write-PodeJsonResponse -Value @{
+ Username = $user.username
+ Age = $user.age
+ }
+ }
+}
+```
+
+#### Deserialization with Get-PodeQueryParameter
+
+The `Get-PodeQueryParameter` function can also deserialize query parameters passed in the URL, using specific styles to interpret the data correctly. This feature is particularly useful when handling complex data structures or encoded parameter values.
+
+To enable deserialization, use the `-Deserialize` switch along with the following options:
+
+- **`-NoExplode`**: Prevents deserialization from exploding arrays when handling comma-separated values. This is useful when array expansion is not desired.
+- **`-Style`**: Defines the deserialization style (`'Simple'`, `'Label'`, `'Matrix'`, `'Form'`, `'SpaceDelimited'`, `'PipeDelimited'`, `'DeepObject'`) to interpret the query parameter value correctly. The default style is `'Form'`.
+- **`-KeyName`**: Specifies the key name to use when deserializing, allowing you to map the query parameter data accurately. The default value for `KeyName` is `'id'`.
+
+#### Supported Deserialization Styles
+
+
+| Style | Explode | URI Template | Primitive Value (id = 5) | Array (id = [3, 4, 5]) | Object (id = {"role": "admin", "firstName": "Alex"}) |
+|----------------|---------|--------------|--------------------------|------------------------|------------------------------------------------------|
+| form* | true* | /users{?id*} | /users?id=5 | /users?id=3&id=4&id=5 | /users?role=admin&firstName=Alex |
+| form | false | /users{?id} | /users?id=5 | /users?id=3,4,5 | /users?id=role,admin,firstName,Alex |
+| spaceDelimited | true | /users{?id*} | n/a | /users?id=3&id=4&id=5 | n/a |
+| spaceDelimited | false | n/a | n/a | /users?id=3%204%205 | n/a |
+| pipeDelimited | true | /users{?id*} | n/a | /users?id=3&id=4&id=5 | n/a |
+| pipeDelimited | false | n/a | n/a | /users?id=3\|4\|5 | n/a |
+| deepObject | true | n/a | n/a | n/a | /users?id[role]=admin&id[firstName]=Alex |
+
+
+\* Default serialization method
+
+#### Example with Deserialization
+
+This example demonstrates deserialization of a query parameter with specific styles and options:
+
+```powershell
+Start-PodeServer {
+ Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
+
+ Add-PodeRoute -Method Get -Path '/items' -ScriptBlock {
+ # retrieve and deserialize the 'filter' query parameter
+ $filter = Get-PodeQueryParameter -Name 'filter' -Deserialize -Style 'SpaceDelimited' -NoExplode
+
+ # get items based on the deserialized filter data
+ $items = Get-DummyItems -Filter $filter
+
+ # return the item details
+ Write-PodeJsonResponse -Value $items
+ }
+}
+```
+
+In this example, the `Get-PodeQueryParameter` function is used to deserialize the `filter` query parameter, interpreting it according to the specified style (`SpaceDelimited`) and preventing array explosion (`-NoExplode`). This approach allows for dynamic and precise handling of complex query data, enhancing the flexibility of your Pode routes.
+
+For further information regarding serialization, please refer to the [RFC6570](https://tools.ietf.org/html/rfc6570).
\ No newline at end of file
diff --git a/docs/Tutorials/SharedState.md b/docs/Tutorials/SharedState.md
index b34ba137e..b596c2721 100644
--- a/docs/Tutorials/SharedState.md
+++ b/docs/Tutorials/SharedState.md
@@ -1,79 +1,122 @@
# Shared State
-Most things in Pode run in isolated runspaces: routes, middleware, schedules - to name a few. This means you can't create a variable in a timer, and then access that variable in a route. To overcome this limitation you can use the Shared State feature within Pode, which allows you to set/get variables on a state shared between all runspaces. This lets you can create a variable in a timer and store it within the shared state; then you can retrieve the variable from the state in a route.
+Most things in Pode run in isolated runspaces: routes, middleware, schedules - to name a few. This means you can't create a variable in a timer, and then access that variable in a route. To overcome this limitation you can use the Shared State feature within Pode, which allows you to set/get variables on a state shared between all runspaces. This lets you create a variable in a timer and store it within the shared state; then you can retrieve the variable from the state in a route.
You also have the option of saving the current state to a file, and then restoring the state back on server start. This way you won't lose state between server restarts.
-You can also use the State in combination with [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject) to ensure thread safety - if needed.
+Pode supports various structures for shared state, some of which are thread-safe:
+
+**Thread-Safe Structures:**
+
+- `ConcurrentDictionary`
+- `ConcurrentBag`
+- `ConcurrentQueue`
+- `ConcurrentStack`
+
+**Non-Thread-Safe Structures (Require Locking):**
+
+- `OrderedDictionary`
+- `Hashtable`
+- `PSCustomObject`When using a thread-safe object, `Lock-PodeObject` is no longer required.
!!! tip
- It's wise to use the State in conjunction with [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject), to ensure thread safety between runspaces.
+It's wise to use the State in conjunction with [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject) when dealing with non-thread-safe objects to ensure thread safety between runspaces.
!!! warning
- If you omit the use of [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject), you might run into errors due to multi-threading. Only omit if you are *absolutely confident* you do not need locking. (ie: you set in state once and then only ever retrieve, never updating the variable).
+If you are using a non-thread-safe object, such as `Hashtable`, `OrderedDictionary`, or `PSCustomObject`, you should use [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject) to ensure thread safety between runspaces. Omitting this may lead to concurrency issues and unpredictable behavior.
## Usage
-Where possible use the same casing for the `-Name` of state keys. When using [`Restore-PodeState`](../../Functions/State/Restore-PodeState) the state will become case-sensitive due to the nature of how `ConvertFrom-Json` works.
+Where possible, use the same casing for the `-Name` of state keys. When using [`Restore-PodeState`](../../Functions/State/Restore-PodeState), the state will become case-sensitive due to the nature of how `ConvertFrom-Json` works.
### Set
-The [`Set-PodeState`](../../Functions/State/Set-PodeState) function will create/update a variable in the state. You need to supply a name and a value to set on the state, there's also an optional scope that can be supplied - which lets you save specific state objects with a certain scope.
+#### **NewCollectionType Parameter**
+
+The `-NewCollectionType` parameter allows users to specify the type of collection to initialize within the shared state. This eliminates the need to manually instantiate collections before setting them in the state.
+
+**Supported Collection Types:**
+
+- `Hashtable`
+- `ConcurrentDictionary`
+- `OrderedDictionary`
+- `ConcurrentBag`
+- `ConcurrentQueue`
+- `ConcurrentStack`
+
+If `-NewCollectionType` is used, the specified collection type will be created and stored in the state. The `-Value` parameter is ignored when this option is used.
-An example of setting a hashtable variable in the state is as follows:
+**Examples:**
```powershell
-Start-PodeServer {
- Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
- Lock-PodeObject -ScriptBlock {
- Set-PodeState -Name 'data' -Value @{ 'Name' = 'Rick Sanchez' } | Out-Null
- }
- }
-}
+# Set a simple hashtable in shared state
+Set-PodeState -Name 'Data' -Value @{ 'Name' = 'Rick Sanchez' }
+
+# Initialize a ConcurrentDictionary instead of providing a value
+Set-PodeState -Name 'Cache' -NewCollectionType 'ConcurrentDictionary'
+
+# Create a ConcurrentQueue for shared state management
+Set-PodeState -Name 'Tasks' -NewCollectionType 'ConcurrentQueue'
+```
+
+The [`Set-PodeState`](../../Functions/State/Set-PodeState) function will create/update a variable in the state. You need to supply a name and a value to set on the state, and there's also an optional scope that can be supplied - which lets you save specific state objects with a certain scope.
+
+!!! tip
+The .NET collections `ConcurrentDictionary` and `OrderedDictionary` are case-sensitive by default. To make them case-insensitive, initialize them as follows:
+
+```powershell
+# Case-insensitive ConcurrentDictionary
+Set-PodeState -Name 'Cache' -Value ([System.Collections.Concurrent.ConcurrentDictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase))
+
+# Case-insensitive OrderedDictionary
+Set-PodeState -Name 'Config' -Value ([System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::OrdinalIgnoreCase))
+
+# Case-insensitive OrderedDictionary
+Set-PodeState -Name 'Running' -Value ([ordered]@{})
+```
+
+Alternatively, you can use:
+
+```powershell
+Set-PodeState -Name 'Cache' -NewCollectionType 'ConcurrentDictionary'
+Set-PodeState -Name 'Config' -NewCollectionType 'OrderedDictionary'
+Set-PodeState -Name 'Running' -NewCollectionType 'OrderedDictionary'
```
-Alternatively you could use the `$state:` variable scope to set a variable in state. This variable will be scopeless, so if you need scope then use [`Set-PodeState`](../../Functions/State/Set-PodeState). `$state:` can be used anywhere, but keep in mind that like `$session:` Pode can only remap the this in scriptblocks it's aware of; so using it in a function of a custom module won't work. Similar to the example above:
+#### Example: Non-Thread-Safe Objects
+
+If using a non-thread-safe object, such as `Hashtable`, `OrderedDictionary`, or `PSCustomObject`, wrap access to the state in `Lock-PodeObject` to prevent concurrency issues.
```powershell
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
Lock-PodeObject -ScriptBlock {
- $state:data = @{ 'Name' = 'Rick Sanchez' }
+ Set-PodeState -Name 'data' -Value @{ 'Name' = 'Rick Sanchez' } | Out-Null
}
}
}
```
-### Get
+The [`Set-PodeState`](../../Functions/State/Set-PodeState) function will create/update a variable in the state. You need to supply a name and a value to set on the state, and there's also an optional scope that can be supplied - which lets you save specific state objects with a certain scope.
-The [`Get-PodeState`](../../Functions/State/Get-PodeState) function will return the value currently stored in the state for a variable. If the variable doesn't exist then `$null` is returned.
-
-An example of retrieving a value from the state is as follows:
+An example of setting a `ConcurrentDictionary` variable in the state is as follows:
```powershell
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
- $value = $null
-
- Lock-PodeObject -ScriptBlock {
- $value = (Get-PodeState -Name 'data')
- }
-
- # do something with $value
+ Set-PodeState -Name 'data' -Value ([System.Collections.Concurrent.ConcurrentDictionary[string, string]]::new()) | Out-Null
}
}
```
-Alternatively you could use the `$state:` variable scope to get a variable in state. `$state:` can be used anywhere, but keep in mind that like `$session:` Pode can only remap the this in scriptblocks it's aware of; so using it in a function of a custom module won't work. Similar to the example above:
+### Get
+
+The [`Get-PodeState`](../../Functions/State/Get-PodeState) function will return the value currently stored in the state for a variable. If the variable doesn't exist, `$null` is returned.
```powershell
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
- $value = $null
-
- Lock-PodeObject -ScriptBlock {
- $value = $state:data
- }
+ $value = (Get-PodeState -Name 'data')
# do something with $value
}
@@ -84,45 +127,29 @@ Start-PodeServer {
The [`Remove-PodeState`](../../Functions/State/Remove-PodeState) function will remove a variable from the state. It will also return the value stored in the state before removing the variable.
-An example of removing a variable from the state is as follows:
-
```powershell
Start-PodeServer {
Add-PodeTimer -Name 'do-something' -Interval 5 -ScriptBlock {
- Lock-PodeObject -ScriptBlock {
- Remove-PodeState -Name 'data' | Out-Null
- }
+ Remove-PodeState -Name 'data' | Out-Null
}
}
```
### Save
-The [`Save-PodeState`](../../Functions/State/Save-PodeState) function will save the current state, as JSON, to the specified file. The file path can either be relative, or literal. When saving the state, it's recommended to wrap the function within [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject).
-
-An example of saving the current state every hour is as follows:
+The [`Save-PodeState`](../../Functions/State/Save-PodeState) function will save the current state, as JSON, to the specified file. The file path can either be relative or literal. When saving the state, it's recommended to wrap the function within [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject) if dealing with non-thread-safe objects.
```powershell
Start-PodeServer {
Add-PodeSchedule -Name 'save-state' -Cron '@hourly' -ScriptBlock {
- Lock-PodeObject -ScriptBlock {
- Save-PodeState -Path './state.json'
- }
+ Save-PodeState -Path './state.json'
}
}
```
-When saving the state, you can also use the `-Exclude` or `-Include` parameters to exclude/include certain state objects from being saved. Saving also has a `-Scope` parameter, which allows you so save only state objects created with the specified scope(s).
-
-You can use all the above 3 parameter in conjunction, with `-Exclude` having the highest precedence and `-Scope` having the lowest.
-
-By default the JSON will be saved expanded, but you can saved the JSON as compressed by supplying the `-Compress` switch.
-
### Restore
-The [`Restore-PodeState`](../../Functions/State/Restore-PodeState) function will restore the current state from the specified file. The file path can either be relative, or a literal path. if you're restoring the state immediately on server start, you don't need to use [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject).
-
-An example of restore the current state on server start is as follows:
+The [`Restore-PodeState`](../../Functions/State/Restore-PodeState) function will restore the current state from the specified file. The file path can either be relative or a literal path. If you're restoring the state immediately on server start, you don't need to use [`Lock-PodeObject`](../../Functions/Threading/Lock-PodeObject).
```powershell
Start-PodeServer {
@@ -130,54 +157,37 @@ Start-PodeServer {
}
```
-By default, restoring from a state file will overwrite the current state. You can change this so the restored state is merged instead by using the `-Merge` switch. (Note: if you restore a key that already exists in state, this will still overwrite that key).
-
## Full Example
-The following is a full example of using the State functions. It is a simple Timer that creates and updates a `hashtable` variable, and then a Route is used to retrieve that variable. There is also another route that will remove the variable from the state. The state is also saved on every iteration of the timer, and restored on server start:
+The following is a full example of using the State functions. It is a simple Timer that creates and updates a `ConcurrentDictionary` variable, and then a Route is used to retrieve that variable. There is also another route that will remove the variable from the state. The state is also saved on every iteration of the timer and restored on server start:
```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http
# create the shared variable
- Set-PodeState -Name 'hash' -Value @{ 'values' = @(); } | Out-Null
+ Set-PodeState -Name 'dict' -Value ([System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()) | Out-Null
- # attempt to re-initialise the state (will do nothing if the file doesn't exist)
+ # attempt to re-initialize the state (will do nothing if the file doesn't exist)
Restore-PodeState -Path './state.json'
# timer to add a random number to the shared state
Add-PodeTimer -Name 'forever' -Interval 2 -ScriptBlock {
- # ensure we're thread safe
- Lock-PodeObject -ScriptBlock {
- # attempt to get the hashtable from the state
- $hash = (Get-PodeState -Name 'hash')
-
- # add a random number
- $hash.values += (Get-Random -Minimum 0 -Maximum 10)
-
- # save the state to file
- Save-PodeState -Path './state.json'
- }
+ $dict = (Get-PodeState -Name 'dict')
+ $dict["random"] = (Get-Random -Minimum 0 -Maximum 10)
+ Save-PodeState -Path './state.json'
}
- # route to return the value of the hashtable from shared state
+ # route to return the value of the dictionary from shared state
Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
- # again, ensure we're thread safe
- Lock-PodeObject -ScriptBlock {
- # get the hashtable from the state and return it
- $hash = (Get-PodeState -Name 'hash')
- Write-PodeJsonResponse -Value $hash
- }
+ $dict = (Get-PodeState -Name 'dict')
+ Write-PodeJsonResponse -Value $dict
}
- # route to remove the hashtable from shared state
+ # route to remove the dictionary from shared state
Add-PodeRoute -Method Delete -Path '/' -ScriptBlock {
- # ensure we're thread safe
- Lock-PodeObject -ScriptBlock {
- # remove the hashtable from the state
- Remove-PodeState -Name 'hash' | Out-Null
- }
+ Remove-PodeState -Name 'dict' | Out-Null
}
}
```
+
diff --git a/docs/Tutorials/Ssl.md b/docs/Tutorials/Ssl.md
new file mode 100644
index 000000000..1d65ad098
--- /dev/null
+++ b/docs/Tutorials/Ssl.md
@@ -0,0 +1,85 @@
+# SSL Protocols
+
+By default, the server chooses the allowed SSL/TLS protocols based on the operating system’s native support.
+
+For example, on Windows 11 and Windows Server 2022 only TLS 1.2 and TLS 1.3 are enabled, while older systems (such as Windows Vista/Server 2008) allow SSL 2.0 and SSL 3.0. This behavior follows the table below:
+
+| Operating System | SSL 2.0 | SSL 3.0 | TLS 1.0 | TLS 1.1 | TLS 1.2 | TLS 1.3 |
+|------------------------------------|---------------------|---------------------|---------------------|---------------------|--------------------|--------------------|
+| Windows Vista / Server 2008 | Enabled | Enabled | Not Supported | Not Supported | Not Supported | Not Supported |
+| Windows 7 / Server 2008 R2 | Enabled | Enabled | Disabled | Disabled | Disabled | Not Supported |
+| Windows 8 / Server 2012 | Disabled by Default | Enabled by Default | Enabled by Default | Enabled by Default | Enabled by Default | Not Supported |
+| Windows 10 (Build 20170 and later) | No | Disabled by Default | Enabled by Default | Enabled by Default | Enabled by Default | Enabled by Default |
+| Windows 11 / Server 2022 | No | Disabled by Default | Disabled by Default | Disabled by Default | Enabled by Default | Enabled by Default |
+| macOS 10.8 - 10.10 | No | Yes | Yes | Yes | Yes | No |
+| macOS 10.11 | No | No | Yes | Yes | Yes | No |
+| macOS 10.13 and later | No | No | Yes | Yes | Yes | Yes |
+| Linux (OpenSSL 1.0.1 - 1.0.1f) | No | Yes | Yes | Yes | Yes | No |
+| Linux (OpenSSL 1.0.1g and later) | No | No | Yes | Yes | Yes | No |
+| Linux (OpenSSL 1.1.1 and later) | No | No | Yes | Yes | Yes | Yes |
+
+**Notes:**
+
+- **Windows Operating Systems:**
+ - TLS 1.3 is supported starting from Windows 10 Build 20170 and Windows Server 2022.
+ - Earlier versions (like Windows 7 and Windows Server 2008 R2) support up to TLS 1.2, but may require manual configuration to enable it.
+
+- **macOS:**
+ - TLS 1.3 support begins with macOS 10.13.
+
+- **Linux:**
+ - The supported SSL/TLS protocols on Linux systems depend on the version of OpenSSL installed:
+ - OpenSSL versions 1.0.1 to 1.0.1f support up to TLS 1.2, with SSL 3.0 enabled by default.
+ - OpenSSL version 1.0.1g and later disable SSL 3.0 by default.
+ - OpenSSL version 1.1.1 and later add support for TLS 1.3.
+
+## Override the Default Values
+
+If you wish to override the defaults, you can customize the allowed protocols in your `server.psd1` configuration file. For example, if you want to allow only TLS protocols (excluding the deprecated SSL versions), you can configure it as follows:
+
+```powershell
+@{
+ Server = @{
+ Ssl = @{
+ Protocols = @('Tls', 'Tls11', 'Tls12')
+ }
+ }
+}
+```
+
+Or, to include TLS 1.3 where supported:
+
+```powershell
+@{
+ Server = @{
+ Ssl = @{
+ Protocols = @('Tls', 'Tls11', 'Tls12', 'Tls13')
+ }
+ }
+}
+```
+
+This configuration allows you to explicitly set the protocols from the following list of supported values: `'Ssl2'`, `'Ssl3'`, `'Tls'`, `'Tls11'`, `'Tls12'`, and `'Tls13'`.
+
+> **Important:** Overriding these default values in your configuration file does **not** automatically enable the corresponding protocols at the operating system level. The OS may block a protocol unless its native settings are also changed. In other words, even if you add `'Ssl3'` to your allowed protocols, Windows 11 will still reject SSLv3 connections unless you modify the OS settings.
+
+### Example: Enabling SSLv3 on Windows 11
+
+By default, Windows 11 disables SSLv3 in its Schannel settings. To enable SSLv3, you need to change the registry settings. **Proceed with caution** as enabling SSLv3 can expose your system to known vulnerabilities (such as the POODLE attack).
+
+You can enable SSLv3 on Windows 11 using PowerShell as follows:
+
+```powershell
+# Create the registry keys for SSL 3.0 if they don't already exist
+New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0" -Force | Out-Null
+New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client" -Force | Out-Null
+New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server" -Force | Out-Null
+
+# Enable SSLv3 for both client and server by setting the Enabled DWORD to 1
+Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client" -Name "Enabled" -Value 1 -Type DWord
+Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server" -Name "Enabled" -Value 1 -Type DWord
+
+Write-Output "SSLv3 has been enabled. A system restart may be required for the changes to take effect."
+```
+
+After making these changes, your Windows 11 system will accept SSLv3 connections. Remember that this registry modification is an OS-level change, and overriding the configuration in `server.psd1` alone will not suffice.
diff --git a/docs/index.md b/docs/index.md
index 25d0d9dd9..22b75db5a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -18,36 +18,37 @@ Pode is a Cross-Platform framework to create web servers that host REST APIs, We
## 🚀 Features
-* Cross-platform using PowerShell Core (with support for PS5)
-* Docker support, including images for ARM/Raspberry Pi
-* Azure Functions, AWS Lambda, and IIS support
-* OpenAPI specification version 3.0.x and 3.1.0
-* OpenAPI documentation with Swagger, Redoc, RapidDoc, StopLight, OpenAPI-Explorer and RapiPdf
-* Listen on a single or multiple IP(v4/v6) addresses/hostnames
-* Cross-platform support for HTTP(S), WS(S), SSE, SMTP(S), and TCP(S)
-* Host REST APIs, Web Pages, and Static Content (with caching)
-* Support for custom error pages
-* Request and Response compression using GZip/Deflate
-* Multi-thread support for incoming requests
-* Inbuilt template engine, with support for third-parties
-* Async timers for short-running repeatable processes
-* Async scheduled tasks using cron expressions for short/long-running processes
-* Supports logging to CLI, Files, and custom logic for other services like LogStash
-* Cross-state variable access across multiple runspaces
-* Restart the server via file monitoring, or defined periods/times
-* Ability to allow/deny requests from certain IP addresses and subnets
-* Basic rate limiting for IP addresses and subnets
-* Middleware and Sessions on web servers, with Flash message and CSRF support
-* Authentication on requests, such as Basic, Windows and Azure AD
-* Authorisation support on requests, using Roles, Groups, Scopes, etc.
-* Support for dynamically building Routes from Functions and Modules
-* Generate/bind self-signed certificates
-* Secret management support to load secrets from vaults
-* Support for File Watchers
-* In-memory caching, with optional support for external providers (such as Redis)
-* (Windows) Open the hosted server as a desktop application
-* FileBrowsing support
-* Localization (i18n) in Arabic, German, Spanish, France, Italian, Japanese, Korean, Polish, Portuguese, and Chinese
+* ✅ Cross-platform using PowerShell Core (with support for PS5)
+* ✅ Docker support, including images for ARM/Raspberry Pi
+* ✅ Azure Functions, AWS Lambda, and IIS support
+* ✅ OpenAPI specification version 3.0.x and 3.1.0
+* ✅ OpenAPI documentation with Swagger, Redoc, RapidDoc, StopLight, OpenAPI-Explorer and RapiPdf
+* ✅ Listen on a single or multiple IP(v4/v6) addresses/hostnames
+* ✅ Cross-platform support for HTTP(S), WS(S), SSE, SMTP(S), and TCP(S)
+* ✅ Host REST APIs, Web Pages, and Static Content (with caching)
+* ✅ Support for custom error pages
+* ✅ Request and Response compression using GZip/Deflate
+* ✅ Multi-thread support for incoming requests
+* ✅ Inbuilt template engine, with support for third-parties
+* ✅ Async timers for short-running repeatable processes
+* ✅ Async scheduled tasks using cron expressions for short/long-running processes
+* ✅ Supports logging to CLI, Files, and custom logic for other services like LogStash
+* ✅ Cross-state variable access across multiple runspaces
+* ✅ Restart the server via file monitoring, or defined periods/times
+* ✅ Ability to allow/deny requests from certain IP addresses and subnets
+* ✅ Basic rate limiting for IP addresses and subnets
+* ✅ Middleware and Sessions on web servers, with Flash message and CSRF support
+* ✅ Authentication on requests, such as Basic, Windows and Azure AD
+* ✅ Authorisation support on requests, using Roles, Groups, Scopes, etc.
+* ✅ Enhanced authentication support, including Basic, Bearer (with JWT), Certificate, Digest, Form, OAuth2, and ApiKey (with JWT).
+* ✅ Support for dynamically building Routes from Functions and Modules
+* ✅ Generate/bind self-signed certificates
+* ✅ Secret management support to load secrets from vaults
+* ✅ Support for File Watchers
+* ✅ In-memory caching, with optional support for external providers (such as Redis)
+* ✅ (Windows) Open the hosted server as a desktop application
+* ✅ FileBrowsing support
+* ✅ Localization (i18n) in Arabic, German, Spanish, France, Italian, Japanese, Korean, Polish, Portuguese,Dutch and Chinese
## 🏢 Companies using Pode
diff --git a/examples/Authentication/Modules/Invoke-Digest.psm1 b/examples/Authentication/Modules/Invoke-Digest.psm1
new file mode 100644
index 000000000..07672527f
--- /dev/null
+++ b/examples/Authentication/Modules/Invoke-Digest.psm1
@@ -0,0 +1,662 @@
+function ConvertTo-Hash {
+ param (
+ [string]$Value,
+ [string]$Algorithm
+ )
+
+ $crypto = switch ($Algorithm) {
+ 'MD5' { [System.Security.Cryptography.MD5]::Create() }
+ 'SHA-1' { [System.Security.Cryptography.SHA1]::Create() }
+ 'SHA-256' { [System.Security.Cryptography.SHA256]::Create() }
+ 'SHA-384' { [System.Security.Cryptography.SHA384]::Create() }
+ 'SHA-512' { [System.Security.Cryptography.SHA512]::Create() }
+ 'SHA-512/256' {
+ # Compute SHA-512 and take first 32 bytes (256 bits)
+ $sha512 = [System.Security.Cryptography.SHA512]::Create()
+ $fullHash = $sha512.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))
+ return [System.BitConverter]::ToString($fullHash[0..31]).Replace('-', '').ToLowerInvariant()
+ }
+ }
+
+ return [System.BitConverter]::ToString($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Value))).Replace('-', '').ToLowerInvariant()
+}
+
+function ChallengeDigest {
+ param(
+ [Parameter(Mandatory = $true)]
+ [ValidateSet('Connect', 'Delete', 'Get', 'Head', 'Merge', 'Options', 'Patch', 'Post', 'Put', 'Trace')]
+ [string]$Method,
+
+ [Parameter(Mandatory = $true)]
+ [string]$Uri
+ )
+ # Create an HTTP client
+ $handler = [System.Net.Http.HttpClientHandler]::new()
+ $httpClient = [System.Net.Http.HttpClient]::new($handler)
+
+ # Step 1: Send an initial request to get the challenge
+ $initialRequest = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::$Method, $Uri)
+ $initialResponse = $httpClient.SendAsync($initialRequest).Result
+ if ($null -eq $initialResponse) {
+ Throw "Server $Uri is not responding"
+ }
+
+ # Extract WWW-Authenticate headers safely
+ $wwwAuthHeaders = $initialResponse.Headers.GetValues('WWW-Authenticate')
+ # Filter to get only the Digest authentication scheme
+ $wwwAuthHeader = $wwwAuthHeaders | Where-Object { $_ -match '^Digest' }
+
+ Write-Verbose 'Extracted WWW-Authenticate headers:'
+ $wwwAuthHeaders | ForEach-Object { Write-Verbose " - $_" }
+
+ if (-not $wwwAuthHeader) {
+ Throw 'Digest authentication not supported by server!'
+ }
+
+ # Extract Digest Authentication challenge values
+ $challenge = @{}
+
+ if ($wwwAuthHeader -match '^Digest ') {
+ $headerContent = $wwwAuthHeader -replace '^Digest ', ''
+ Write-Verbose "RAW HEADER: $headerContent"
+
+ # 1) CAPTURE supported algorithms
+ if ($headerContent -match 'algorithm=((?:SHA-1|SHA-256|SHA-384|SHA-512(?:/256)?|MD5)(?:,\s*(?:SHA-1|SHA-256|SHA-384|SHA-512(?:/256)?|MD5))*)') {
+ $algorithms = ($matches[1] -split '\s*,\s*')
+ Write-Verbose "Supported Algorithms: $algorithms"
+ $challenge['algorithm'] = $algorithms
+ }
+
+ # 2) REMOVE algorithm parameter
+ $headerContent = $headerContent -replace 'algorithm=(?:SHA-1|SHA-256|SHA-384|SHA-512(?:/256)?|MD5)(?:,\s*(?:SHA-1|SHA-256|SHA-384|SHA-512(?:/256)?|MD5))*\s*,?', ''
+ # 3) CLEAN UP extra commas/whitespace
+ $headerContent = $headerContent -replace ',\s*,', ','
+ $headerContent = $headerContent -replace '^\s*,', ''
+
+ # Split remaining parameters safely
+ $headerContent -split ', ' | ForEach-Object {
+ $key, $value = $_ -split '=', 2
+ if ($key -and $value) {
+ $challenge[$key.Trim()] = $value.Trim('"')
+ }
+ }
+ }
+
+ Write-Verbose 'Extracted Digest Authentication Challenge:'
+ $challenge.GetEnumerator() | ForEach-Object { Write-Verbose "$($_.Key) = $($_.Value)" }
+
+ $realm = $challenge['realm']
+ $nonce = $challenge['nonce']
+ $qop = $challenge['qop']
+ $algorithm = $challenge['algorithm']
+
+ if (('Post', 'Put', 'Patch') -contains $Method) {
+ if ($qop -eq 'auth-int' -or $qop -eq 'auth,auth-int') {
+ $qop = 'auth-int'
+ }
+ else {
+ $qop = 'auth'
+ }
+ }
+ else {
+ if ($qop -eq 'auth' -or $qop -eq 'auth,auth-int') {
+ $qop = 'auth'
+ }
+ else {
+ throw "$Method doesn't support QualityOfProtection 'auth-int'"
+ }
+ }
+
+ Write-Verbose "Selected QOP: $qop"
+
+ $preferredAlgorithms = @('SHA-512/256', 'SHA-512', 'SHA-384', 'SHA-256', 'SHA-1', 'MD5')
+ if ($algorithm -isnot [System.Array]) {
+ $algorithm = @($algorithm)
+ }
+ $algorithm = ($preferredAlgorithms | Where-Object { $algorithm -contains $_ } | Select-Object -First 1)
+ if (-not $algorithm) {
+ Throw "No supported algorithms found! Server supports: $algorithm"
+ }
+ return [PSCustomObject]@{
+ realm = $realm
+ nonce = $nonce
+ qop = $qop
+ algorithm = $algorithm
+ wwwAuthHeader = $wwwAuthHeader
+ uri = $Uri
+ httpClient = $httpClient
+ method = $Method
+ }
+}
+
+<#
+.SYNOPSIS
+ Sends an HTTP request using Digest authentication and returns a web response.
+
+.DESCRIPTION
+ The Invoke-WebRequestDigest function performs an HTTP request with Digest authentication,
+ handling HTTP headers, authentication challenges, retries, and timeouts.
+ It returns a BasicHtmlWebResponseObject similar to Invoke-WebRequest.
+
+.PARAMETER Uri
+ The target URI for the request.
+
+.PARAMETER Method
+ The HTTP method to use for the request. Default is 'GET'.
+
+.PARAMETER Body
+ The request body, required for methods like POST, PUT, and PATCH.
+
+.PARAMETER Credential
+ The PSCredential object containing the username and password for Digest authentication.
+
+.PARAMETER Headers
+ A hashtable of additional headers to include in the request.
+
+.PARAMETER ContentType
+ The Content-Type of the request body. Default is 'application/json'.
+
+.PARAMETER OperationTimeoutSeconds
+ The maximum time in seconds before the request times out. Default is 100.
+
+.PARAMETER ConnectionTimeoutSeconds
+ The timeout in seconds for establishing a connection. Default is 100.
+
+.PARAMETER DisableKeepAlive
+ If specified, disables persistent connections by adding the 'Connection: close' header.
+
+.PARAMETER HttpVersion
+ The HTTP version to use, such as '1.1' or '2.0'. Default is '1.1'.
+
+.PARAMETER MaximumRetryCount
+ The number of times to retry the request in case of failure. Default is 1.
+
+.PARAMETER RetryIntervalSec
+ The interval in seconds between retry attempts. Default is 1.
+
+.PARAMETER OutFile
+ If specified, writes the response body to the specified file instead of returning content.
+
+.PARAMETER PassThru
+ If specified, returns the response object even if OutFile is used.
+
+.PARAMETER SkipCertificateCheck
+ If specified, disables SSL certificate validation (useful for self-signed certificates).
+
+.PARAMETER SslProtocol
+ Specifies the allowed SSL/TLS protocol(s) to use (e.g., 'Tls12').
+
+.PARAMETER TransferEncoding
+ The value for the 'Transfer-Encoding' header.
+
+.PARAMETER UserAgent
+ The User-Agent string to use in the request.
+
+.OUTPUTS
+ - Returns a [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject].
+ - If OutFile is specified, writes response data to the specified file.
+
+.EXAMPLE
+ $cred = Get-Credential
+ $response = Invoke-WebRequestDigest -Uri 'https://example.com/data' -Method 'GET' -Credential $cred
+ Write-Output $response.Content
+
+.EXAMPLE
+ $body = @{ "name" = "John Doe"; "email" = "john@example.com" }
+ $cred = Get-Credential
+ $response = Invoke-WebRequestDigest -Uri 'https://example.com/users' -Method 'POST' -Credential $cred -Body $body -ContentType 'application/json'
+ Write-Output $response.Content
+
+.EXAMPLE
+ # Download file
+ Invoke-WebRequestDigest -Uri 'https://example.com/file.zip' -Method 'GET' -Credential $cred -OutFile 'C:\Downloads\file.zip'
+
+.NOTES
+ - This function provides full control over HTTP requests with Digest authentication.
+ - Supports custom headers, connection options, timeouts, and retries.
+ - Unlike Invoke-RestMethodDigest, this function does not automatically parse JSON/XML.
+#>
+function Invoke-WebRequestDigest {
+ [CmdletBinding(DefaultParameterSetName = 'Uri')]
+ [OutputType([Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject])]
+ param(
+ # URI of the request (required)
+ [Parameter(Mandatory = $true, Position = 0)]
+ [Uri]$Uri,
+
+ # HTTP method (default GET)
+ [Parameter(Position = 1)]
+ [ValidateSet('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'PATCH', 'MERGE', 'CONNECT')]
+ [string]$Method = 'GET',
+
+ # Request body (for POST/PUT/PATCH, etc.)
+ [Parameter()]
+ $Body,
+
+ # Credential for Digest authentication (required)
+ [Parameter(Mandatory = $true)]
+ [System.Management.Automation.PSCredential]$Credential,
+
+ # Additional headers (as a hashtable)
+ [Parameter()]
+ [hashtable]$Headers,
+
+ # Content type for the request body (default application/json)
+ [Parameter()]
+ [string]$ContentType = 'application/json',
+
+ # Timeout (for the overall operation) in seconds
+ [Parameter()]
+ [int]$OperationTimeoutSeconds = 100,
+
+ # Connection timeout in seconds
+ [Parameter()]
+ [int]$ConnectionTimeoutSeconds = 100,
+
+ # Disable persistent connections (KeepAlive)
+ [Parameter()]
+ [switch]$DisableKeepAlive,
+
+ # Specify the HTTP version (e.g. '1.1' or '2.0')
+ [Parameter()]
+ [string]$HttpVersion = '1.1',
+
+ # Maximum number of retries (if request fails)
+ [Parameter()]
+ [int]$MaximumRetryCount = 1,
+
+ # Interval between retries (seconds)
+ [Parameter()]
+ [int]$RetryIntervalSec = 1,
+
+ # If provided, write response body to this file
+ [Parameter()]
+ [string]$OutFile,
+
+ # If specified, output the response object even if OutFile is used
+ [Parameter()]
+ [switch]$PassThru,
+
+ # Skip certificate validation (useful for self-signed certs)
+ [Parameter()]
+ [switch]$SkipCertificateCheck,
+
+ # Specify allowed SSL/TLS protocol(s) (e.g. 'Tls12')
+ [Parameter()]
+ [string]$SslProtocol,
+
+ # Transfer-Encoding header value to set on the request
+ [Parameter()]
+ [string]$TransferEncoding,
+
+ # User-Agent string to use on the request
+ [Parameter()]
+ [string]$UserAgent
+ )
+
+ # Validate that we have a credential
+ if (-not $Credential) {
+ Throw 'A credential is required for Digest authentication.'
+ }
+
+ # Use HttpClientHandler
+ $handler = [System.Net.Http.HttpClientHandler]::new()
+ if ($SkipCertificateCheck) {
+ $handler.ServerCertificateCustomValidationCallback = { return $true }
+ }
+ if ($SslProtocol) {
+ $handler.SslProtocols = [System.Enum]::Parse(
+ [System.Security.Authentication.SslProtocols], $SslProtocol)
+ }
+ $httpClient = [System.Net.Http.HttpClient]::new($handler)
+
+ $httpClient.Timeout = [TimeSpan]::FromSeconds($ConnectionTimeoutSeconds)
+
+ # If DisableKeepAlive is specified, add a header to close the connection.
+ if ($DisableKeepAlive) {
+ if (-not $Headers) { $Headers = @{} }
+ $Headers['Connection'] = 'close'
+ }
+
+ # Use the challenge function to get the digest details.
+ try {
+ $challenge = ChallengeDigest -Uri $Uri -Method $Method
+ }
+ catch {
+ Throw "Error retrieving Digest authentication challenge: $_"
+ }
+
+ try {
+ # If a body is provided and content type is JSON, convert it if necessary.
+ if ($Body -and ($ContentType -match 'application/json')) {
+ if ($Body -isnot [string]) {
+ $Body = $Body | ConvertTo-Json -Compress
+ }
+ }
+
+ # Build the digest response parameters.
+ $nc = '00000001'
+ $cnonce = (New-Guid).Guid.Substring(0, 8)
+ $Method = $challenge.Method.ToUpper()
+ Write-Verbose "Using method: $Method"
+ $uriPath = ([System.Uri]$challenge.uri).AbsolutePath
+
+ # Compute HA1
+ $HA1 = ConvertTo-Hash -Value "$($Credential.UserName):$($challenge.realm):$($Credential.GetNetworkCredential().Password)" -Algorithm $challenge.algorithm
+
+ if ($challenge.qop -eq 'auth-int') {
+ if (('Post', 'Put', 'Patch') -notcontains $Method) {
+ Throw "'auth-int' doesn't support $Method"
+ }
+ $requestBody = $Body | ConvertTo-Json
+ $entityBodyHash = ConvertTo-Hash -Value $requestBody -Algorithm $challenge.algorithm
+ $HA2 = ConvertTo-Hash -Value "$($Method):$($uriPath):$($entityBodyHash)" -Algorithm $challenge.algorithm
+ }
+ else {
+ $HA2 = ConvertTo-Hash -Value "$($Method):$($uriPath)" -Algorithm $challenge.algorithm
+ }
+
+ $responseHash = ConvertTo-Hash -Value "$($HA1):$($challenge.nonce):$($nc):$($cnonce):$($challenge.qop):$HA2" -Algorithm $challenge.algorithm
+
+ # Build the Authorization header using StringBuilder.
+ $sb = [System.Text.StringBuilder]::new()
+ [void]$sb.Append('Digest username="').Append($Credential.UserName).Append('"')
+ [void]$sb.Append(', realm="').Append($challenge.realm).Append('"')
+ [void]$sb.Append(', nonce="').Append($challenge.nonce).Append('"')
+ [void]$sb.Append(', uri="').Append($uriPath).Append('"')
+ [void]$sb.Append(', algorithm=').Append($challenge.algorithm)
+ [void]$sb.Append(', response="').Append($responseHash).Append('"')
+ [void]$sb.Append(', qop="').Append($challenge.qop).Append('"')
+ [void]$sb.Append(', nc=').Append($nc)
+ [void]$sb.Append(', cnonce="').Append($cnonce).Append('"')
+
+ # Create the HttpRequestMessage.
+ $authRequest = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::$Method, $challenge.uri)
+ $authRequest.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Digest', $sb.ToString())
+
+ # Set the HTTP version if provided.
+ if ($HttpVersion) {
+ $authRequest.Version = [System.Version]$HttpVersion
+ }
+
+ # Add additional headers (if any) to the request.
+ if ($Headers) {
+ foreach ($key in $Headers.Keys) {
+ $authRequest.Headers.TryAddWithoutValidation($key, $Headers[$key]) | Out-Null
+ }
+ }
+
+ # Set Transfer-Encoding if provided.
+ if ($TransferEncoding) {
+ $authRequest.Headers.TryAddWithoutValidation('Transfer-Encoding', $TransferEncoding) | Out-Null
+ }
+
+ # Set User-Agent if provided.
+ if ($UserAgent) {
+ $authRequest.Headers.UserAgent.Clear()
+ $authRequest.Headers.UserAgent.ParseAdd($UserAgent)
+ }
+
+ if ($challenge.qop -eq 'auth-int') {
+ $authRequest.Content = [System.Net.Http.StringContent]::new($requestBody, [System.Text.Encoding]::UTF8, $ContentType)
+ }
+
+ # Implement a simple retry loop.
+ $retryCount = 0
+ do {
+ try {
+ $rawResponse = $challenge.httpClient.SendAsync($authRequest).Result
+ break
+ }
+ catch {
+ if (++$retryCount -ge $MaximumRetryCount) {
+ Throw "Error sending the authenticated request after $MaximumRetryCount attempts: $_"
+ }
+ else {
+ Write-Verbose "Retrying in $RetryIntervalSec seconds..."
+ Start-Sleep -Seconds $RetryIntervalSec
+ }
+ }
+ } while ($true)
+
+ # Optionally write response to file.
+ if ($OutFile) {
+ $mediaType = $rawResponse.Content.Headers.ContentType.MediaType
+ if ($mediaType -match '^(text|application/json|application/xml)') {
+ $contentString = $rawResponse.Content.ReadAsStringAsync().Result
+ Set-Content -Path $OutFile -Value $contentString -Encoding UTF8
+ }
+ else {
+ $rawResponse.Content.ReadAsByteArrayAsync().Result | Set-Content -Path $OutFile -Encoding Byte
+ }
+ if (-not $PassThru) { return }
+ }
+
+ # Wrap the response in a BasicHtmlWebResponseObject using the OperationTimeoutSeconds value.
+ $contentStream = $rawResponse.Content.ReadAsStream()
+ $timeout = [TimeSpan]::FromSeconds($OperationTimeoutSeconds)
+ $cancellationToken = [System.Threading.CancellationToken]::None
+ return [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]::new($rawResponse, $contentStream, $timeout, $cancellationToken)
+ }
+ catch {
+ Throw "Error sending Digest authenticated request: $_"
+ }
+}
+
+
+<#
+.SYNOPSIS
+ Sends an HTTP or REST request using Digest authentication and returns parsed data.
+
+.DESCRIPTION
+ The Invoke-RestMethodDigest function performs an HTTP request with Digest authentication,
+ leveraging Invoke-WebRequestDigest under the hood. It automatically parses the response
+ content into an object, supporting JSON and XML formats.
+
+.PARAMETER Uri
+ The target URI for the request.
+
+.PARAMETER Method
+ The HTTP method to use for the request. Default is 'GET'.
+
+.PARAMETER Body
+ The request body, required for methods like POST, PUT, and PATCH.
+
+.PARAMETER Credential
+ The PSCredential object containing the username and password for Digest authentication.
+
+.PARAMETER Headers
+ A hashtable of additional headers to include in the request.
+
+.PARAMETER ContentType
+ The Content-Type of the request body. Default is 'application/json'.
+
+.PARAMETER OperationTimeoutSeconds
+ The maximum time in seconds before the request times out. Default is 100.
+
+.PARAMETER ConnectionTimeoutSeconds
+ The timeout in seconds for establishing a connection. Default is 100.
+
+.PARAMETER DisableKeepAlive
+ If specified, disables persistent connections by adding the 'Connection: close' header.
+
+.PARAMETER HttpVersion
+ The HTTP version to use, such as '1.1' or '2.0'. Default is '1.1'.
+
+.PARAMETER MaximumRetryCount
+ The number of times to retry the request in case of failure. Default is 1.
+
+.PARAMETER RetryIntervalSec
+ The interval in seconds between retry attempts. Default is 1.
+
+.PARAMETER OutFile
+ If specified, writes the response body to the specified file instead of returning content.
+
+.PARAMETER PassThru
+ If specified, returns the response object even if OutFile is used.
+
+.PARAMETER SkipCertificateCheck
+ If specified, disables SSL certificate validation (useful for self-signed certificates).
+
+.PARAMETER SslProtocol
+ Specifies the allowed SSL/TLS protocol(s) to use (e.g., 'Tls12').
+
+.PARAMETER TransferEncoding
+ The value for the 'Transfer-Encoding' header.
+
+.PARAMETER UserAgent
+ The User-Agent string to use in the request.
+
+.OUTPUTS
+ - JSON responses are converted to PowerShell objects.
+ - XML responses are parsed into XML objects.
+ - Plain text or other data is returned as-is.
+
+.EXAMPLE
+ $cred = Get-Credential
+ $response = Invoke-RestMethodDigest -Uri 'https://example.com/api/data' -Method 'GET' -Credential $cred
+ Write-Output $response
+
+.EXAMPLE
+ $body = @{ "name" = "John Doe"; "email" = "john@example.com" }
+ $cred = Get-Credential
+ $response = Invoke-RestMethodDigest -Uri 'https://example.com/api/users' -Method 'POST' -Credential $cred -Body $body -ContentType 'application/json'
+ Write-Output $response
+
+.NOTES
+ - This function is a wrapper around Invoke-WebRequestDigest and provides an easier way
+ to work with REST APIs by automatically parsing the response content.
+ - Use Invoke-WebRequestDigest if you need full access to response headers and raw content.
+#>
+function Invoke-RestMethodDigest {
+ [CmdletBinding(DefaultParameterSetName = 'Uri')]
+ [OutputType([xml])]
+ [OutputType([psobject])]
+ param(
+ # URI of the request (required)
+ [Parameter(Mandatory = $true, Position = 0)]
+ [Uri]$Uri,
+
+ # HTTP method (default GET)
+ [Parameter(Position = 1)]
+ [ValidateSet('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'PATCH', 'MERGE', 'CONNECT')]
+ [string]$Method = 'GET',
+
+ # Request body (for POST/PUT/PATCH, etc.)
+ [Parameter()]
+ $Body,
+
+ # Credential for Digest authentication (required)
+ [Parameter(Mandatory = $true)]
+ [System.Management.Automation.PSCredential]$Credential,
+
+ # Additional headers (as a hashtable)
+ [Parameter()]
+ [hashtable]$Headers,
+
+ # Content type for the request body (default application/json)
+ [Parameter()]
+ [string]$ContentType = 'application/json',
+
+ # Timeout (for the overall operation) in seconds
+ [Parameter()]
+ [int]$OperationTimeoutSeconds = 100,
+
+ # Connection timeout in seconds
+ [Parameter()]
+ [int]$ConnectionTimeoutSeconds = 100,
+
+ # Disable persistent connections (KeepAlive)
+ [Parameter()]
+ [switch]$DisableKeepAlive,
+
+ # Specify the HTTP version (e.g. '1.1' or '2.0')
+ [Parameter()]
+ [string]$HttpVersion = '1.1',
+
+ # Maximum number of retries (if request fails)
+ [Parameter()]
+ [int]$MaximumRetryCount = 1,
+
+ # Interval between retries (seconds)
+ [Parameter()]
+ [int]$RetryIntervalSec = 1,
+
+ # If provided, write response body to this file
+ [Parameter()]
+ [string]$OutFile,
+
+ # If specified, output the response object even if OutFile is used
+ [Parameter()]
+ [switch]$PassThru,
+
+ # Skip certificate validation (useful for self-signed certs)
+ [Parameter()]
+ [switch]$SkipCertificateCheck,
+
+ # Specify allowed SSL/TLS protocol(s) (e.g. 'Tls12')
+ [Parameter()]
+ [string]$SslProtocol,
+
+
+ # Transfer-Encoding header value
+ [Parameter()]
+ [string]$TransferEncoding,
+
+ # User-Agent string to use on the request
+ [Parameter()]
+ [string]$UserAgent
+ )
+
+ # Build a parameter hashtable for Invoke-WebRequestDigest
+ $params = @{
+ Uri = $Uri
+ Method = $Method
+ Body = $Body
+ Credential = $Credential
+ Headers = $Headers
+ ContentType = $ContentType
+ OperationTimeoutSeconds = $OperationTimeoutSeconds
+ ConnectionTimeoutSeconds = $ConnectionTimeoutSeconds
+ DisableKeepAlive = $DisableKeepAlive
+ HttpVersion = $HttpVersion
+ MaximumRetryCount = $MaximumRetryCount
+ RetryIntervalSec = $RetryIntervalSec
+ OutFile = $OutFile
+ PassThru = $PassThru
+ SkipCertificateCheck = $SkipCertificateCheck
+ SslProtocol = $SslProtocol
+ TransferEncoding = $TransferEncoding
+ UserAgent = $UserAgent
+ }
+
+ # Call the digest-enabled web request function
+ $webResponse = Invoke-WebRequestDigest @params
+
+ if ($null -eq $webResponse) {
+ return $null
+ }
+
+ # Parse the response content based on its media type
+ $content = $webResponse.Content
+ if ($content) {
+ # Get Content-Type header if available
+ $mediaType = $webResponse.Headers.'Content-Type'
+ if ($mediaType -match 'application/json') {
+ return $content | ConvertFrom-Json
+ }
+ elseif ($mediaType -match 'application/xml' -or $mediaType -match 'text/xml') {
+ return [xml]$content
+ }
+ else {
+ # For non-parsed content (plain text or other formats)
+ return $content
+ }
+ }
+ else {
+ return $null
+ }
+}
+
+Export-ModuleMember -Function Invoke-WebRequestDigest
+Export-ModuleMember -Function Invoke-RestMethodDigest
diff --git a/examples/WebAuth-ApikeyJWT.ps1 b/examples/Authentication/Web-AuthApiKey.ps1
similarity index 94%
rename from examples/WebAuth-ApikeyJWT.ps1
rename to examples/Authentication/Web-AuthApiKey.ps1
index dcf242d54..e7b88e135 100644
--- a/examples/WebAuth-ApikeyJWT.ps1
+++ b/examples/Authentication/Web-AuthApiKey.ps1
@@ -29,7 +29,7 @@
Invoke-RestMethod -Uri http://localhost:8081/users -Method Get
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/WebAuth-ApikeyJWT.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/WebAuth-ApikeyJWT.ps1
.NOTES
Author: Pode Team
@@ -44,7 +44,7 @@ param(
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
diff --git a/examples/Web-AuthBasic.ps1 b/examples/Authentication/Web-AuthBasic.ps1
similarity index 93%
rename from examples/Web-AuthBasic.ps1
rename to examples/Authentication/Web-AuthBasic.ps1
index e0a886a02..ef795df87 100644
--- a/examples/Web-AuthBasic.ps1
+++ b/examples/Authentication/Web-AuthBasic.ps1
@@ -23,7 +23,7 @@
Invoke-RestMethod -Uri http://localhost:8081/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' }
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasic.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasic.ps1
.NOTES
Author: Pode Team
@@ -31,7 +31,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
@@ -75,7 +75,7 @@ Start-PodeServer -Threads 2 {
return @{ Message = 'Invalid details supplied' }
}
-
+
# POST request to get current user (since there's no session, authentication will always happen)
Add-PodeRoute -Method Post -Path '/users' -Authentication 'Validate' -ScriptBlock {
Write-PodeJsonResponse -Value @{
diff --git a/examples/Web-AuthBasicAccess.ps1 b/examples/Authentication/Web-AuthBasicAccess.ps1
similarity index 96%
rename from examples/Web-AuthBasicAccess.ps1
rename to examples/Authentication/Web-AuthBasicAccess.ps1
index 1bd7d36c5..0c07afe5d 100644
--- a/examples/Web-AuthBasicAccess.ps1
+++ b/examples/Authentication/Web-AuthBasicAccess.ps1
@@ -27,7 +27,7 @@
Invoke-RestMethod -Uri http://localhost:8081/users-all -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' }
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Dot-SourceScript.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Dot-SourceScript.ps1
.NOTES
Author: Pode Team
@@ -35,7 +35,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
diff --git a/examples/Web-AuthBasicAdhoc.ps1 b/examples/Authentication/Web-AuthBasicAdhoc.ps1
similarity index 94%
rename from examples/Web-AuthBasicAdhoc.ps1
rename to examples/Authentication/Web-AuthBasicAdhoc.ps1
index c4eea0b73..b7b905ec5 100644
--- a/examples/Web-AuthBasicAdhoc.ps1
+++ b/examples/Authentication/Web-AuthBasicAdhoc.ps1
@@ -27,7 +27,7 @@
Invoke-RestMethod -Uri http://localhost:8081/users -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' }
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasicAdhoc.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasicAdhoc.ps1
.NOTES
Author: Pode Team
@@ -35,7 +35,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
diff --git a/examples/Web-AuthBasicAnon.ps1 b/examples/Authentication/Web-AuthBasicAnon.ps1
similarity index 94%
rename from examples/Web-AuthBasicAnon.ps1
rename to examples/Authentication/Web-AuthBasicAnon.ps1
index 597c3cf9c..2f6648779 100644
--- a/examples/Web-AuthBasicAnon.ps1
+++ b/examples/Authentication/Web-AuthBasicAnon.ps1
@@ -27,7 +27,7 @@
Invoke-RestMethod -Uri http://localhost:8081/users -Headers @{ Authorization = 'Basic bW9ydHk6cmljaw==' }
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasicAnon.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasicAnon.ps1
.NOTES
Author: Pode Team
@@ -35,7 +35,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
diff --git a/examples/Web-AuthBasicBearer.ps1 b/examples/Authentication/Web-AuthBasicBearer.ps1
similarity index 80%
rename from examples/Web-AuthBasicBearer.ps1
rename to examples/Authentication/Web-AuthBasicBearer.ps1
index f6c73820d..5ccec6948 100644
--- a/examples/Web-AuthBasicBearer.ps1
+++ b/examples/Authentication/Web-AuthBasicBearer.ps1
@@ -9,10 +9,16 @@
.EXAMPLE
To run the sample: ./Web-AuthBasicBearer.ps1
- Invoke-RestMethod -Method Get -Uri 'http://localhost:8081/users' -Headers @{ Authorization = 'Bearer test-token' }
+ Invoke-RestMethod -Method Get -Uri 'http://localhost:8081/users' -Headers @{ Authorization = 'Bearer test-token' } -ResponseHeadersVariable headers
+
+.EXAMPLE
+ "No Authorization header found"
+
+ Invoke-RestMethod -Method Get -Uri 'http://localhost:8081/users' -ResponseHeadersVariable headers -Verbose -SkipHttpErrorCheck
+ $headers | Format-List
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasicBearer.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasicBearer.ps1
.NOTES
Author: Pode Team
@@ -20,7 +26,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
@@ -64,7 +70,7 @@ Start-PodeServer -Threads 2 {
}
# GET request to get list of users (since there's no session, authentication will always happen)
- Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -ScriptBlock {
+ Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -ErrorContentType 'application/json' -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
diff --git a/examples/Web-AuthBasicClientcert.ps1 b/examples/Authentication/Web-AuthBasicClientcert.ps1
similarity index 92%
rename from examples/Web-AuthBasicClientcert.ps1
rename to examples/Authentication/Web-AuthBasicClientcert.ps1
index 489b37b72..d99470183 100644
--- a/examples/Web-AuthBasicClientcert.ps1
+++ b/examples/Authentication/Web-AuthBasicClientcert.ps1
@@ -10,7 +10,7 @@
To run the sample: ./Web-AuthBasicClientcert.ps1
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasicClientcert.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasicClientcert.ps1
.NOTES
Author: Pode Team
@@ -18,7 +18,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
diff --git a/examples/Web-AuthBasicHeader.ps1 b/examples/Authentication/Web-AuthBasicHeader.ps1
similarity index 84%
rename from examples/Web-AuthBasicHeader.ps1
rename to examples/Authentication/Web-AuthBasicHeader.ps1
index 0fb0f8d27..169d3f445 100644
--- a/examples/Web-AuthBasicHeader.ps1
+++ b/examples/Authentication/Web-AuthBasicHeader.ps1
@@ -17,7 +17,8 @@
The example used here is Basic authentication.
Login:
- $session = (Invoke-WebRequest -Uri http://localhost:8081/login -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' }).Headers['pode.sid']
+ Invoke-RestMethod -Uri http://localhost:8081/login -Method Post -Headers @{ Authorization = 'Basic bW9ydHk6cGlja2xl' } -ResponseHeadersVariable headers -SkipHttpErrorCheck
+ $session = $headers['pode.sid']
Users:
Invoke-RestMethod -Uri http://localhost:8081/users -Method Post -Headers @{ 'pode.sid' = "$session" }
@@ -26,7 +27,7 @@
Invoke-WebRequest -Uri http://localhost:8081/logout -Method Post -Headers @{ 'pode.sid' = "$session" }
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthBasicHeader.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthBasicHeader.ps1
.NOTES
Author: Pode Team
@@ -34,7 +35,7 @@
#>
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
@@ -81,13 +82,13 @@ Start-PodeServer -Threads 2 {
}
# POST request to login
- Add-PodeRoute -Method Post -Path '/login' -Authentication 'Login'
+ Add-PodeRoute -Method Post -Path '/login' -Authentication 'Login' -ErrorContentType 'application/json'
# POST request to logout
- Add-PodeRoute -Method Post -Path '/logout' -Authentication 'Login' -Logout
+ Add-PodeRoute -Method Post -Path '/logout' -Authentication 'Login' -Logout -ErrorContentType 'application/json'
# POST request to get list of users - the "pode.sid" header is expected
- Add-PodeRoute -Method Post -Path '/users' -Authentication 'Login' -ScriptBlock {
+ Add-PodeRoute -Method Post -Path '/users' -Authentication 'Login' -ErrorContentType 'application/json' -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
diff --git a/examples/Authentication/Web-AuthBearerJWT.ps1 b/examples/Authentication/Web-AuthBearerJWT.ps1
new file mode 100644
index 000000000..1ae85402f
--- /dev/null
+++ b/examples/Authentication/Web-AuthBearerJWT.ps1
@@ -0,0 +1,248 @@
+<#
+.SYNOPSIS
+ A PowerShell script to set up a Pode server with JWT authentication and various route configurations.
+
+.DESCRIPTION
+ This script initializes a Pode server that listens on a specified port, enables request and error logging,
+ and configures JWT authentication using either the request header or query parameters. It also defines
+ a protected route to fetch a list of users, requiring authentication.
+
+.PARAMETER Location
+ Specifies where the API key (JWT token) is expected.
+ Valid values: 'Header', 'Query'.
+ Default: 'Header'.
+
+.EXAMPLE
+ # Run the sample
+ ./WebAuth-bearerJWT.ps1
+
+ JWT payload:
+ {
+ "sub": "1234567890",
+ "name": "morty",
+ "username":"morty",
+ "type": "Human",
+ "id" : "M0R7Y302",
+ "admin": true,
+ "iat": 1516239022,
+ "exp": 2634234231,
+ "iss": "auth.example.com",
+ "sub": "1234567890",
+ "aud": "myapi.example.com",
+ "nbf": 1690000000,
+ "jti": "unique-token-id",
+ "role": "admin"
+ }
+
+.EXAMPLE
+ # Example request using PS512 JWT authentication
+ $jwt = ConvertTo-PodeJwt -PfxPath ./cert.pfx -RsaPaddingScheme Pss -PfxPassword (ConvertTo-SecureString 'mySecret' -AsPlainText -Force)
+ $headers = @{ 'Authorization' = "Bearer $jwt" }
+ $response = Invoke-RestMethod -Uri 'http://localhost:8081/auth/bearer/jwt/PS512' -Method Get -Headers $headers
+
+.EXAMPLE
+ # Example request using RS384 JWT authentication
+ $headers = @{ 'Authorization' = 'Bearer ' }
+ $response = Invoke-RestMethod -Uri 'http://localhost:8081/users' -Method Get -Headers $headers
+
+.EXAMPLE
+ # Example request using HS256 JWT authentication
+ $jwt = ConvertTo-PodeJwt -Algorithm HS256 -Secret (ConvertTo-SecureString 'secret' -AsPlainText -Force) -Payload @{id='id';name='Morty'}
+ $headers = @{ 'Authorization' = "Bearer $jwt" }
+ $response = Invoke-RestMethod -Uri 'http://localhost:8081/users' -Method Get -Headers $headers
+
+ .LINK
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthbearerJWT.ps1
+
+ .NOTES
+ - This script uses Pode to create a lightweight web server with authentication.
+ - JWT authentication is handled via Bearer tokens passed in either the header or query.
+ - Ensure the private key is securely stored and managed for RS256-based JWT signing.
+ - Using query parameters for authentication is **discouraged** due to security risks.
+ - Always use HTTPS in production to protect sensitive authentication data.
+
+ Author: Pode Team
+ License: MIT License
+#>
+
+param(
+ [Parameter()]
+ [ValidateSet('Header', 'Query' )]
+ [string]
+ $Location = 'Header'
+)
+
+try {
+ # Determine the script path and Pode module path
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
+ $podePath = Split-Path -Parent -Path $ScriptPath
+
+ # Import the Pode module from the source path if it exists, otherwise from installed modules
+ if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) {
+ Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop
+ }
+ else {
+ Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop
+ }
+}
+catch { throw }
+
+# or just:
+# Import-Module Pode
+
+# create a server, and start listening on port 8081
+Start-PodeServer -Threads 2 -ApplicationName 'webauth' {
+
+ # listen on localhost:8081
+ Add-PodeEndpoint -Address localhost -Port 8081 -Protocol Http
+
+ New-PodeLoggingMethod -File -Name 'requests' | Enable-PodeRequestLogging
+ New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging
+
+
+ $JwtVerificationMode = 'Lenient' # Set your desired verification mode (Lenient or Strict)
+
+ $certificateTypes = @{
+ 'RS256' = @{
+ KeyType = 'RSA'
+ KeyLength = 2048
+ }
+ 'RS384' = @{
+ KeyType = 'RSA'
+ KeyLength = 3072
+ }
+ 'RS512' = @{
+ KeyType = 'RSA'
+ KeyLength = 4096
+ }
+ 'PS256' = @{
+ KeyType = 'RSA'
+ KeyLength = 2048
+ }
+ 'PS384' = @{
+ KeyType = 'RSA'
+ KeyLength = 3072
+ }
+ 'PS512' = @{
+ KeyType = 'RSA'
+ KeyLength = 4096
+ }
+ 'ES256' = @{
+ KeyType = 'ECDSA'
+ KeyLength = 256
+ }
+ 'ES384' = @{
+ KeyType = 'ECDSA'
+ KeyLength = 384
+ }
+ 'ES512' = @{
+ KeyType = 'ECDSA'
+ KeyLength = 521
+ }
+ }
+
+ $CertsPath = Join-Path -Path (Get-PodeServerPath) -ChildPath "certs"
+ if (!(Test-Path -Path $CertsPath -PathType Container)) {
+ New-Item -Path $CertsPath -ItemType Directory
+ }
+ foreach ($alg in $certificateTypes.Keys) {
+ $x509Certificate = New-PodeSelfSignedCertificate -Loopback -KeyType $certificateTypes[$alg].KeyType -KeyLength $certificateTypes[$alg].KeyLength -CertificatePurpose CodeSigning -Ephemeral -Exportable
+
+ Export-PodeCertificate -Certificate $x509Certificate -Format PFX -Path (join-path -path $CertsPath -ChildPath $alg)
+
+ # Define the authentication location dynamically (e.g., `/auth/bearer/jwt/{algorithm}`)
+ $pathRoute = "/auth/bearer/jwt/$alg"
+ # Register Pode Bearer Authentication
+ Write-PodeHost "🔹 Registering JWT Authentication for: $alg ($Location)"
+
+ $rsaPaddingScheme = if ($alg.StartsWith('PS')) { 'Pss' } else { 'Pkcs1V15' }
+
+ $param = @{
+ Location = $Location
+ AsJWT = $true
+ RsaPaddingScheme = $rsaPaddingScheme
+ JwtVerificationMode = $JwtVerificationMode
+ X509Certificate = $x509Certificate
+ }
+
+ New-PodeAuthBearerScheme @param |
+ Add-PodeAuth -Name "Bearer_JWT_$alg" -Sessionless -ScriptBlock {
+ param($jwt)
+
+ # here you'd check a real user storage, this is just for example
+ if ($jwt.username -ieq 'morty') {
+ return @{
+ User = @{
+ ID = $jWt.id
+ Name = $jst.name
+ Type = $jst.type
+ }
+ }
+ }
+
+ return $null
+ }
+
+ # GET request to get list of users (since there's no session, authentication will always happen)
+ Add-PodeRoute -Method Get -Path $pathRoute -Authentication "Bearer_JWT_$alg" -ScriptBlock {
+
+ Write-PodeJsonResponse -Value @{
+ Users = @(
+ @{
+ Name = 'Deep Thought'
+ Age = 42
+ },
+ @{
+ Name = 'Leeroy Jenkins'
+ Age = 1337
+ }
+ )
+ }
+ }
+ }
+
+
+
+ # setup bearer auth
+ New-PodeAuthBearerScheme -Location $Location -AsJWT -Secret (ConvertTo-SecureString 'your-256-bit-secret' -AsPlainText -Force) -JwtVerificationMode Lenient | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock {
+ param($jwt)
+
+ # here you'd check a real user storage, this is just for example
+ if ($jwt.username -ieq 'morty') {
+ return @{
+ User = @{
+ ID = $jWt.id
+ Name = $jst.name
+ Type = $jst.type
+ }
+ }
+ }
+
+ return $null
+ }
+
+ # GET request to get list of users (since there's no session, authentication will always happen)
+ Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -ScriptBlock {
+
+ Write-PodeJsonResponse -Value @{
+ Users = @(
+ @{
+ Name = 'Deep Thought'
+ Age = 42
+ },
+ @{
+ Name = 'Leeroy Jenkins'
+ Age = 1337
+ }
+ )
+ }
+ }
+
+
+ Register-PodeEvent -Type Stop -Name 'CleanCerts' -ScriptBlock {
+ if ( (Test-Path -Path "$(Get-PodeServerPath)/cert" -PathType Container)) {
+ Remove-Item -Path "$(Get-PodeServerPath)/cert" -Recurse -Force
+ Write-PodeHost "$(Get-PodeServerPath)/cert removed."
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/Authentication/Web-AuthBearerJWTLifecycle.ps1 b/examples/Authentication/Web-AuthBearerJWTLifecycle.ps1
new file mode 100644
index 000000000..90fe58d93
--- /dev/null
+++ b/examples/Authentication/Web-AuthBearerJWTLifecycle.ps1
@@ -0,0 +1,274 @@
+<#
+.SYNOPSIS
+ A PowerShell script demonstrating the full lifecycle of JWT authentication using X.509 certificates in Pode.
+
+.DESCRIPTION
+ This script sets up a Pode server that listens on a specified port and enables JWT authentication using X.509 certificates.
+ It showcases the full JWT authentication lifecycle, including login, renewal, validation, and retrieval of user information.
+ Authentication is performed using JWT tokens signed with the selected cryptographic algorithm.
+
+.PARAMETER Location
+ Specifies where the API key (JWT token) is expected.
+ Valid values: 'Header', 'Query'.
+ Default: 'Header'.
+
+.PARAMETER Algorithm
+ Specifies the cryptographic algorithm used for JWT signing and verification.
+ Valid values: 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512'.
+ Default: 'ES512'.
+
+.EXAMPLE
+ # Run the sample
+ ./WebAuth-bearerJWT.ps1
+
+ JWT payload example:
+ {
+ "sub": "1234567890",
+ "name": "morty",
+ "username": "morty",
+ "type": "Human",
+ "id": "M0R7Y302",
+ "admin": true,
+ "iat": 1516239022,
+ "exp": 2634234231,
+ "iss": "auth.example.com",
+ "aud": "myapi.example.com",
+ "nbf": 1690000000,
+ "jti": "unique-token-id",
+ "role": "admin"
+ }
+
+.LINK
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthbearerJWTLifecycle.ps1
+
+.NOTES
+ - This script uses Pode to create a lightweight web server with authentication.
+ - JWT authentication is handled via Bearer tokens passed in either the header or query parameters.
+ - JWTs are signed using X.509 certificates and verified based on the selected algorithm.
+ - The script implements endpoints for login, token renewal, and token validation.
+ - Ensure the private key is securely stored and managed for RS256-based JWT signing.
+ - Using query parameters for authentication is **discouraged** due to security risks.
+ - Always use HTTPS in production to protect sensitive authentication data.
+
+ Author: Pode Team
+ License: MIT License
+#>
+
+param(
+ [Parameter()]
+ [ValidateSet('Header', 'Query' )]
+ [string]
+ $Location = 'Header',
+
+ [Parameter()]
+ [ValidateSet( 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512')]
+ [string]
+ $Algorithm = 'ES512'
+)
+
+try {
+ # Determine the script path and Pode module path
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
+ $podePath = Split-Path -Parent -Path $ScriptPath
+
+ # Import the Pode module from the source path if it exists, otherwise from installed modules
+ if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) {
+ Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop
+ }
+ else {
+ Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop
+ }
+}
+catch { throw }
+
+# Define a function to autenticate user credentials
+function Test-User {
+ param (
+ [string]$username,
+ [string]$password
+ )
+ if ($username -eq 'morty' -and $password -eq 'pickle') {
+ return @{
+ Id = 'M0R7Y302'
+ Username = 'morty.smith'
+ Name = 'Morty Smith'
+ Groups = 'Domain Users'
+ }
+ }
+ throw 'Invalid credentials'
+}
+
+# or just:
+# Import-Module Pode
+
+# create a server, and start listening on port 8081
+Start-PodeServer -Threads 2 -ApplicationName 'webauth' {
+
+ # listen on localhost:8081
+ Add-PodeEndpoint -Address localhost -Port 8043 -Protocol Https -SelfSigned
+
+ New-PodeLoggingMethod -File -Name 'requests' | Enable-PodeRequestLogging
+ New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging
+ # Configure CORS
+ Set-PodeSecurityAccessControl -Origin '*' -Duration 7200 -WithOptions -AuthorizationHeader -autoMethods -AutoHeader -Credentials -CrossDomainXhrRequests
+
+
+ # Enable OpenAPI documentation
+
+ Enable-PodeOpenApi -Path '/docs/openapi' -OpenApiVersion '3.0.3' -EnableSchemaValidation:($PSVersionTable.PSEdition -eq 'Core') -DisableMinimalDefinitions -NoDefaultResponses
+ Add-PodeOAInfo -Title 'JWT Test' -Version 1.0.17 -Description 'test'
+ Add-PodeOAServerEndpoint -url '/auth/bearer/jwt' -Description 'default endpoint'
+ # Enable OpenAPI viewers
+ Enable-PodeOAViewer -Type Swagger -Path '/docs/swagger'
+ Enable-PodeOAViewer -Type ReDoc -Path '/docs/redoc' -DarkMode
+ Enable-PodeOAViewer -Type RapiDoc -Path '/docs/rapidoc' -DarkMode
+ Enable-PodeOAViewer -Type StopLight -Path '/docs/stoplight' -DarkMode
+ Enable-PodeOAViewer -Type Explorer -Path '/docs/explorer' -DarkMode
+ Enable-PodeOAViewer -Type RapiPdf -Path '/docs/rapipdf' -DarkMode
+
+ # Enable OpenAPI editor and bookmarks
+ Enable-PodeOAViewer -Editor -Path '/docs/swagger-editor'
+ Enable-PodeOAViewer -Bookmarks -Path '/docs'
+
+
+ $JwtVerificationMode = 'Strict' # Set your desired verification mode (Lenient or Strict)
+ # $SecurePassword = ConvertTo-SecureString 'MySecurePassword' -AsPlainText -Force
+
+ $param = @{
+ Location = $Location
+ AsJWT = $true
+ JwtVerificationMode = $JwtVerificationMode
+ SelfSigned = $true
+ }
+ # Register Pode Bearer Authentication
+ New-PodeAuthBearerScheme @param |
+ Add-PodeAuth -Name "Bearer_JWT_$Algorithm" -Sessionless -ScriptBlock {
+ param($jwt)
+
+ # here you'd check a real user storage, this is just for example
+ if ($jwt.id -ieq 'M0R7Y302') {
+ return @{
+ User = @{
+ ID = $jWt.id
+ Name = $jWt.name
+ Type = $jWt.type
+ sub = $jWt.Id
+ username = $jWt.Username
+ groups = $jWt.Groups
+ }
+ }
+ }
+ else {
+ write-podehost $jwt -Explode
+ }
+
+ return $null
+ }
+
+ # GET request to get list of users (since there's no session, authentication will always happen)
+ Add-PodeRoute -PassThru -Method Get -Path "/auth/bearer/jwt/$Algorithm" -Authentication "Bearer_JWT_$Algorithm" -ScriptBlock {
+ Write-PodeJsonResponse -Value $WebEvent.auth.User
+ } | Set-PodeOARouteInfo -Summary 'Get my info.' -Tags 'user' -OperationId "myinfo_$Algorithm"
+
+
+
+ Add-PodeRoute -PassThru -Method Post -Path '/auth/bearer/jwt/login' -ArgumentList $Algorithm -ScriptBlock {
+ param(
+ [string]
+ $Algorithm
+ )
+ try {
+ # In a real scenario, you'd validate the incoming credentials from $WebEvent.data
+ $username = $WebEvent.Data.username
+ $password = $WebEvent.Data.password
+ $user = Test-User -username $username -password $password
+
+
+ $payload = @{
+ sub = $user.Id
+ name = $user.Name
+ username = $user.Username
+ id = $user.Id
+ groups = $user.Groups
+ type = 'human'
+ }
+
+ # If valid, generate a JWT that matches the 'ExampleApiKeyCert' scheme
+ $jwt = ConvertTo-PodeJwt -Payload $payload -Authentication "Bearer_JWT_$Algorithm" -Expiration 600
+ Write-PodeJsonResponse -StatusCode 200 -Value @{
+ 'success' = $true
+ 'user' = $user
+ 'token' = $jwt
+ }
+
+ }
+ catch {
+ write-podehost $_.Exception.Message
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid credentials' }
+ }
+ } | Set-PodeOARouteInfo -Summary 'Logs user into the system.' -Tags 'user' -OperationId 'loginUser' -PassThru |
+ Set-PodeOARequest -RequestBody (
+ New-PodeOARequestBody -Description 'Update an existent pet in the store' -Required -Content (
+ New-PodeOAContentMediaType -ContentType 'application/json' -Content (
+ New-PodeOAStringProperty -Name 'username' -Description 'The user name for login' -Default 'morty' |
+ New-PodeOAStringProperty -Name 'password' -Description 'The password for login in clear text' -Format Password -Default 'pickle' |
+ New-PodeOAObjectProperty)
+ )
+ ) -PassThru |
+ Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (
+ New-PodeOAContentMediaType -ContentType 'application/json' -Content (
+ New-PodeOABoolProperty -Name 'success' -Description 'Operation success' -Example $true |
+ New-PodeOAStringProperty -Name 'user' -Description 'The user for login' -Example 'morty' |
+ New-PodeOAStringProperty -Name 'token' -Description 'Bearen JWT token' -Example '6656565' |
+ New-PodeOAObjectProperty
+ )
+ ) -PassThru |
+ Add-PodeOAResponse -StatusCode 400 -Description 'Invalid username/password supplied'
+
+ Add-PodeRoute -PassThru -Method Post -Path '/auth/bearer/jwt/renew' -Authentication "Bearer_JWT_$Algorithm" -ScriptBlock {
+ try {
+
+ $jwt = Update-PodeJwt
+
+ Write-PodeJsonResponse -StatusCode 200 -Value @{
+ 'success' = $true
+ 'token' = $jwt
+ }
+ }
+ catch {
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid JWT token supplied' }
+ }
+ } | Set-PodeOARouteInfo -Summary 'Extend JWT Token.' -Tags 'JWT' -OperationId 'renewToken' -PassThru |
+ Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (
+ New-PodeOAContentMediaType -ContentType 'application/json' -Content (
+ New-PodeOABoolProperty -Name 'success' -Description 'Operation success' -Example $true |
+ New-PodeOAStringProperty -Name 'user' -Description 'The user for login' -Example 'morty' |
+ New-PodeOAStringProperty -Name 'token' -Description 'Bearen JWT token' -Example 'eyJ0eXAiOiJKV1QifQ.eyJpZCI6Ik0wUjdZMzAyIi ... UG9kZSJ9.hhU1fmykkSyZhUCr1NSZto-dGyt50r5OUlYj5SgL88EFlnulSOtsM-61tht-X5lEZVP7TCwG2q6ZELiA-4zey7BTIEecKg8zQ4NasZQi6eq9scSL0WJPNHNiGf91F1BsSAQmTxmtJz9-R9l7dxxonFlgLhq9ZwToPuAEK76lYuEQ45ERH-LoO5En9nRnar5N8SLe244To_T7UPKKBgd_DQNSuW4pShMbeK1_TTwELxroV2-d7bPyhUKIwrP61DDsGxgYCzsJ_8XG4YOfFg_u3bHp_JEplCFPoc5KUVNOQHFCzYR0WMZDhRDMnAF6J8Xn0RKTsFB7q1QNC0NF1-7TGQ' |
+ New-PodeOAObjectProperty
+ )
+ ) -PassThru |
+ Add-PodeOAResponse -StatusCode 401 -Description 'Invalid JWT token supplied'
+
+
+ Add-PodeRoute -PassThru -Method Get -Path '/auth/bearer/jwt/info' -Authentication "Bearer_JWT_$Algorithm" -ScriptBlock {
+ try {
+ $jwtInfo = ConvertFrom-PodeJwt -Outputs 'Header,Payload,Signature' -HumanReadable
+ $jwtInfo.success = $true
+ Write-PodeJsonResponse -StatusCode 200 -Value $jwtInfo
+ }
+ catch {
+ Write-PodeJsonResponse -StatusCode 401 -Value @{ error = 'Invalid JWT token supplied' }
+ }
+ } | Set-PodeOARouteInfo -Summary 'return JWT Token info.' -Tags 'JWT' -OperationId 'getInfoToken' -PassThru |
+ Add-PodeOAResponse -StatusCode 200 -Description 'Successful operation' -Content (
+ New-PodeOAContentMediaType -ContentType 'application/json' -Content (
+ New-PodeOAObjectProperty -Properties (
+ ( New-PodeOABoolProperty -Name 'success' -Description 'Operation success' -Example $true),
+ (New-PodeOAObjectProperty -Name Header ),
+ ( New-PodeOAObjectProperty -Name Payload), ( New-PodeOAStringProperty -Name Signature)
+ )
+ )
+ ) -PassThru |
+ Add-PodeOAResponse -StatusCode 401 -Description 'Invalid JWT token supplied'
+
+}
\ No newline at end of file
diff --git a/examples/Authentication/Web-AuthDigest.ps1 b/examples/Authentication/Web-AuthDigest.ps1
new file mode 100644
index 000000000..779df40cc
--- /dev/null
+++ b/examples/Authentication/Web-AuthDigest.ps1
@@ -0,0 +1,215 @@
+<#
+.SYNOPSIS
+ PowerShell script to set up a Pode server with Digest authentication or make client requests.
+
+.DESCRIPTION
+ This script can either:
+ - Start a Pode server that listens on a specified port and uses Digest authentication to secure access.
+ - Act as a client to send requests with Digest authentication.
+
+ The authentication details are checked against predefined user data.
+ For non-MD5 algorithms, use ./utility/DigestClient.ps1.
+
+.PARAMETER Client
+ If specified, the script runs in client mode instead of starting a server.
+
+.PARAMETER Algorithm
+ The Digest authentication algorithm(s) to use. Supported values: MD5, SHA-1, SHA-256, SHA-512, SHA-384, SHA-512/256.
+ Defaults to all supported algorithms.
+
+.PARAMETER QualityOfProtection
+ Specifies the Quality of Protection (qop) to use in Digest authentication.
+ Valid options:
+ - 'auth': Authentication only.
+ - 'auth-int': Authentication with integrity protection.
+ - 'auth,auth-int': Support both modes.
+
+.EXAMPLE
+ To start the Pode server with default settings:
+ ```powershell
+ ./Web-AuthDigest.ps1
+ ```
+
+.EXAMPLE
+ To start the Pode server with SHA-256 authentication only:
+ ```powershell
+ ./Web-AuthDigest.ps1 -Algorithm SHA-256
+ ```
+
+.EXAMPLE
+ To run in client mode and send a Digest-authenticated request:
+ ```powershell
+ ./Web-AuthDigest.ps1 -Client
+ ```
+
+.EXAMPLE
+ Client request example using default .Net Digest support:
+
+ ```powershell
+ # Define the URI and credentials
+ $uri = [System.Uri]::new("http://localhost:8081/users")
+ $username = "morty"
+ $password = "pickle"
+
+ # Create a credential cache and add Digest authentication
+ $credentialCache = [System.Net.CredentialCache]::new()
+ $networkCredential = [System.Net.NetworkCredential]::new($username, $password)
+ $credentialCache.Add($uri, "Digest", $networkCredential)
+
+ # Create the HTTP client handler with the credential cache
+ $handler = [System.Net.Http.HttpClientHandler]::new()
+ $handler.Credentials = $credentialCache
+
+ # Create the HTTP client
+ $httpClient = [System.Net.Http.HttpClient]::new($handler)
+
+ # Send the GET request
+ $requestMessage = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $uri)
+ $response = $httpClient.SendAsync($requestMessage).Result
+
+ # Display response headers and content
+ $response.Headers | ForEach-Object { "$($_.Key): $($_.Value)" }
+ $content = $response.Content.ReadAsStringAsync().Result
+ $content
+ ```
+.EXAMPLE
+ Client request example using `Invoke-WebRequestDigest`:
+
+ ```powershell
+ Import-Module './client/Invoke-Digest.psm1'
+
+ # Define the URI and credentials
+ $uri = 'http://localhost:8081/users'
+ $username = 'morty'
+ $password = 'pickle'
+
+ # Convert the password to a SecureString and create a credential object
+ $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
+ $credential = [System.Management.Automation.PSCredential]::new($username, $securePassword)
+
+ # Make a GET request using Digest authentication
+ $response = Invoke-WebRequestDigest -Uri $uri -Method 'GET' -Credential $credential
+
+ # Display response headers and content
+ $response.Headers | Format-List
+ Write-Output $response.Content
+ ```
+
+.EXAMPLE
+ Running the server with `auth-int` quality of protection:
+ ```powershell
+ ./Web-AuthDigest.ps1 -QualityOfProtection auth-int
+ ```
+
+.LINK
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthDigest.ps1
+
+.NOTES
+ Author: Pode Team
+ License: MIT License
+#>
+
+[CmdletBinding(DefaultParameterSetName = 'Server')]
+param(
+ [Parameter(ParameterSetName = 'Client')]
+ [switch]
+ $Client,
+
+ [Parameter(ParameterSetName = 'Server')]
+ [string[]]
+ $Algorithm = @('MD5', 'SHA-1', 'SHA-256', 'SHA-512', 'SHA-384', 'SHA-512/256'),
+
+ [Parameter(ParameterSetName = 'Server')]
+ [ValidateSet('auth', 'auth-int', 'auth,auth-int' )]
+ [string[]]
+ $QualityOfProtection = 'auth,auth-int'
+)
+if ($Client) {
+ Import-Module './Modules/Invoke-Digest.psm1'
+ $uri = 'http://localhost:8081/users'
+ $username = 'morty'
+ $password = 'pickle'
+
+ $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
+ $credential = [System.Management.Automation.PSCredential]::new($username, $securePassword)
+
+ $response = Invoke-WebRequestDigest -Uri $uri -Method 'GET' -Credential $credential
+ $response | Format-List *
+
+ Invoke-WebRequestDigest -Uri $uri -Method 'GET' -Credential $credential -OutFile 'outfile.json'
+
+ $response = Invoke-RestMethodDigest -Uri $uri -Method 'GET' -Credential $credential
+ $response
+ return
+}
+try {
+ # Determine the script path and Pode module path
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
+ $podePath = Split-Path -Parent -Path $ScriptPath
+
+ # Import the Pode module from the source path if it exists, otherwise from installed modules
+ if (Test-Path -Path "$($podePath)/src/Pode.psm1" -PathType Leaf) {
+ Import-Module "$($podePath)/src/Pode.psm1" -Force -ErrorAction Stop
+ }
+ else {
+ Import-Module -Name 'Pode' -MaximumVersion 2.99 -ErrorAction Stop
+ }
+}
+catch { throw }
+
+# or just:
+# Import-Module Pode
+
+# create a server, and start listening on port 8081
+Start-PodeServer -Threads 2 {
+
+ # listen on localhost:8081
+ Add-PodeEndpoint -Address localhost -Port 8081 -Protocol Http
+
+ # setup digest auth
+ New-PodeAuthDigestScheme -Algorithm $Algorithm -QualityOfProtection $QualityOfProtection | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock {
+ param($username, $params)
+
+ # here you'd check a real user storage, this is just for example
+ if ($username -ieq 'morty') {
+ return @{
+ User = @{
+ ID = 'M0R7Y302'
+ Name = 'Morty'
+ Type = 'Human'
+ }
+ Password = 'pickle'
+ }
+ }
+
+ return $null
+ }
+ # If QualityOfProtection is 'auth-int' skip GET because it is not supported
+ if ($QualityOfProtection -ne 'auth-int') {
+ # GET request to get list of users (since there's no session, authentication will always happen)
+ Add-PodeRoute -Method Get -Path '/users' -Authentication 'Validate' -ErrorContentType 'application/json' -ScriptBlock {
+ Write-PodeJsonResponse -Value @{
+ Users = @(
+ @{
+ Name = 'Deep Thought'
+ Age = 42
+ },
+ @{
+ Name = 'Leeroy Jenkins'
+ Age = 1337
+ }
+ )
+ }
+ }
+ }
+
+ Add-PodeRoute -Method Post -Path '/users' -Authentication 'Validate' -ErrorContentType 'application/json' -ScriptBlock {
+ if ($WebEvent.data) {
+ Write-PodeJsonResponse -Value $WebEvent.data -StatusCode 200
+ }
+ else {
+ Write-PodeJsonResponse -Value @{success = $false } -StatusCode 400
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/examples/Web-AuthForm.ps1 b/examples/Authentication/Web-AuthForm.ps1
similarity index 92%
rename from examples/Web-AuthForm.ps1
rename to examples/Authentication/Web-AuthForm.ps1
index 45fad7a19..095f6539d 100644
--- a/examples/Web-AuthForm.ps1
+++ b/examples/Authentication/Web-AuthForm.ps1
@@ -22,7 +22,7 @@
You will be redirected to the login page, where you can log in with the credentials provided above.
.LINK
- https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthForm.ps1
+ https://github.com/Badgerati/Pode/blob/develop/examples/Authentication/Web-AuthForm.ps1
.NOTES
Author: Pode Team
@@ -31,7 +31,7 @@
try {
# Determine the script path and Pode module path
- $ScriptPath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
+ $ScriptPath = (Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path))
$podePath = Split-Path -Parent -Path $ScriptPath
# Import the Pode module from the source path if it exists, otherwise from installed modules
@@ -63,7 +63,7 @@ Start-PodeServer -Threads 2 {
Enable-PodeSessionMiddleware -Duration 120 -Extend
# setup form auth (