Overview
Authentication can either be sessionless (requiring validation on every request), or session-persistent (only requiring validation once, and then checks against a session-signed cookie/header).
Info
To use session-persistent authentication you will also need to use Session Middleware.
To set up and use authentication in Pode you need to use the New-PodeAuthScheme
and Add-PodeAuth
functions.
You can also set up Authorisation for use with Authentication as well.
Schemes
The New-PodeAuthScheme
function allows you to create and configure authentication schemes, or you can create your own Custom authentication schemes. These schemes can then be piped into Add-PodeAuth
. The role of a scheme is to parse the request for any user credentials, or other information, that is required for a user to be authenticated.
The following schemes are supported:
- API Key
- Azure AD
- Basic
- Bearer
- Client Certificate
- Digest
- Form
- JWT (Done using Bearer or API Key)
- OAuth2
Or you can define a custom scheme:
Validators
The Add-PodeAuth
function allows you to add authentication validators to your server. You can have many methods configured, defining which one to validate against using the -Authentication
parameter on Routes. Their job is to validate the information parsed from the supplied scheme to ensure a user is valid.
An example of using Add-PodeAuth
for Basic sessionless authentication is as follows:
Start-PodeServer {
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
param($username, $pass)
# logic to check user
return @{ 'user' = $user }
}
}
The -Name
of the authentication method must be unique. The -Scheme
comes from the object returned via the New-PodeAuthScheme
function, and can also be piped in.
The -ScriptBlock
is used to validate a user, checking if they exist and the password is correct (or checking if they exist in some data store). If the ScriptBlock succeeds, then a User
object needs to be returned from the script as @{ User = $user }
. If $null
, or a null user, is returned then the script is assumed to have failed - meaning the user will have failed authentication, and a 401 response is returned.
Custom Status and Headers
When authenticating a user in Pode, any failures will return a 401 response with a generic message. You can inform Pode to return a custom message/status from Add-PodeAuth
by returning the relevant hashtable values.
You can return a custom status code as follows:
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
return @{ Code = 403 }
}
or a custom message (the status description) as follows, which can be used with a custom status code or on its own:
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
return @{ Message = 'Custom authentication failed message' }
}
You can also set custom headers on the response; these will be set regardless if authentication fails or succeeds:
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
return @{
Headers = @{
HeaderName = 'HeaderValue'
}
}
}
If you're defining an authenticator that needs to send back a Challenge, then you can also do this by setting the response Code property to 401, and/or by also supplying a Challenge property.
This Challenge property is a string and will be automatically appended onto the WWW-Authenticate
Header. It does not need to include the Authentication Type or Realm (these will be added for you).
For example, in Digest, you could return:
return @{
Code = 401
Challenge = 'qop="auth", nonce="<some-random-guid>"'
}
Authenticate Type/Realm
When authentication fails, and a 401 response is returned, then Pode will also attempt to respond to the client with a WWW-Authenticate
header (if you've manually set this header using the custom headers from above, then the custom header will be used instead). For the inbuilt types, such as Basic, this Header will always be returned on a 401 response.
You can set the -Name
and -Realm
of the header using the New-PodeAuthScheme
function. If no Name is supplied, then the header will not be returned - also if there is no Realm, then this will not be added to the header.
For example, if you setup Basic authenticate with a custom Realm as follows:
New-PodeAuthScheme -Basic -Realm 'Enter creds to access site'
Then on a 401 response, the `WWW-Authenticate`` header will look as follows:
WWW-Authenticate: Basic realm="Enter creds to access site"
Note
If no Realm was set then it would just look as follows: WWW-Authenticate: Basic
Redirecting
When building custom authenticators, it might be required that you redirect mid-auth and stop processing the current request. To achieve this you can return the following from the scriptblock of New-PodeAuthScheme
or Add-PodeAuth
:
return @{ IsRedirected = $true }
An example of this could be OAuth2, where the authentication needs to redirect to the Provider.
Routes/Middleware
To use authentication on a specific route, you can use the -Authentication
parameter on the Add-PodeRoute
function; this takes the Name supplied to the -Name
parameter on Add-PodeAuth
. This will set the authentication up to run before other route middleware.
An example of using some Basic authentication on a REST API route is as follows:
Start-PodeServer {
Add-PodeRoute -Method Get -Path '/api/users' -Authentication 'BasicAuth' -ScriptBlock {
# route logic
}
}
The Add-PodeAuthMiddleware
function lets you set up authentication as global middleware - so it will run against all routes.
An example of using some Basic authentication on all REST API routes is as follows:
Start-PodeServer {
Add-PodeAuthMiddleware -Name 'GlobalAuth' -Authentication 'BasicAuth' -Route '/api/*'
}
If any of the authentication middleware fails, then a 401 response is returned for the route. On success, it will allow the Route logic to be invoked. If Session Middleware has been configured then an authenticated session is also created for future requests, using a signed session cookie/header.
When the user makes another call using the same authenticated session and that cookie/header is present, then the authentication middleware will detect the already authenticated session and skip validation. If you're using sessions and you don't want to check the session or store the user against a session, then use the -Sessionless
switch on Add-PodeAuth
.
Users
After successful validation, an Auth
object will be created for use against the current web event. This Auth
object will be accessible via the argument supplied to Routes and Middleware.
The Auth
object will also contain:
Name | Description |
---|---|
User | Details about the authenticated user |
IsAuthenticated | States if the request is for an authenticated user, can be $true , $false or $null |
Store | States whether the authentication is for a session, and will be stored as a cookie |
IsAuthorised | If using Authorisation, this value will be $true or $false depending on whether or not the authenticated user is authorised to access the Route. If not using Authorisation this value will just be $true |
Name | The name(s) of the Authentication methods which passed - useful if you're using merged Authentications and you want to know which one(s) passed |
The following example gets the user's name from the Auth
object:
Add-PodeRoute -Method Get -Path '/' -Authentication 'Login' -Login -ScriptBlock {
Write-PodeViewResponse -Path 'index' -Data @{
'Username' = $WebEvent.Auth.User.Name
}
}
Merging
For advanced authentication scenarios, you can merge multiple authentication methods using Merge-PodeAuth
. This allows you to have an authentication strategy where multiple authentications are required to pass for a user to be fully authenticated, or you could have fallback authentications should the primary authentication fail.
When you merge authentication methods, they become a new authentication method that you can supply to -Authentication
on Routes. By default the merged authentications expect just one to pass, but you can state that you require all to pass via the -Valid
parameter on Merge-PodeAuth
.
All
For example, you might require both an API Key and Basic authentication for a user to view a Route, in which case you would set something up as follows - in the below example we assume that the User object returned by the Basic authentication method is the User details we want to use for the Route, by specifying -MergeDefault
on Merge-PodeAuth
:
# setup apikey auth
New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Sessionless -ScriptBlock {
param($key)
# here you'd check a real user storage, this is just for example
if ($key -ieq 'test-api-key') {
return @{
User = @{ Name = 'Morty-API' }
Groups = @('Software')
}
}
return $null
}
# setup basic auth
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Basic' -Sessionless -ScriptBlock {
param($username, $password)
# here you'd check a real user storage, this is just for example
if ($username -eq 'morty' -and $password -eq 'pickle') {
return @{
User = @{ Name = 'Morty-BASIC' }
Groups = @('Platform')
}
}
return @{ Message = 'Invalid details supplied' }
}
# merge the authentications together, and require all to pass - using the User result object of Basic
Merge-PodeAuth -Name 'MergedAuth' -Authentication 'ApiKey', 'Basic' -Valid All -MergeDefault 'Basic'
# use the merged auth in a route
Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -ScriptBlock {
# this would write back "Name = Morty-BASIC"
Write-PodeJsonResponse -Value @{
Users = $WebEvent.Auth.User
}
}
The above example assumes a simple scenario of using the Basic authentication's User object, but what if we did want to use both authentication User objects and merge them into one? Well to achieve this you can supply a -ScriptBlock
. This scriptblock will be supplied a hashtable of all the result objects, from all authentication methods used in the Valid=All merged authentication method, which you can then use to construct a single result object. The result objects themselves will contain a User and Headers hashtable - more commonly just the User.
Using a similar example to the example above, the following will use a -ScriptBlock
instead to merge the User objects from the API key and Basic authentication methods into one User object:
# setup apikey auth
New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Sessionless -ScriptBlock {
param($key)
# here you'd check a real user storage, this is just for example
if ($key -ieq 'test-api-key') {
return @{ User = @{
Name = 'Morty'
Groups = @('Software')
} }
}
return $null
}
# setup basic auth
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Basic' -Sessionless -ScriptBlock {
param($username, $password)
# here you'd check a real user storage, this is just for example
if ($username -eq 'morty' -and $password -eq 'pickle') {
return @{ User = @{
Name = 'Morty'
Groups = @('Platform')
} }
}
return @{ Message = 'Invalid details supplied' }
}
# merge the authentications together, and require all to pass - using the User result object of Basic
Merge-PodeAuth -Name 'MergedAuth' -Authentication 'ApiKey', 'Basic' -Valid All -ScriptBlock {
param($results)
$apiUser = $results['ApiKey'].User
$basicUser = $results['Basic'].User
return @{
User = @{
User = $basicUser.Username
Groups = @($apiUser.Groups + $basicUser.Groups) | Sort-Object -Unique
}
}
}
# use the merged auth in a route
Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -ScriptBlock {
# this would write back "Name = Morty" and "Groups = Platform, Software"
Write-PodeJsonResponse -Value @{
Users = $WebEvent.Auth.User
}
}
One
Or, you might want to check for a JWT Bearer token, but if one isn't present default to Azure AD authentication so we can store the returned access token and utilise the first Bearer auth later on:
# setup jwt bearer auth
New-PodeAuthScheme -Bearer -AsJWT | Add-PodeAuth -Name 'Bearer' -Sessionless -ScriptBlock {
param($payload)
# check payload
return @{ User = @{ Name = 'Morty' } }
}
# setup basic azure-ad auth
$basic = New-PodeAuthScheme -Basic
$scheme = New-PodeAuthAzureADScheme -ClientID '<clientId>' -ClientSecret '<clientSecret>' -Tenant '<tenant>' -InnerScheme $basic
$scheme | Add-PodeAuth -Name 'AzureAD' -Sessionless -ScriptBlock {
param($user, $accessToken, $refreshToken, $response)
# check if the user is valid
return @{ User = $user }
}
# merge the authentications together, and require just one to pass
Merge-PodeAuth -Name 'MergedAuth' -Authentication 'Bearer', 'AzureAD' -Valid One
# use the merged auth in a route
Add-PodeRoute -Method Get -Path '/users' -Authentication 'MergedAuth' -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = $WebEvent.Auth.User
}
}
Advanced
You can also merge other merged authentication methods. This lets you build scenarios where you require an API key, and then need either a JWT Bearer token or OAuth2 to pass. As a very brief example:
# setup apikey auth
New-PodeAuthScheme -ApiKey -Location Header | Add-PodeAuth -Name 'ApiKey' -Sessionless -ScriptBlock {
param($key)
# here you'd check a real user storage, this is just for example
if ($key -ieq 'test-api-key') {
return @{ User = @{ Name = 'Morty' } }
}
return $null
}
# setup jwt bearer auth
New-PodeAuthScheme -Bearer -AsJWT | Add-PodeAuth -Name 'Bearer' -Sessionless -ScriptBlock {
param($payload)
# check payload
return @{ User = @{ Name = 'Morty' } }
}
# setup basic azure-ad auth
$basic = New-PodeAuthScheme -Basic
$scheme = New-PodeAuthAzureADScheme -ClientID '<clientId>' -ClientSecret '<clientSecret>' -Tenant '<tenant>' -InnerScheme $basic
$scheme | Add-PodeAuth -Name 'AzureAD' -Sessionless -ScriptBlock {
param($user, $accessToken, $refreshToken, $response)
# check if the user is valid
return @{ User = $user }
}
# merge the authentications together, and require just one to pass
Merge-PodeAuth -Name 'JwtMergedAuth' -Authentication 'Bearer', 'AzureAD' -Valid One
# merge the above merged auth with the apikey auth, and require both to pass
Merge-PodeAuth -Name 'ApiMergedAuth' -Authentication 'ApiKey', 'JwtMergedAuth' -Valid All -MergeDefault 'ApiKey'
# use the merged auth in a route
Add-PodeRoute -Method Get -Path '/users' -Authentication 'ApiMergedAuth' -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = $WebEvent.Auth.User
}
}
Users
When using a single authentication method, the authenticated user's details will be accessible at $WebEvent.Auth.User
.
The same still applies for merged authentication methods; if you're using Merge-PodeAuth -Valid One
then there will only be one User object to set, but if you're using Merge-PodeAuth -Valid All
then the User objects of all authentication methods will need to be merged into one User object - this can be done by either supplying -MergeDefault
or -ScriptBlock
to Merge-PodeAuth
when -Valid
is set to All. (see All for more info).
Parameters
Similar to Add-PodeAuth
the Merge-PodeAuth
function also supports the -FailureUrl
, -SuccessUrl
, etc. parameters. When set on the merged authentication method, they will be used as a fallback if the initial authentication methods don't have them set. This means you can set up 2 authentication methods without Failure URLs, and then merge them with a default Failure URL of /login
on the merged authentication.
Inbuilt Authenticators
Over time Pode will start to support inbuilt authentication methods - such as Windows Active Directory. More information can be found in the Inbuilt section.
For example, the below would use the inbuilt Windows AD authentication method:
Start-PodeServer {
New-PodeAuthScheme -Basic | Add-PodeAuthWindowsAd -Name 'Login'
}