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 Session Authentication, and Refactor Authorisation #1166

Merged
merged 2 commits into from
Oct 12, 2023
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
59 changes: 59 additions & 0 deletions docs/Tutorials/Authentication/Inbuilt/Session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Sessions

Pode has support for Sessions when using Authentication, by default if you call a Route with authentication and you already have a session on the request then you're "authenticated". If there's no session, then the authentication logic is invoked, and if the details are invalid you're redirected to a login screen.

If you have a need to use multiple authentication methods for login, and the user can chose the one they want, then on Routes there's no simple way of say which authentication is required. However, under the hood they all create a session object which can be used as a "shared" authentication method.

This sessions authenticator can be used to pass authentication if a valid session in on the request, or to automatically redirect to a login page if there is no valid session. Useful for if you're using multiple authentication methods the user can choose from.

## Usage

To add sessions authentication you can use [`Add-PodeAuthSession`](../../../../Functions/Authentication/Add-PodeAuthSession). The following example will validate a user's credentials on login using Form authentication, but the home page uses session authentication to just verify there's a valid session:

```powershell
Start-PodeServer {
# endpoint and view engine
Add-PodeEndpoint -Address * -Port 8085 -Protocol Http
Set-PodeViewEngine -Type Pode

# enable sessions
Enable-PodeSessionMiddleware -Duration 120 -Extend

# setup form auth for login
New-PodeAuthScheme -Form | Add-PodeAuth -Name 'FormAuth' -FailureUrl '/login' -SuccessUrl '/' -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' } }
}

return @{ Message = 'Invalid details supplied' }
}

# setup session auth for routes and logout
Add-PodeAuthSession -Name 'SessionAuth' -FailureUrl '/login'

# home page: use session auth, and redirect to login if no valid session
Add-PodeRoute -Method Get -Path '/' -Authentication SessionAuth -ScriptBlock {
Write-PodeViewResponse -Path 'auth-home'
}

# login page: use form auth here to actually verify the user's credentials
Add-PodeRoute -Method Get -Path '/login' -Authentication FormAuth -Login -ScriptBlock {
Write-PodeViewResponse -Path 'auth-login' -FlashMessages
}

# login check: again, use form auth
Add-PodeRoute -Method Post -Path '/login' -Authentication FormAuth -Login

# logout - use session auth here to purge the session
Add-PodeRoute -Method Post -Path '/logout' -Authentication SessionAuth -Logout
}
```

### User Object

If a valid session is found on the request, then the user object set at `$WebEvent.Auth.User` will take the form of which ever authentication method using for login.

The user object will simply be loaded from the session.
2 changes: 1 addition & 1 deletion docs/Tutorials/Authentication/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ The `Auth` object will also contain:
| 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](../../Authorisation/Overview), 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` |
| Access | If using [Authorisation](../../Authorisation/Overview), this property will contain the access values for the User per Access method. If not using Authorisation this value will just be an empty hashtable |
| 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 get the user's name from the `Auth` object:

Expand Down
109 changes: 55 additions & 54 deletions docs/Tutorials/Authorisation/Overview.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions examples/public/styles/simple.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
body {
background-color: rebeccapurple;
}

a {
color: white;
}

a:visited {
color: yellow;
}
4 changes: 2 additions & 2 deletions examples/tcp-server-auth.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Start-PodeServer -Threads 2 {
New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging

# create a role access method get retrieves roles from a database
Add-PodeAuthAccess -Name 'RoleExample' -Type Role -ScriptBlock {
Add-PodeAccess -Name 'RoleExample' -Type Role -ScriptBlock {
param($username)
if ($username -ieq 'morty') {
return @('Developer')
Expand All @@ -25,7 +25,7 @@ Start-PodeServer -Threads 2 {

# setup a Verb that only allows Developers
Add-PodeVerb -Verb 'EXAMPLE :username' -ScriptBlock {
if (!(Test-PodeAuthAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) {
if (!(Test-PodeAccess -Name 'RoleExample' -Destination 'Developer' -ArgumentList $TcpEvent.Parameters.username)) {
Write-PodeTcpClient -Message "Forbidden Access"
'Forbidden!' | Out-Default
return
Expand Down
23 changes: 23 additions & 0 deletions examples/views/auth-about.pode
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<html>
<head>
<title>Auth About</title>
<link rel="stylesheet" type="text/css" href="/styles/simple.css">
</head>
<body>

Hello, this is an about page!
<br />

<p>
<a href='/'>Home</a>
<a href='/register'>Register</a>
</p>

<form action="/logout" method="post">
<div>
<input type="submit" value="Logout"/>
</div>
</form>

</body>
</html>
5 changes: 5 additions & 0 deletions examples/views/auth-home.pode
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
Your session will expire on $($data.Expiry)
<br />

<p>
<a href='/about'>About</a>
<a href='/register'>Register</a>
</p>

<form action="/logout" method="post">
<div>
<input type="submit" value="Logout"/>
Expand Down
23 changes: 23 additions & 0 deletions examples/views/auth-register.pode
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<html>
<head>
<title>Auth Register</title>
<link rel="stylesheet" type="text/css" href="/styles/simple.css">
</head>
<body>

Hello, this is a register page!
<br />

<p>
<a href='/'>Home</a>
<a href='/about'>About</a>
</p>

<form action="/logout" method="post">
<div>
<input type="submit" value="Logout"/>
</div>
</form>

</body>
</html>
76 changes: 60 additions & 16 deletions examples/web-auth-basic-access.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ Start-PodeServer -Threads 2 {
Add-PodeEndpoint -Address * -Port 8085 -Protocol Http

# setup RBAC
Add-PodeAuthAccess -Type Role -Name 'TestRbac'
Add-PodeAuthAccess -Type Group -Name 'TestGbac'
# Add-PodeAuthAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator {
# Add-PodeAccess -Type Role -Name 'TestRbac'
# Add-PodeAccess -Type Group -Name 'TestGbac'
New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'TestRbac'
New-PodeAccessScheme -Type Group | Add-PodeAccess -Name 'TestGbac'
# Add-PodeAccess -Type Custom -Name 'TestRbac' -Path 'CustomAccess' -Validator {
# param($userRoles, $customValues)
# return $userRoles.Example -iin $customValues.Example
# }

Merge-PodeAuthAccess -Name 'TestMerged' -Access 'TestRbac', 'TestGbac' -Valid All
Merge-PodeAccess -Name 'TestMergedAll' -Access 'TestRbac', 'TestGbac' -Valid All
Merge-PodeAccess -Name 'TestMergedOne' -Access 'TestRbac', 'TestGbac' -Valid One

# setup basic auth (base64> username:password in header)
New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Access 'TestMerged' -Sessionless -ScriptBlock {
New-PodeAuthScheme -Basic -Realm 'Pode Example Page' | Add-PodeAuth -Name 'Validate' -Sessionless -ScriptBlock {
param($username, $password)

# here you'd check a real user storage, this is just for example
Expand All @@ -48,7 +51,7 @@ Start-PodeServer -Threads 2 {
Type = 'Human'
Username = 'm.orty'
Roles = @('Developer')
Groups = @('Software')
Groups = @('Software', 'Admins')
CustomAccess = @{ Example = 'test-val-1' }
}
}
Expand All @@ -62,40 +65,81 @@ Start-PodeServer -Threads 2 {
$WebEvent.Auth | Out-Default
}

# POST request to get list of users - there's no Roles, so any auth'd user can access
Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -Group 'Ops' -ScriptBlock {
# POST request to get list of users - there's no Access, so any auth'd user can access
Add-PodeRoute -Method Post -Path '/users-all' -Authentication 'Validate' -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Deep Thought'
Age = 42
}
)
}
}

# POST request to get list of users - only Developer roles can access
Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Role Developer -Group Software -ScriptBlock {
Add-PodeRoute -Method Post -Path '/users-dev' -Authentication 'Validate' -Access 'TestRbac' -Role Developer -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Leeroy Jenkins'
Age = 1337
}
)
}
} -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-1' }
}

# POST request to get list of users - only QA roles can access
Add-PodeRoute -Method Post -Path '/users-qa' -Authentication 'Validate' -Access 'TestRbac' -Role QA -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Nikola Tesla'
}
)
}
}

# POST request to get list of users - only users in the SOftware group can access
Add-PodeRoute -Method Post -Path '/users-soft' -Authentication 'Validate' -Access 'TestGbac' -Group Software -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Smooth McGroove'
}
)
}
}

# POST request to get list of users - only Admin roles can access
Add-PodeRoute -Method Post -Path '/users-admin' -Authentication 'Validate' -Role Admin -ScriptBlock {
# POST request to get list of users - only Developer role in the Admins group can access
Add-PodeRoute -Method Post -Path '/users-dev-admin' -Authentication 'Validate' -Access 'TestMergedAll' -Role Developer -Group Admins -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Arthur Dent'
Age = 30
}
)
}
} -PassThru | Add-PodeAuthCustomAccess -Name 'TestRbac' -Value @{ Example = 'test-val-2' }
}

# POST request to get list of users - either DevOps role or Admins group can access
Add-PodeRoute -Method Post -Path '/users-devop-admin' -Authentication 'Validate' -Access 'TestMergedOne' -Role DevOps -Group Admins -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Monkey D. Luffy'
}
)
}
}

# POST request to get list of users - either QA role or Support group can access
Add-PodeRoute -Method Post -Path '/users-qa-support' -Authentication 'Validate' -Access 'TestMergedOne' -Role QA -Group Support -ScriptBlock {
Write-PodeJsonResponse -Value @{
Users = @(
@{
Name = 'Donald Duck'
}
)
}
}

}
103 changes: 103 additions & 0 deletions examples/web-auth-form-access.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
$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

<#
This examples shows how to use session persistant authentication with access.
The example used here is Form authentication and RBAC access on pages, sent from the <form> in HTML.

Navigating to the 'http://localhost:8085' endpoint in your browser will auto-rediect you to the '/login'
page. Here, you can type the username (morty) and the password (pickle); clicking 'Login' will take you
back to the home page with a greeting and a view counter. Clicking 'Logout' will purge the session and
take you back to the login page.

- The Home and Login pages are accessible by all.
- The About page is only accessible by Developers (for morty it will load)
- The Register page is only accessible by QAs (for morty this will 403)
#>

# create a server, and start listening on port 8085
Start-PodeServer -Threads 2 {

# listen on localhost:8085
Add-PodeEndpoint -Address * -Port 8085 -Protocol Http

# set the view engine
Set-PodeViewEngine -Type Pode

# enable error logging
New-PodeLoggingMethod -Terminal | Enable-PodeErrorLogging

# setup session details
Enable-PodeSessionMiddleware -Duration 120 -Extend

# setup form auth (<form> in HTML)
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
if ($username -eq 'morty' -and $password -eq 'pickle') {
return @{
User = @{
Name = 'Morty'
Roles = @('Developer')
}
}
}

return @{ Message = 'Invalid details supplied' }
}

# set RBAC access
New-PodeAccessScheme -Type Role | Add-PodeAccess -Name 'Rbac' -Match One


# home page:
# redirects to login page if not authenticated
Add-PodeRoute -Method Get -Path '/' -Authentication Login -ScriptBlock {
$session:Views++

Write-PodeViewResponse -Path 'auth-home' -Data @{
Username = $WebEvent.Auth.User.Name
Views = $session:Views
Expiry = Get-PodeSessionExpiry
}
}


# about page:
# only Developers can access this page
Add-PodeRoute -Method Get -Path '/about' -Authentication Login -Access Rbac -Role Developer -ScriptBlock {
Write-PodeViewResponse -Path 'auth-about'
}


# register page:
# only QAs can access this page
Add-PodeRoute -Method Get -Path '/register' -Authentication Login -Access Rbac -Role QA -ScriptBlock {
Write-PodeViewResponse -Path 'auth-register'
}


# login page:
# the login flag set below checks if there is already an authenticated session cookie. If there is, then
# the user is redirected to the home page. If there is no session then the login page will load without
# checking user authetication (to prevent a 401 status)
Add-PodeRoute -Method Get -Path '/login' -Authentication Login -Login -ScriptBlock {
Write-PodeViewResponse -Path 'auth-login' -FlashMessages
}


# login check:
# this is the endpoint the <form>'s action will invoke. If the user validates then they are set against
# the session as authenticated, and redirect to the home page. If they fail, then the login page reloads
Add-PodeRoute -Method Post -Path '/login' -Authentication Login -Login


# logout check:
# when the logout button is click, this endpoint is invoked. The logout flag set below informs this call
# to purge the currently authenticated session, and then redirect back to the login page
Add-PodeRoute -Method Post -Path '/logout' -Authentication Login -Logout
}
Loading