We're excited to introduce support for server-side rendering (SSR) authentication patterns. This change enhances the developer experience when building with popular frameworks that provide SSR as an option, such as Next.js, SvelteKit, Nuxt, Astro, Remix, and more.
Until now, Appwrite’s authentication system has been optimized for client-side rendering, which worked well with single page applications (SPAs), but had limitations on how SSR could be implemented. While it was possible to implement SSR authentication, it was hacky and undocumented. In the latest release, we are officially introducing support for SSR with new methods and workflows to the server SDK's.
Understanding past limitations
To understand the new changes and workflows, let's first take a step back and address the challenges we aimed to solve in this new release.
When building applications with SSR, we need a way to generate and store a session secret server-side to protect API routes and pages and a way to make authenticated requests.
The problem we faced was that there was no way to access a session secret when using authentication methods. When using methods such as createEmailPasswordSession, Appwrite’s web SDK automatically stores the session in the browser's cookies and does not make it available to you. For client-side rendering, this is a non-issue since we don't need to access the session manually. However, when it comes to SSR we need a way to access a session and set it in the server's cookies for subsequent requests.
Getting started with SSR
As you get started with SSR it’s important to note that all methods we will be working with are based on Appwrite’s server SDKs. In the examples below, we will use the Node JS SDK.
It’s also recommended that as you follow along with this article, you do not install any client SDK’s as that may lead to confusion. All examples use node-appwrite.
npm install node-appwrite
Create sessions server-side
To solve this issue, all existing server SDK methods that create a session now return a secret attribute. The following methods are:
account.createEmailPasswordSession(email, password)
account.createAnonymousSession()
account.createSession(userId, token)
You can use these methods now to create a session, and to set a session cookie on your domain.
const session = account.createEmailPasswordSession(email, password)
console.log(session.secret) // Output: 'eyJpZCI...sdfahfkjjy'
Using session secrets
With a session cookie set, we can now authenticate users and protect routes.
When using the Appwrite SDK, you can use the new setSession method to authenticate a user for any request.
client.setSession(session.secret)
const currentUser = await account.get()
Appwrite client security
It’s important to note that the client instance should be re-created for every request. Failure to do so on your server SDK could result in leaked/shared session cookies between request
Admin and Session Clients
With the new authentication patterns for SSR, you’ll need to create two different types of instances of an Appwrite client when initializing the SDK. An admin client for performing admin request, and a session client for performing authenticated request on behalf of an end user.
Admin Client
The admin client will need to be initialized with an API key in order to bypass rate limits when performing unauthenticated request or when we need to perform actions that bypass permissions. Without an API key our server will be rate limited when trying to make request to certain endpoints.
import { Client } from "node-appwrite"
const adminClient = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
.setKey('<YOUR_API_KEY>')
Session Client
A session client will allow us to make requests as an authenticated end-user with the setSession helper method.
const sessionClient = new Client()
.setEndpoint('https://cloud.appwrite.io/v1')
.setProject('<PROJECT_ID>')
const session = req.cookies.session
if (session) {
sessionClient.setSession(session)
}
const user = await account.get()
Seeing it in action
Using Next.js, let’s see how we can create an application with two endpoints:
/api/signin - creates a session and sets a session cookie
/api/user - retrieves session from cookies and authenticates the request
We'll use an admin client to create sessions and a session client to perform authenticated requests.
Creating admin and session client
First, we’ll need to configure our SDK and create two methods to initiate our clients in /src/appwrite.js. We'll use environment variables to store our Appwrite endpoint, project ID, and API key.
import { Client, Account } from "node-appwrite"
import { parseCookie } from "next/dist/compiled/@edge-runtime/cookies";
export function createAdminClient() => {
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT)
.setKey(process.env.APPWRITE_API_KEY)
return {
get account() {
return new Account(client)
}
}
}
export function createSessionClient(headers) {
const client = new Client()
.setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(process.env.PUBLIC_APPWRITE_PROJECT)
const cookies = parseCookie(headers.get('cookie') ?? '')
const session = cookies.get('my-session-cookie') ?? ''
if (session) {
client.setSession(session.secret)
}
return {
get account() {
return new Account(client)
}
}
}
Creating a session
Now, in the /signin route, we can use the admin client to generate a session and set a cookie.
import { createAdminClient } from "@/lib/appwrite"
import { cookies } from 'next/headers'
export async function POST(request){
const { account } = await createAdminClient()
const { email, password } = await request.json()
const session = await account.createEmailPasswordSession(email, password)
cookies().set('session', session.secret, {
httpOnly: true,
secure: true,
sameSite: 'strict',
expires: new Date(session.expire),
path: '/',
})
return Response.redirect('/api/user')
}
Authenticating requests
With a session set, we can now use the session client to authenticate a user and return the user object. This session client can now be used on all requests that require an authenticated user.
import { createSessionClient } from "@/lib/appwrite"
import { headers } from 'next/headers'
export async function GET(request){
const { account } = await createSessionClient(headers)
try {
const user = await account.get()
return Response.json(user)
} catch (error) {
return Response.json(error)
}
}
Resources
Visit our documentation to learn more about SSR, join us on Discord to be part of the discussion, visit our blog and YouTube channel to learn more, or visit our GitHub repository to see our source code.
SSR will be available as part of the Appwrite 1.5 release on GitHub and Cloud in March 2024.