Skip to content
How much to read

Knowledge base

projects / easemysemester / decisions

Google OAuth only for the mobile PWA

v2 shipped hand-rolled Google OAuth with an invite list, email plus role deciding access, a revoked account's sign-in refused before a session is minted, and citations verified across eight books; auth was later replaced by email and password.

decision · superseded · updated 2026-09-17 · EaseMySemester case study

See overview and mobile PWA design.

Context

The mobile PWA design needed a sign-in screen and had to settle how an account gets created.

Decision

Google OAuth only. One button does both sign in and sign up; the account is created on first success. No email and password. A line on the sign-in screen states this directly, to remove any confusion about a missing signup link.

Why

Settled with the architect and PM as one of the design's locked decisions, alongside dropping the paywall, exam countdowns, streaks and offline downloads from the earlier six-screen pass.

Consequences

The scope statement on the sign-in screen commits to requesting only name and email from Google, not broader profile access. A wrong Google account is handled with a "not you?" affordance on the following screen rather than a separate account-switch flow.

Built, then the invite list was dropped

The first build (2026-09-08) was hand-rolled authorization-code OAuth with no auth library and no local ID-token verification, Google establishing identity and an invite list (email plus role) deciding access. A revoked account's next sign-in returned the inactive row and was refused before a session, fixed after review found the callback could mint a session for a revoked account regardless of active.

The next day (2026-09-09), the invite list was dropped: establishUser finds or creates an account for a verified Google sub, so the same callback serves both a first-time sign-up and a returning sign-in, and a student who signs out for a year returns to their own history rather than a second empty account. The invites table stays in the schema and stops being read, rather than being dropped in the same change, since this is a production database with no staging to roll back to. A new account is written as role STUDENT literally in the insert; ADMIN is now granted only by someone with direct database access. active becomes the sole access gate, so revocation had to be re-verified end to end: a revoked student could otherwise re-create their own access by signing in again.

A single guard module reads role and active from the database on every request rather than trusting the session cookie, so revoking a student takes effect on their next request. A revoked account that completes the OAuth round trip is refused before a session is minted; the first build minted one regardless, caught in review and fixed same day. The invite-gated build also carried a route-protection test that enumerates every guarded route from one list, and a citation spot-check across eight books, offsets 11 to 363, all correct, which was the check meant to separate this build from v1's citations that were off by up to 42 pages.

Status

Shipped 2026-09-08 (invite-gated) and 2026-09-09 (invite list dropped, closing issue #38). Superseded: the design bundle synced 2026-09-16 states Google was dropped in favour of email and password sign-in with a demo link on the landing page, because the consent screen was doing nothing for the students. See the replacement decision.

This page was written by a model from my own repositories and checked by a second one against the same sources. I did not write it, and no version of it was approved by me before it went up. How that works, and where it can still be wrong.