diff --git a/docs/Tutorials/Authentication/Overview.md b/docs/Tutorials/Authentication/Overview.md index 19ddacece..32dfbd738 100644 --- a/docs/Tutorials/Authentication/Overview.md +++ b/docs/Tutorials/Authentication/Overview.md @@ -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. + diff --git a/examples/Web-AuthBasicBearer.ps1 b/examples/Web-AuthBasicBearer.ps1 index f6c73820d..3cd1ccaf6 100644 --- a/examples/Web-AuthBasicBearer.ps1 +++ b/examples/Web-AuthBasicBearer.ps1 @@ -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 @@ -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-AuthBasicHeader.ps1 b/examples/Web-AuthBasicHeader.ps1 index 0fb0f8d27..eca16bba7 100644 --- a/examples/Web-AuthBasicHeader.ps1 +++ b/examples/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" } @@ -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/Web-AuthDigest.ps1 b/examples/Web-AuthDigest.ps1 index 7c26eef53..65c8b37ed 100644 --- a/examples/Web-AuthDigest.ps1 +++ b/examples/Web-AuthDigest.ps1 @@ -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 @@ -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 = @( @{ diff --git a/examples/Web-AuthForm.ps1 b/examples/Web-AuthForm.ps1 index 45fad7a19..03fe2147d 100644 --- a/examples/Web-AuthForm.ps1 +++ b/examples/Web-AuthForm.ps1 @@ -63,7 +63,7 @@ Start-PodeServer -Threads 2 { Enable-PodeSessionMiddleware -Duration 120 -Extend # setup form auth (