Skip to content

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:

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'
}