Building the foundation Claudius runs on
This tutorial was written by Néstor Daza . This is the third article in a series about building Claudius , my own Claude-based chatbot ( Github ). The previous article discussed the MongoDB data model to use for the app. The previous article decided the shape of the data. None of it matters until the app around it is working, and getting it there is the unglamorous half of this phase. It comes down to three things: an identity system the client cannot tamper with, proof that Claudius can reach the two services it depends on, and the deployment realities that decide whether any of it runs at all. This is the boring work that quietly decides whether a project survives contact with production. Identity: the client never gets a vote Any Google account on Earth can sign into Claudius safely because a user's role is never something the client sends. It is decided on the server every time. One piece of this lives outside the code. The Google provider needs an OAuth (Open Authorization) client that you register once in the Google Cloud Console, and the client identifier and secret from that registration are set in corresponding env variables. These setup steps live in the Auth.js and Google documentation, so I am not repeating them here. Sign-in runs on Auth.js v5 with the Google provider and the MongoDB adapter. There are three roles, admin, member, and guest, and they resolve in exactly one place on the server, with a clear precedence: export async function resolveRole ( email : string | null | undefined ): Promise < Role > { if ( ! email ) return " guest " ; const normalized = email . toLowerCase (); if ( normalized === env . ADMIN_EMAIL . toLowerCase ()) return " admin " ; const settings = await settingsCol (); const allowlist = await settings . findOne ({ _id : " allowlist " }); if ( allowlist && " emails " in allowlist ) { const allowed = allowlist . emails . some (( e ) => e . toLowerCase () === normalized ); if ( allowed ) return " member " ; } return " guest " ; }