Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication Enhancements: Invoke-PodeAuth & -NoMiddlewareAuthentication #1468

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
66 changes: 66 additions & 0 deletions docs/Tutorials/Authentication/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,69 @@ Start-PodeServer {
New-PodeAuthScheme -Basic | Add-PodeAuthWindowsAd -Name 'Login'
}
```

## Handling Authentication Manually

Sometimes, the standard Pode authentication approach may not provide the flexibility needed to accommodate all use cases. For example, specific requirements such as customizing an OpenAPI definition for an unsuccessful authentication might necessitate a more tailored solution. This approach offers greater control over authentication processing within routes.

### Invoke-PodeAuth

The `Invoke-PodeAuth` function allows for direct invocation of authentication methods and returns the result from `Add-PodeAuth`. This function offers a streamlined approach for manually handling authentication within routes.

#### Usage Example

```powershell
New-PodeAuthScheme -ApiKey | Add-PodeAuth -Name 'APIKey' -Sessionless -ScriptBlock {
param($key)

# Handle missing API key
if (!$key) {
return @{ Success = $false; Reason = 'No X-API-KEY Header found' }
}

# Validate API key
if ($key -eq 'test_user') {
return @{ Success = $true; User = 'test_user'; UserId = 1 }
}

# Return failure for invalid users
return @{ Success = $false; User = $key; UserId = -1; Reason = 'Not existing user' }
}

Add-PodeRoute -Method 'Get' -Path '/api/v3/' -Authentication 'APIKey' -NoMiddlewareAuthentication -ScriptBlock {
$auth = Invoke-PodeAuth -Name 'APIKey'

if ($auth.Success) {
Write-PodeJsonResponse -Value @{ Username = $auth.User }
} else {
Write-PodeJsonResponse -Value @{ message = $auth.Reason; user = $auth.User } -StatusCode 401
}
}
```

The `Auth` object, when managed this way, includes any value returned by the authentication scriptblock, such as `User`, `UserId`, and `Reason` fields as shown in the example.

### NoMiddlewareAuthentication Parameter

The `-NoMiddlewareAuthentication` parameter for `Add-PodeRoute` enables routes to bypass automatic authentication middleware processing. This allows developers to manually handle authentication within route script blocks.

#### Example Usage

```powershell
Add-PodeRoute -Method 'Get' -Path '/api/v3/' -Authentication 'APIKey' -NoMiddlewareAuthentication -ScriptBlock {
$auth = Invoke-PodeAuth -Name 'APIKey'

if ($auth.Success) {
Write-PodeJsonResponse -Value @{ Username = $auth.User }
} else {
Write-PodeJsonResponse -Value @{ message = $auth.Reason; user = $auth.User } -StatusCode 401
}
}
```

### Benefits

- Allows developers to manually control authentication within route logic.
- Provides flexibility to bypass automatic authentication middleware.
- Ensures better customization for complex authentication scenarios.

10 changes: 8 additions & 2 deletions examples/Web-AuthBasicBearer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
.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
Expand Down Expand Up @@ -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 = @(
@{
Expand Down
9 changes: 5 additions & 4 deletions examples/Web-AuthBasicHeader.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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 = @(
@{
Expand Down
94 changes: 92 additions & 2 deletions examples/Web-AuthDigest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,97 @@
.EXAMPLE
To run the sample: ./Web-AuthDigest.ps1

Invoke-RestMethod -Uri http://localhost:8081/users -Method Get
# 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)

# Create the HTTP GET request message
$requestMessage = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $uri)

# Send the request and get the response
$response = $httpClient.SendAsync($requestMessage).Result

# Extract and display the response headers
$response.Headers | ForEach-Object { "$($_.Key): $($_.Value)" }

# Optionally, get content as string if needed
$content = $response.Content.ReadAsStringAsync().Result
$content

.EXAMPLE
No authentication

# Define the URI
$uri = [System.Uri]::new("http://localhost:8081/users")

# Create the HTTP client handler (no authentication)
$handler = [System.Net.Http.HttpClientHandler]::new()

# Create the HTTP client
$httpClient = [System.Net.Http.HttpClient]::new($handler)

# Create the HTTP GET request message
$requestMessage = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $uri)

# Send the request and get the response
$response = $httpClient.SendAsync($requestMessage).Result

# Extract and display the response headers
$response.Headers | ForEach-Object { "$($_.Key): $($_.Value)" }

# Optionally, get content as string if needed
$content = $response.Content.ReadAsStringAsync().Result
$content

.EXAMPLE
Wrong password

# Define the URI and wrong credentials
$uri = [System.Uri]::new("http://localhost:8081/users")
$wrongUsername = "wrongUser"
$wrongPassword = "wrongPassword"

# Create a credential cache and add Digest authentication with incorrect credentials
$credentialCache = [System.Net.CredentialCache]::new()
$networkCredential = [System.Net.NetworkCredential]::new($wrongUsername, $wrongPassword)
$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)

# Create the HTTP GET request message
$requestMessage = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $uri)

# Send the request and get the response
$response = $httpClient.SendAsync($requestMessage).Result

# Display the response status code (to check for 401 Unauthorized)
$response.StatusCode

# Extract and display the response headers
$response.Headers | ForEach-Object { "$($_.Key): $($_.Value)" }

# Optionally, get content as string if needed
$content = $response.Content.ReadAsStringAsync().Result
$content


.LINK
https://github.com/Badgerati/Pode/blob/develop/examples/Web-AuthDigest.ps1
Expand Down Expand Up @@ -62,7 +152,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 = @(
@{
Expand Down
2 changes: 1 addition & 1 deletion examples/Web-AuthForm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Start-PodeServer -Threads 2 {
Enable-PodeSessionMiddleware -Duration 120 -Extend

# setup form auth (<form> in HTML)
New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock {
New-PodeAuthScheme -Form | Add-PodeAuth -Name 'Login' -FailureUrl '/login' -SuccessUrl '/' -ScriptBlock {
param($username, $password)

# here you'd check a real user storage, this is just for example
Expand Down
Loading