Skip to content

Sessions

Session Middleware is supported on web requests and responses in the form of signed a cookie/header and server-side data storage. When configured the middleware will check for a session cookie/header (usually called pode.sid) on the request; if a cookie/header is not found on the request, or the session is not in storage, then a new session is created and attached to the response. If there is a session, then the appropriate data for that session is loaded from storage.

The duration of the session cookie/header can be specified, as well as whether to extend the duration each time on each request. A secret key to sign sessions can be supplied (default is a random GUID), as well as the ability to specify custom data stores - the default is in-memory, but custom storage could be anything like Redis/MongoDB/etc.

Note

Using sessions via headers is best used with REST APIs and the CLI. It's not advised to use them for normal websites, as browsers don't send back response headers in new requests - unlike cookies.

Tip

Sessions are typically used in conjunction with Authentication, but you can use them standalone as well!

Usage

To initialise sessions in Pode you'll need to call Enable-PodeSessionMiddleware. This function will configure and automatically create the Middleware needed to enable sessions. By default, sessions are set to use cookies, but support is also available for headers.

Sessions are automatically signed using a random GUID. For Pode running on a single server using the default in-memory storage this is OK, however if you're running Pode on multiple servers, or if you're defining a custom storage then a -Secret is required - this is so that sessions from different servers, or after a server restart, don't become corrupt and unusable.

Cookies

The following is an example of how to set up session middleware using cookies. The duration of each session is defined as a total number of seconds via the -Duration parameter; here we set the duration to 120, so each session created will expire after 2mins, but the expiry time will be extended each time the session is used:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend
}

The default name of the session cookie is pode.sid, but this can be customised using the -Name parameter.

Headers

Sessions are also supported using headers - useful for CLI requests. The following example will enable sessions to use headers instead of cookies, and will also set each session created to have a -Duration of 120 seconds:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend -UseHeaders
}

When using headers, the default name of the session header in the request/response is pode.sid - this can be customised using the -Name parameter. When you make an initial request to authenticate some user, the pode.sid header will be returned in the response. You can then use the value of this header in subsequent requests for the authenticated user, and then make a call using the session one last time against some route to expire the session - or just let it automatically expire.

Scope

Sessions have two different Scopes: Browser and Tab. You can specify the scope using the -Scope parameter on Enable-PodeSessionMiddleware.

Important

Using the Tabs scope requires additional frontend logic, and doesn't work out-of-the-box like the Browser scope. See more below.

The default Scope is Browser, where any authentication and general session data is shared across all tabs in a browser. If you have a site with a view counter and you log in to your site on one tab, and then open another tab and navigate to your site, you'll be automatically logged in and see the same view counter results in both tabs.

The Tabs scope still shares authentication data, so if you log in to your site in one tab and open it again in a different tab, you'll still be logged in. However, general data is separated; taking the view counter example above, both tabs will show different results for the view counter.

Tabs

Unlike the Browser scope which works out-of-the-box when enabled, the Tabs scope requires additional frontend logic.

For session data to be split across different tabs, you need to inform Pode about what the "TabId" is for each tab. This is done by supplying an X-PODE-SESSION-TAB-ID HTTP header in the request. From a browser on a normal page request, there's no way to supply this header, and the normal base session will be supplied instead - hence why authentication data remains shared across tabs. However, if you load the content of your site asynchronously, or via any other means, you can supply this header and it will let you split general session data across tabs.

In websites, the TabId is typically generated via JavaScript, and stored in window.sessionStorage. However, you have to be careful with this approach, as it does make tab sessions susceptible to XSS attacks. The following is a similar approach as used by Pode.Web:

// set TabId
if (window.sessionStorage.TabId) {
    window.TabId = window.sessionStorage.TabId;
    window.sessionStorage.removeItem("TabId");
}
else {
    window.TabId = Math.floor(Math.random() * 1000000);
}

// binding to persist TabId on refresh
window.addEventListener("beforeunload", function(e) {
    window.sessionStorage.TabId = window.TabId;
    return null;
});

The TabId could then be sent as an HTTP header using AJAX. There are other approaches available online as well.

SessionIds

The built-in SessionId generator used for sessions is a GUID, but you can supply a custom generator using the -Generator parameter.

If supplied, the -Generator is a scriptblock that must return a valid string. The string itself should be a random unique value, that can be used as a unique session identifier.

Within a route, or middleware, you can get the currently authenticated session's ID using Get-PodeSessionId. If there is no session, or the session is not authenticated, then $null is returned. This function can also return the fully signed sessionId as well. If you want the sessionId even if it's not authenticated, then you can supply -Force to get the current SessionId back.

Note

If you're using the Tab -Scope, then the SessionId will include the TabId as well, if one was supplied.

Strict

You can flag sessions as being strict using the -Strict switch. Strict sessions will extend the signing process by also using the client's UserAgent and RemoteIPAddress, to help prevent session sharing on different browsers/consoles.

Pode will automatically extend the Secret used for signing for you, whether you're using the default GUID, or supplying a specific -Secret value.

Storage

The inbuilt storage for sessions is a simple in-memory store - with auto-cleanup for expired sessions.

You can define a custom storage by supplying a psobject to the -Storage parameter, and also note that a -Secret will be required. The psobject supplied should have the following NoteProperty scriptblock members:

[hashtable] Get([string] $sessionId)
[void]      Set([string] $sessionId, [hashtable] $data, [datetime] $expiry)
[void]      Delete([string] $sessionId)

For example, the following is a mock up of a Storage for Redis. Note that the functions are fake and also that the returned User property in the hashtable MUST be an object (such as via PSCO cast):

# create the object
$store = [psobject]::new()

# add a Get property for retreiving a session's data by SessionId
$store | Add-Member -MemberType NoteProperty -Name Get -Value {
    param($sessionId)
    $data = Get-RedisKey -Key $sessionId
    $session = $data | ConvertFrom-Json -AsHashtable
    try {
        $session.Data.Auth.User = [PSCustomObject]$session.Data.Auth.User
    }
    catch {}
    return $session
}

# add a Set property to save a session's data
$store | Add-Member -MemberType NoteProperty -Name Set -Value {
    param($sessionId, $data, $expiry)
    Set-RedisKey -Key $sessionId -Value ($data | ConvertTo-Json -Compress) -TimeToLive $expiry
}

# add a Delete property to delete a session's data by SessionId
$store | Add-Member -MemberType NoteProperty -Name Delete -Value {
    param($sessionId)
    Remove-RedisKey -Key $sessionId
}

# enable session middleware - a secret is required
Enable-PodeSessionMiddleware -Duration 120 -Storage $store -Secret 'schwifty'

Session Data

To add data to a session you can use the .Session.Data property within the web event object, which is accessible in a Route or other Middleware. The data will be saved to some storage at the end of the request automatically using Endware. When a request is made using the same SessionId, the data is loaded from the store. For example, incrementing some view counter:

Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
    $WebEvent.Session.Data.Views++
}

You can also use the $session: variable scope, which will get/set data on the current session for the name supplied. You can use $session: anywhere a $WebEvent is available - such as Routes, Middleware, Authentication, and Endware. The same view counter example above would now be as follows:

Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
    $session:Views++
}

A session's data will be automatically saved by Pode at the end of each request, but you can force the data of the current session to be saved by using Save-PodeSession.

Important

$session: can only be used in the main scriptblocks of Routes, etc. If you attempt to use it in a function of a custom module, it will fail; even if you're using the function in a route. Pode remaps $session: on server start, and can only do this to the main scriptblocks supplied to functions such as Add-PodeRoute. In these scenarios, you will have to use $WebEvent.Session.Data.

Note

If using the Tab -Scope, any session data will be stored separately from other tabs. This allows you to have multiple tabs open for the same site/page but all with separate session data. Any authentication data will still be shared.

Expiry

When you enable Sessions using Enable-PodeSessionMiddleware you can define the duration of each session created, in seconds, using the -Duration parameter. When a session is created its expiry is set to DateTime.UtcNow + Duration, and by default, a session will automatically expire when the calculated DateTime is reached:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120
}

You can tell Pode to reset/extend each session's expiry on each request sent, that uses that SessionId, by supplying the -Extend switch. When a session's expiry is reset/extended, the DateTime/Duration calculation is re-calculated:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend
}

Retrieve

You can retrieve the expiry for the current session by using Get-PodeSessionExpiry. If you use this function without -Extend specified originally then this will return the explicit DateTime the current session will expire. However, if you did set up sessions to extend then this function will return the recalculated expiry for the current session on each call:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend

    Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
        # this will return a DateTime that will always be 2mins in the future
        $expiry = Get-PodeSessionExpiry
    }
}

Terminate

To terminate the current session you can call Remove-PodeSession. Calling this will immediately set the session to expire now - as if somebody had clicked "Log Out". The session's data will be removed, the cookie will be discarded, and any authentication information will be dropped.

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend

    Add-PodeRoute -Method Get -Path '/logout' -ScriptBlock {
        # this will terminate the current session
        Remove-PodeSession
    }
}

Reset

For any session created when -Extend wasn't supplied to Enable-PodeSessionMiddleware will always have an explicit DateTime set for expiring. However, you can reset this expiry date using Reset-PodeSessionExpiry, and the current session's expiry will be recalculated from now plus the specified -Duration:

Start-PodeServer {
    Enable-PodeSessionMiddleware -Duration 120 -Extend

    Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
        # this will reset the current session's expiry to be DateTime.Now + 2mins
        Reset-PodeSessionExpiry
    }
}

Example

An example of using sessions in a Route to increment a views counter could be done as follows (the counter will continue to increment on each call to the route until the session expires after 2mins):

Start-PodeServer {
    Add-PodeEndpoint -Address localhost -Port 8080 -Protocol Http
    Enable-PodeSessionMiddleware -Duration 120

    Add-PodeRoute -Method Get -Path '/' -ScriptBlock {
        $session:Views++
        Write-PodeJsonResponse -Value @{ 'Views' = $session:Views }
    }
}