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

Adds client certificate authentication support #610

Merged
merged 4 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/Getting-Started/Migrating/1X-to-2X.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ There's also a new [`Add-PodeAuthMiddleware`](../../../Functions/Authentication/

Furthermore, the OpenAPI functions for `Set-PodeOAAuth` and `Set-PodeOAGlobalAuth` have been removed. The new [`Add-PodeAuthMiddleware`](../../../Functions/Authentication/Add-PodeAuthMiddleware) function and `-Authentication` parameter on [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) set these up for you automatically in OpenAPI.

On `Add-PodeAuth`, `Add-PodeAuthWindowsAd`, and `Add-PodeAuthUserFile` the `-Type` parameter has been renamed to `-Scheme`. If you have always piped `New-PodeAuthScheme` (formally `New-PodeAuthType`) into them, then this won't affect you.

### Endpoint and Protocol

On the following functions:
Expand Down
88 changes: 88 additions & 0 deletions docs/Tutorials/Authentication/Methods/ClientCertificate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Client Certificate

Client Certificate Authentication is when the server requires the client to supply a certificate on the request, to verify themselves with the server. This only works over HTTPS connections.

If at any point to you need to access the client's certificate outside of this validator, then it can be found on the web event object at `Request.ClientCertificate`.

## Setup

To setup and start using Client Certificate Authentication in Pode you use the `New-PodeAuthScheme -ClientCertificate` function, and then pipe this into the [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function. The [`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth) function's ScriptBlock is supplied the client's certificate, and any SSL errors that may have occurred (like chain issues, etc).

You will also need to supply `-AllowClientCertificate` to [`Add-PodeEndpoint`], and ensure the `-Protocol` is HTTPS:

```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned -AllowClientCertificate

New-PodeAuthScheme -ClientCertificate | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
param($cert, $errors)

# check if the client's cert is valid

return @{ User = $user }
}
}
```

By default, Pode will ensure a certificate was supplied, and also ensure the certificate's Before/After dates are valid - if not, a 401 response will be returned.

## Middleware

Once configured you can start using Client Certificate Authentication to validate incoming Requests. You can either configure the validation to happen on every Route as global Middleware, or as custom Route Middleware.

The following will use Client Certificate Authentication to validate every request on every Route:

```powershell
Start-PodeServer {
Add-PodeAuthMiddleware -Name 'GlobalAuthValidation' -Authentication 'Login'
}
```

Whereas the following example will use Client Certificate authentication to only validate requests on specific a Route:

```powershell
Start-PodeServer {
Add-PodeRoute -Method Get -Path '/info' -Authentication 'Login' -ScriptBlock {
# logic
}
}
```

## Full Example

The following full example of Basic authentication will setup and configure authentication, validate that a users username/password is valid, and then validate on a specific Route:

```powershell
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned -AllowClientCertificate

# setup client cert authentication to validate a user
New-PodeAuthScheme -ClientCertificate | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
param($cert, $errors)

# validate the thumbprint - here you would check a real cert store, or database
if ($cert.Thumbprint -ieq '3571B3BE3CA202FA56F73691FC258E653D0874C1') {
return @{
User = @{
ID ='M0R7Y302'
Name = 'Morty'
Type = 'Human'
}
}
}

# an invalid cert
return @{ Message = 'Invalid certificate supplied' }
}

# check the request on this route against the authentication
Add-PodeRoute -Method Get -Path '/cpu' -Authentication 'Login' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'cpu' = 82 }
}

# this route will not be validated against the authentication
Add-PodeRoute -Method Get -Path '/memory' -ScriptBlock {
Write-PodeJsonResponse -Value @{ 'memory' = 14 }
}
}
```
30 changes: 15 additions & 15 deletions docs/Tutorials/Authentication/Methods/Custom.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Custom

Custom authentication works much like the inbuilt types (Basic/Form/etc), but allows you to specify your own parsing logic, as well as any custom options that might be required.
Custom authentication works much like the inbuilt schemes (Basic/Form/etc), but allows you to specify your own parsing logic, as well as any custom options that might be required.

## Setup and Parsing

Expand All @@ -12,8 +12,8 @@ The `-ScriptBlock` on [`New-PodeAuthScheme`](../../../../Functions/Authenticatio

```powershell
Start-PodeServer {
# define a new custom authentication type
$custom_type = New-PodeAuthScheme -Custom -ScriptBlock {
# define a new custom authentication scheme
$custom_scheme = New-PodeAuthScheme -Custom -ScriptBlock {
param($e, $opts)

# get client/user/password field names
Expand All @@ -30,8 +30,8 @@ Start-PodeServer {
return @($client, $username, $password)
}

# now, add a new custom authentication method using the type you created above
$custom_type | Add-PodeAuth -Name 'Login' -ScriptBlock {
# now, add a new custom authentication validator using the scheme you created above
$custom_scheme | Add-PodeAuth -Name 'Login' -ScriptBlock {
param($client, $username, $password)

# check if the client is valid in some database
Expand All @@ -47,9 +47,9 @@ Start-PodeServer {

## Post Validation

The typical setup of authentication is that you create some type to parse the request ([`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme)), and then you pipe this into a validator/method to validate the parsed user's credentials ([`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth)).
The typical setup of authentication is that you create some scheme to parse the request ([`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme)), and then you pipe this into a validator to validate the parsed user's credentials ([`Add-PodeAuth`](../../../../Functions/Authentication/Add-PodeAuth)).

There is however also an optional `-PostValidator` ScriptBlock that can be passed to your Custom Authentication type on the [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme) function. This `-PostValidator` script runs after normal user validation, and is supplied the current [web event](../../../WebEvent), the original splatted array returned from the [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme) ScriptBlock, the result HashTable from the user validator from `Add-PodeAuth`, and the `-ArgumentList` HashTable from `New-PodeAuthScheme`. You can use this script to re-generate any hashes for further validation, but if successful you *must* return the User object again (ie: re-return the last parameter which is the original validation result).
There is however also an optional `-PostValidator` ScriptBlock that can be passed to your Custom Authentication scheme on the [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme) function. This `-PostValidator` script runs after normal user validation, and is supplied the current [web event](../../../WebEvent), the original splatted array returned from the [`New-PodeAuthScheme`](../../../../Functions/Authentication/New-PodeAuthScheme) ScriptBlock, the result HashTable from the user validator from `Add-PodeAuth`, and the `-ArgumentList` HashTable from `New-PodeAuthScheme`. You can use this script to re-generate any hashes for further validation, but if successful you *must* return the User object again (ie: re-return the last parameter which is the original validation result).

For example, if you have a post validator script for the above Client Custom Authentication, then it would be supplied the following parameters:

Expand All @@ -59,14 +59,14 @@ For example, if you have a post validator script for the above Client Custom Aut
* Password
* ClientName
* Validation Result
* Type ArgumentsList
* Scheme ArgumentsList

For example:

```powershell
Start-PodeServer {
# define a new custom authentication type
$custom_type = New-PodeAuthScheme -Custom -ScriptBlock {
# define a new custom authentication scheme
$custom_scheme = New-PodeAuthScheme -Custom -ScriptBlock {
param($e, $opts)

# get client/user/password field names
Expand All @@ -91,8 +91,8 @@ Start-PodeServer {
return $result
}

# now, add a new custom authentication method using the type you created above
$custom_type | Add-PodeAuth -Name 'Login' -ScriptBlock {
# now, add a new custom authentication method using the scheme you created above
$custom_scheme | Add-PodeAuth -Name 'Login' -ScriptBlock {
param($client, $username, $password)

# check if the client is valid in some database
Expand Down Expand Up @@ -133,8 +133,8 @@ The following full example of Custom authentication will setup and configure aut
Start-PodeServer {
Add-PodeEndpoint -Address * -Port 8080 -Protocol Http

# define a new custom authentication type
$custom_type = New-PodeAuthScheme -Custom -ScriptBlock {
# define a new custom authentication scheme
$custom_scheme = New-PodeAuthScheme -Custom -ScriptBlock {
param($e, $opts)

# get client/user/pass field names to get from payload
Expand All @@ -152,7 +152,7 @@ Start-PodeServer {
}

# now, add a new custom authentication method
$custom_type | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
$custom_scheme | Add-PodeAuth -Name 'Login' -Sessionless -ScriptBlock {
param($client, $username, $password)

# check if the client is valid
Expand Down
32 changes: 24 additions & 8 deletions docs/Tutorials/Authentication/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ To setup and use authentication in Pode you need to use the [`New-PodeAuthScheme

## Usage

### Types/Parsers
### Schemes

The [`New-PodeAuthScheme`](../../../Functions/Authentication/New-PodeAuthScheme) function allows you to create and configure authentication types/parsers, or you can create your own Custom authentication types. These types can then be used on the [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) function. There job is to parse the request for any user credentials, or other information, that is required for a user to be authenticated.
The [`New-PodeAuthScheme`](../../../Functions/Authentication/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 used on the [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) function. There job is to parse the request for any user credentials, or other information, that is required for a user to be authenticated.

An example of creating some authentication types is as follows:
An example of creating some authentication schemes is as follows:

```powershell
Start-PodeServer {
$basic_auth = New-PodeAuthScheme -Basic
$digest_auth = New-PodeAuthScheme -Digest
$bearer_auth = New-PodeAuthScheme -Bearer
$form_auth = New-PodeAuthScheme -Form
$cert_auth = New-PodeAuthScheme -ClientCertificate
}
```

Where as the following example defines a Custom type that retrieves the user's credentials from the Request's Payload:
Where as the following example defines a Custom scheme that retrieves the user's credentials from the Request's Payload:

```powershell
Start-PodeServer {
Expand All @@ -47,9 +48,9 @@ Start-PodeServer {
}
```

### Method/Validator
### Validators

The [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth) function allows you to add authentication methods/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.
The [`Add-PodeAuth`](../../../Functions/Authentication/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`](../../../Functions/Authentication/Add-PodeAuth) for Basic sessionless authentication is as follows:

Expand All @@ -63,7 +64,7 @@ Start-PodeServer {
}
```

The `-Name` of the authentication method must be unique. The `-Type` comes from the object returned via the [`New-PodeAuthScheme`](../../../Functions/Authentication/New-PodeAuthScheme) function, and can also be piped in.
The `-Name` of the authentication method must be unique. The `-Scheme` comes from the object returned via the [`New-PodeAuthScheme`](../../../Functions/Authentication/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.

Expand Down Expand Up @@ -132,9 +133,24 @@ 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`

#### WebEvent

By default the web event for the current request is not supplied to the validator's ScriptBlock. If you ever need the web event though, such as for accessing other request details like a client certificate, then you can supply the `-PassEvent` switch on [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). With this, Pode will supply the current web event as the first parameter:

```powershell
Start-PodeServer {
New-PodeAuthScheme -Basic | Add-PodeAuth -Name 'Login' -Sessionless -PassEvent -ScriptBlock {
param($e, $username, $pass)
# logic to check user
# logic to check client cert (found at: $e.Request.ClientCertificate)
return @{ 'user' = $user }
}
}
```

### Routes/Middleware

To use an authentication type on a specific route, you can use the `-Authentication` parameter on the [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) function; this takes the Name supplied to the `-Name` parameter on [`Add-PodeAuth`](../../../Functions/Authentication/Add-PodeAuth). This will set the authentication up to run before other route middleware.
To use an authentication on a specific route, you can use the `-Authentication` parameter on the [`Add-PodeRoute`](../../../Functions/Routes/Add-PodeRoute) function; this takes the Name supplied to the `-Name` parameter on [`Add-PodeAuth`](../../../Functions/Authentication/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:

Expand Down
2 changes: 2 additions & 0 deletions docs/Tutorials/WebEvent.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
## Customise

The web event itself is just a HashTable, which means you can add your own properties to it within Middleware for further use in other Middleware down the flow, or in the Route itself.

Make sure these custom properties have a unique name, so as to not clash with already existing properties.
48 changes: 48 additions & 0 deletions examples/web-auth-clientcert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode

# create a server, flagged to generate a self-signed cert for dev/testing, but allow client certs for auth
Start-PodeServer {

# bind to ip/port and set as https with self-signed cert
Add-PodeEndpoint -Address * -Port 8443 -Protocol Https -SelfSigned -AllowClientCertificate

# set view engine for web pages
Set-PodeViewEngine -Type Pode

# setup client cert auth
New-PodeAuthScheme -ClientCertificate | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock {
param($cert, $errors)

# validate the thumbprint - here you would check a real cert store, or database
if ($cert.Thumbprint -ieq '3571B3BE3CA202FA56F73691FC258E653D0874C1') {
return @{
User = @{
ID ='M0R7Y302'
Name = 'Morty'
Type = 'Human'
}
}
}

# an invalid cert
return @{ Message = 'Invalid certificate supplied' }
}

# GET request for web page at "/"
Add-PodeRoute -Method Get -Path '/' -Authentication 'Validate' -ScriptBlock {
param($e)
#$e.Request.ClientCertificate | out-default
Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); }
}

# GET request throws fake "500" server error status code
Add-PodeRoute -Method Get -Path '/error' -Authentication 'Validate' -ScriptBlock {
param($e)
Set-PodeResponseStatus -Code 500
}

}
4 changes: 2 additions & 2 deletions examples/web-pages-https.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ Start-PodeServer {

# GET request for web page at "/"
Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
param($session)
param($e)
Write-PodeViewResponse -Path 'simple' -Data @{ 'numbers' = @(1, 2, 3); }
}

# GET request throws fake "500" server error status code
Add-PodeRoute -Method Get -Path '/error' -ScriptBlock {
param($session)
param($e)
Set-PodeResponseStatus -Code 500
}

Expand Down
2 changes: 1 addition & 1 deletion src/Listener/PodeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private void NewRequest()
// attempt to open the request stream
try
{
Request.Open(PodeSocket.Certificate, PodeSocket.Protocols);
Request.Open(PodeSocket.Certificate, PodeSocket.Protocols, PodeSocket.AllowClientCertificate);
State = PodeContextState.Open;
}
catch
Expand Down
Loading