Project Sesame is an open-source demo web application built with Node.js, designed to provide a hands-on environment for web developers to explore, experiment and learn a wide range of identity and authentication features and patterns.
npm cinpm run buildThis command will run the emulator, RP and IdP projects, and Caddy proxy:
npm run dev:localCaddy should proxy from https://rp.localhost to localhost:8080 and https://idp.localhost to localhost:8000,
or other ports that you specify in the rp-localhost.config.json and idp-localhost.config.json config files.
[!NOTE] >
sudois required to run the Caddy scripts. You may need to enter your password during the command.
For local testing, you can configure Chrome to ignore warnings and errors related to certificates.
- Launch Chrome with the
--ignore-certificate-errorscommand line flag - Enable
chrome://flags/#unsafely-treat-insecure-origin-as-secureand set its contents to:
https://localhost,wss://localhost:3000,https://rp.localhost,wss://rp.localhost,wss://rp.localhost:3000,https://idp.localhost
Before submitting any code changes or raising a pull request, you should run the bundled quality checks task. This runs formatting checks, ESLint analysis, Vitest tests with coverage, and production builds:
npm run checkUnit and integration tests are powered by Vitest. The test suite automatically manages the Firestore emulator lifecycle so you do not need to boot it up manually:
- Run tests once:
npm run test:run - Run tests in watch mode for development:
npm run test - Run tests with coverage summary:
npm run test:coverage
We use ESLint with modern Flat Configuration to perform static and security analysis:
- Check for lint errors:
npm run lint - Auto-fix simple issues:
npm run lint:fix
We use Prettier to maintain consistent code layout and structure:
- Check formatting status:
npm run format:check - Auto-format code:
npm run format
You can use this code base to try and experiment with new ideas. To add a new sign-in flow, follow the instructions below.
- Determine the path. e.g.
/sign-in - Add a new HTML template under
src/shared/views. e.g.src/shared/views/sign-in.html - Add a TypeScript file under
src/client/pages. e.g.src/client/pages/sign-in.ts. - Layout template is
src/client/layout.html. The partial templates are undersrc/shared/views/partials. - Add a server behavior at
src/server/app.ts. e.g.app.get('/sign-in', pageAclCheck(PageType.SignIn), (req: Request, res: Response)) => { res.render('sign-in.html', { title: 'Password', layout: 'password', }); });
Unless this is a public page, use the pageAclCheck middleware to specify ACL.
export enum PageType {
NoAuth = 0, // No authentication is required
SignUp = 1, // This is a sign-up page
SigningUp = 2, // The user must be signing up
SignIn = 3, // This is a sign-in page
FirstCredential = 4, // The user has provided the username
Reauth = 5, // The user must be signed in and requires reauthentication
SignedIn = 6, // The user must be signed in
Sensitive = 7, // The user must be recently signed in
}The pageAclCheck middleware automatically redirects the user if they need to be signed in to access this page etc.
Unless this is a public API, use the apiAclCheck middleware to specify ACL.
export enum ApiType {
NoAuth = 0, // No authentication is required
PasskeyRegistration = 1, // The user is either signing-up, signed-in or upgrading
SigningUp = 2, // The user is in the middle of signing up
SignIn = 3, // The user is about to sign in with a username and a credential
FirstCredential = 4, // The user is about to sign in
SecondCredential = 5, // The user is about to sign in
SignedIn = 6, // The user must be signed in
Sensitive = 7, // The user must be recently signed in
}The apiAclCheck middleware automatically blocks requests with insuffcient privilege.
Start a session after the user successfully signed in.
Create or use a middleware under src/server/middlewares.
For example, if the you want to check a password at /auth/sign-in, create the
endpoint in src/server/middlewares/auth.ts.
router.post(
'/sign-in',
apiAclCheck(ApiType.Authentication),
async (req: Request, res: Response) => {
const {username, password} = req.body;
// TODO: Validate entered parameter.
if (!Users.isValidUsername(username) || !Users.isValidPassword(password)) {
return res.status(401).json({error: 'Enter at least one random letter.'});
}
const user = await Users.validatePassword(username, password);
if (user) {
// Set the user as a signed in status
new SessionService(req.session).setSessionUser(user);
return res.json(user);
} else {
return res.status(401).json({error: 'Failed to sign in.'});
}
}
);