Sign-in and users
Next releaseBeta
How midcode reads your site's sign-in from its code, lists the people in your database without ever reading a password, and what it asks your agent to build.
This ships with the next release of midcode. The version you can download today (1.1.2) doesn’t have it yet.
The Sign-in group of Database shows who signs in to your site and how: the people, the ways to sign in the site offers, and which pages ask for a signed-in visitor.
Two things to know first. midcode reads all of this from your project’s code and never guesses it from the database alone. And midcode does not write sign-in code: adding sign-in, another way to sign in, or a check on a page is put in words for your agent, with what midcode already knows about the project.
Where it is
Open Database in the top bar. Under Sign-in in the list on the left there are three places: People, Ways to sign in and Protected pages. The last two show a count once a sign-in library is found.
With no sign-in in the project, People says “No sign-in yet” and offers Add sign-in.
What’s read from your code
The library or service
midcode looks at your package.json (and, for three frameworks, at their files). The first that matches, in this order:
| Sign-in | Recognised by |
|---|---|
| Auth.js | next-auth, @auth/core, @auth/sveltekit, @auth/nextjs or @auth/express |
| Better Auth | better-auth |
| Clerk | Any @clerk/… package |
| Supabase Auth | A Supabase package, and code that calls its auth (.auth.signInWithPassword(, .auth.getUser(, .auth.signOut(…) |
| Firebase Auth | firebase, and an import from firebase/auth |
| Auth0 | @auth0/nextjs-auth0 or @auth0/auth0-react |
| Kinde | Any @kinde-oss/… package |
| WorkOS | @workos-inc/authkit-nextjs or @workos-inc/node |
| Lucia | lucia |
| Laravel | An artisan file |
| Django | A manage.py file |
| Devise | devise in the Gemfile |
Supabase is often only the database, so its package alone doesn’t count: the code has to call its auth.
Ways to sign in
Read from the library’s own setup, for three of them:
| Library | What midcode reads |
|---|---|
| Auth.js | The providers imported from next-auth/providers/… or @auth/…/providers/… |
| Supabase Auth | signInWithOAuth({ provider: '…' }), signInWithPassword / signUp (email and password), signInWithOtp (email link) |
| Better Auth | The keys of socialProviders, emailAndPassword: { enabled: true }, magicLink(), passkey() |
Each way shows the file it’s set up in; click it to open the file. When the users are in your database, each also shows how many people use it, and a way people use that the code doesn’t show (one set up in the service’s dashboard) is listed too.
For the other libraries nothing is read from the code. With a hosted service the list says so: “They’re set up in Clerk, not in your code.”
Protected pages
Every page of the site, with a lock when a check for a signed-in visitor was found. There are two kinds:
“Checked before the page, in
proxy.ts”: Next.js runs one file before pages (proxy.tssince Next 16,middleware.tsbefore; also undersrc/). midcode reads itsmatcherand whether the file asks for a session at all. A page counts when a matcher covers its route, or when the file has no matcher.“Checked in
app/account/page.tsx”: the page’s own file, or in the App Router any layout above it, both asks who is signed in (auth(),getServerSession(),currentUser(),getUser(),getSession(),requireUser()…) and turns the visitor away (redirect(),notFound(),unauthorized(),forbidden(),redirectToSignIn(),auth.protect()).
Anything else says No check found.
People
People lists the rows of the table your sign-in keeps users in.
With Supabase Auth, that’s
auth.users.Otherwise it’s the first of
User,user,users,Users,auth_user,accounts_userorprofiles, in the default schema, that has an email column and a primary key.
Columns are recognised by name: email, name, picture (image, avatar, avatar_url, picture…), whether the email is confirmed, when the person joined, when they were last seen, and a role (role, tier, plan, is_admin…). Only those columns are asked for. A page of people is one query:
select "id", "email", "name", "created_at", "password_hash" is not null
from "public"."users"
order by "created_at" desc nulls last, "id"
limit 50 offset 0midcode never reads a password’s hash or a token. For a password column it asks only whether it’s set (is not null), to show that the person signs in with a password. The other ways each person uses come from the accounts table beside the users (account, accounts or identities, with a user id and a provider).
Each row shows the person’s picture or initial, name and email, a check if the email is confirmed, the role, an icon per way to sign in, and “Joined” and “Seen” dates. Search by name or email; the list is 50 people at a time, newest first.
Click a person to open their row in its table, filtered to that row. There it’s a row like any other: see Edit rows.
Reading people needs no permission to change the database, so it works on a read-only connection.
When the users aren’t in your database
Clerk, Firebase Auth, Auth0, Kinde and WorkOS keep your users for you. People then says “Your users are in” that service, with a button that opens its dashboard. If the database has no table of users, it says “No table of users here” and asks whether it’s the right connection.
What the agent is asked
Three buttons open a dialog that ends in a text for your agent. Each has two steps: you choose, then you read exactly what will be asked, and Copy it or Send to the agent. The files the agent changes from the Chat view are undoable and show in Publish.
| Button | Where | You choose |
|---|---|---|
| Add sign-in | Overview, and People when there’s none | The library, the ways to sign in, and (optional) the pages only signed-in people see |
| Add a way to sign in | Ways to sign in | Which of email and password, Google, GitHub and email link to add (the ones the site doesn’t have) |
| Protect pages | Protected pages; Protect… on a page’s row | The pages, from those with no check found |
For Add sign-in, midcode offers Supabase Auth, Better Auth, Auth.js and Clerk, and marks one “Suggested”: Supabase Auth if the project already uses Supabase, Better Auth if a database is connected, Clerk if there’s none.
Every brief starts from what midcode read, so the agent doesn’t have to find it again: the framework and its version, the router, TypeScript or JavaScript, the package manager, Tailwind, the ORM, the database’s engine, the sign-in already there with its config file, and how the site protects other pages already. A brief for a Next.js project with Prisma:
Add sign-in to this site with Better Auth.
The project: Next.js 16.0.1, app router, TypeScript, pnpm, Tailwind, Prisma for the database. Its database is Postgres.
What to build:
- Ways to sign in: Email and password, Google.
- A sign-in page at /sign-in and a sign-up page at /sign-up, looking like the rest of the site: reuse its components and styles, no new UI library.
- A way to sign out, and who is signed in shown in the site’s header or nav if it has one.
- Require sign-in for these pages, sending signed-out visitors to /sign-in and back where they were going afterwards:
/account (app/account/page.tsx)
How:
- Use Better Auth, keeping users in the project's own database. Its tables (user, session, account, verification) are added through Prisma: with the Prisma adapter, add the models to prisma/schema.prisma and apply them the way this project applies schema changes.
- In Next 16 the file that runs before pages is proxy.ts (middleware.ts before 16): use the one this project's version (16.0.1) expects.
- Keys and secrets go in .env.local as placeholders, and their names in .env.example. Never make up a real key, and never commit one.
- When you’re done, list what’s left to do by hand: each key to create, where to create it, and the variable it goes in.The keys the agent leaves as placeholders show up afterwards in Environment variables as missing, ready to be given a value.
A “protect” brief also names the file that runs before pages and its matchers, and points at a page that’s already protected: “Do it the way the site already does”.
Limits
Sign-in and users is in beta and not in the released version yet.
midcode writes no sign-in code, and has no actions on users: no invite, no password reset, no block. A person’s row can be edited as a row, nothing more.
Ways to sign in are read for Auth.js, Supabase Auth and Better Auth only.
The file that runs before pages is read for Next.js. Checks made elsewhere (SvelteKit hooks, Nuxt or Laravel route middleware, a Django decorator, a wrapper component) aren’t recognised: those pages say “No check found”.
A matcher is read when it’s written as strings. With one built in code, or a file that decides by its own
ifs instead of a matcher, midcode takes the file to run on every page, so every page shows as checked.Users kept by a hosted service (Clerk, Firebase Auth, Auth0, Kinde, WorkOS) aren’t listed; midcode links to the service.
The users table is found by its name. A table called something else isn’t found: open it from Tables.
A picture is shown only when it’s a full
http(s)address.What was checked: the reading of code against
package.jsonfiles and source files of each kind, and the People list against users seeded in a test Postgres. Not tried against live projects of each service.