Skip to content

Commit

Permalink
Add example for custom session strategy using JWTs (#8640)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Cousens <dcousens@users.noreply.github.com>
Co-authored-by: Josh Calder <josh.calder@thinkmill.com.au>
  • Loading branch information
3 people authored Jun 19, 2023
1 parent 19bb460 commit 0dc2865
Show file tree
Hide file tree
Showing 15 changed files with 536 additions and 20 deletions.
Binary file not shown.
102 changes: 102 additions & 0 deletions examples/custom-session-jwt/keystone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import jwt from 'jsonwebtoken';
import { config } from '@keystone-6/core';
import { fixPrismaPath } from '../example-utils';
import { lists, Session } from './schema';
import type { Context, TypeInfo } from '.keystone/types';

// WARNING: this example is for demonstration purposes only
// as with each of our examples, it has not been vetted
// or tested for any particular usage

// WARNING: you need to change this
const jwtSessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --';

type OurJWTClaims = {
id: string;
};

async function jwtSign(claims: OurJWTClaims) {
return new Promise((resolve, reject) => {
jwt.sign(
claims,
jwtSessionSecret,
{
algorithm: 'HS256', // HMAC-SHA256
},
(err, token) => {
if (err) return reject(err);
return resolve(token);
}
);
});
}

async function jwtVerify(token: string): Promise<OurJWTClaims | null> {
return new Promise(resolve => {
jwt.verify(
token,
jwtSessionSecret,
{
algorithms: ['HS256'],
maxAge: '1h', // we use an expiry of 1 hour for this example
},
(err, result) => {
if (err || typeof result !== 'object') return resolve(null);
if (typeof result.id !== 'string') return resolve(null);
return resolve(result as OurJWTClaims);
}
);
});
}

const jwtSessionStrategy = {
async get({ context }: { context: Context }): Promise<Session | undefined> {
if (!context.req) return;

const { cookie = '' } = context.req.headers;
const [cookieName, jwt] = cookie.split('=');
if (cookieName !== 'user') return;

const jwtResult = await jwtVerify(jwt);
if (!jwtResult) return;

const { id } = jwtResult;
const who = await context.sudo().db.User.findOne({ where: { id } });
if (!who) return;
return {
id,
admin: who.admin,
};
},

// we don't need these unless we want to support the functions
// context.sessionStrategy.start
// context.sessionStrategy.end
//
async start() {},
async end() {},
};

export default config<TypeInfo>({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL || 'file:./keystone-example.db',

// WARNING: this is only needed for our monorepo examples, dont do this
...fixPrismaPath,

onConnect: async () => {
// WARNING: remove this
console.error(
'Use any of the following tokens as your `user={token}` cookie for testing this session strategy',
{
Alice: await jwtSign({ id: 'clh9v6pcn0000sbhm9u0j6in0' }), // admin
Bob: await jwtSign({ id: 'clh9v762w0002sbhmhhyc0340' }),
Eve: await jwtSign({ id: 'clh9v7ahs0004sbhmpx30w85n' }),
}
);
},
},
lists,
session: jwtSessionStrategy,
});
23 changes: 23 additions & 0 deletions examples/custom-session-jwt/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@keystone-6/example-custom-session-jwt",
"version": "0.0.1",
"private": true,
"license": "MIT",
"scripts": {
"dev": "keystone dev",
"start": "keystone start",
"build": "keystone build",
"postinstall": "keystone postinstall"
},
"dependencies": {
"@keystone-6/auth": "^7.0.0",
"@keystone-6/core": "^5.0.0",
"@prisma/client": "^4.15.0",
"jsonwebtoken": "^9.0.0"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.2",
"prisma": "^4.15.0",
"typescript": "~5.0.0"
}
}
7 changes: 7 additions & 0 deletions examples/custom-session-jwt/sandbox.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"template": "node",
"container": {
"startScript": "keystone dev",
"node": "16"
}
}
Loading

0 comments on commit 0dc2865

Please sign in to comment.