Authentication
In this lesson, we’ll implement a basic sign-in mechanism using cookie session.
Let’s add a shared/AuthController.ts file and include the following code:
import { BackendMethod, remult } from 'remult'import type express from 'express'import type from 'cookie-session'
declare module 'remult' { export interface RemultContext { request?: express.Request }}
export class AuthController { //}Code Explanation
- We import the necessary modules from
remultand types forexpressandcookie-session. - We extend the
RemultContextinterface to include an optionalrequestproperty of typeexpress.Request. - Remult will automatically set the
requestwith the current request. Since Remult works with any server framework, we need to type it to the correct server, which in this case is Express. This typing gives us access to the request object and its session, managed bycookie-session. - This
requestcan be accessed usingremult.context.request.
Next, we’ll add a static list of users and a sign-in method. (In a real application, you would use a database, but for this tutorial, a static list will suffice.)
const validUsers = [{ name: 'Jane' }, { name: 'Alex' }]
export class AuthController { @BackendMethod({ allowed: true }) static async signIn(name: string) { const user = validUsers.find((user) => user.name === name) if (user) { remult.user = { id: user.name, name: user.name, } remult.context.request!.session!['user'] = remult.user return remult.user } else { throw Error("Invalid user, try 'Alex' or 'Jane'") } }}Code Explanation
- We define a static list of valid users.
- The
signInmethod is decorated with@BackendMethod({ allowed: true }), making it accessible from the frontend. - The method checks if the provided
nameexists in thevalidUserslist. If it does, it setsremult.userto an object that conforms to theUserInfotype from Remult and stores this user in the request session. - If the user is not found, it throws an error.
Next, we’ll add the sign-out method:
export class AuthController { @BackendMethod({ allowed: true }) static async signIn(name: string) { //... }
@BackendMethod({ allowed: true }) static async signOut() { remult.context.request!.session!['user'] = undefined return undefined }}Code Explanation
- The
signOutmethod clears the user session, making the user unauthenticated.
Next, we’ll adjust the backend/index.ts file:
import express from 'express'import session from 'cookie-session'import { AuthController } from '../shared/AuthController'
//...
export const app = express()
app.enable('trust proxy') // required for stackblitz and other reverse proxy scenariosapp.use( session({ signed: false, // only for dev on stackblitz, use secret in production // secret: process.env['SESSION_SECRET'] || 'my secret', }),)
export const api = remultApi({ entities: [Task], controllers: [TasksController, AuthController], getUser: (request) => request.session?.['user'], //...})Code Explanation
- The
signOutmethod clears the user session, making the user unauthenticated. - We import
sessionfromcookie-sessionandAuthController. - We enable
trust proxyfor reverse proxy scenarios like StackBlitz. - We’ve set
signed: falsein the session configuration due to an issue in StackBlitz that causes problems with signed cookies. This is for development purposes only and in production, you should removesigned: falseand encrypt the cookie using a secret by setting thesecretoption (e.g.,secret: process.env['SESSION_SECRET'] || 'my secret'). - We register
AuthControllerin thecontrollersarray. - We add
getUser: (request) => request.session?.['user']to extract the user from the session.
Frontend Authentication
In frontend/Auth.tsx, we’ll call the AuthController to sign in, sign out, etc.
async function signIn(f: FormEvent<HTMLFormElement>) { f.preventDefault() try { setCurrentUser((remult.user = await AuthController.signIn(name))) } catch (error) { alert((error as ErrorInfo).message) }}
async function signOut() { setCurrentUser(await AuthController.signOut())}
useEffect(() => { remult.initUser().then(setCurrentUser)}, [])Code Explanation
- The
signInfunction callsAuthController.signInand sets the current user if successful. - The
signOutfunction callsAuthController.signOutto clear the current user. - The
useEffecthook uses theinitUsermethod to fetch the current user when the component mounts.
Try It Out
Try signing in as Alex or Jane and verify that you can perform CRUD operations on tasks. Sign out and ensure that you can no longer access the tasks.
Files
Preparing Environment
- Installing dependencies
- Starting http server