Lesson 3 · Authentication — who are you?
Shamir — the token authority
Where a Manabie token is born: a login is verified, a user is looked up, and a fresh JWT is minted with their roles and tenant baked in.
Your win: explain the login → exchange → mint flow, what makes Shamir the single source of token truth (it hosts the JWKS everyone verifies against), and why "token exchange" is the right pattern here.
The problem Shamir solves
Users log in through Firebase / Google Identity Platform (or Auth0, or
Salesforce). That gives them a token from Google — but a Google token doesn't know this
platform's roles, permissions, or which resource_path the user belongs to. So the
system exchanges the external token for its own Manabie JWT,
minted by one central service: Shamir.
Step 1 — Login: password → Firebase idToken
A password login is proxied to Google's Identity Toolkit, which returns a Firebase idToken:
LoginInAuthPlatform(...) // POST identitytoolkit.googleapis.com/v1/accounts:signInWithPassword
// → a Firebase idToken (signed by Google, RS256)
At this point the user is authenticated to Google, but not yet carrying any Manabie
identity. (There's also an OAuth 2.0 PKCE flow via GetOAuthUrl for
browser logins, domain_auth.go:212-227.)
Step 2 — Exchange: external token → Manabie JWT
The client calls ExchangeToken, which reaches Shamir. Here's the core of what Shamir
does — verify, look up, mint:
claims := verify(externalToken) // ① is this a real Firebase/Auth0 token?
user := UserRepoV2.GetByAuthInfo(claims.Subject) // ② who is this in OUR database?
if user.DeactivatedAt != nil { return ErrDeactivatedUser } // ③ reject deactivated users
tokenInfo := TokenInfo{ // ④ gather THIS platform's identity
DefaultRole: user.role,
AllowedRoles: []string{user.group},
SchoolIds: user.schoolIDs,
UserGroup: user.group,
ResourcePath: user.resourcePath, // ← the tenant, from the DB
}
newToken := Verifier.ExchangeVerifiedToken(tokenInfo) // ⑤ mint + sign a Manabie JWT
Notice step ④: the roles and resource_path come from the database,
keyed by the verified user — not from anything the client sent. That's what makes the minted token
trustworthy: Shamir is the only party that decides what identity a token asserts.
Step 3 — Mint & sign
generateNewToken(...) // Issuer "manabie"; embeds Manabie + Hasura + Auth0 + Salesforce claims
signNewToken(...) // RS256, signed with the PRIMARY RSA private key
The result is the JWT you met in Lesson 2: sub = user id, iss =
"manabie", and the manabie.* + x-hasura-* claim blocks. This
is the token every subsequent request carries.
The other half: Shamir hosts the JWKS
Minting is only useful if others can verify. Shamir publishes the matching public keys as a JWKS:
GET /.well-known/jwks.json // the public keys for Manabie tokens
GET /.well-known/openid-configuration // OIDC discovery
organization_auths table), so a token from any org's Firebase project can be
exchanged.
Token exchange (RFC 8693) & verifying federated tokens
The STS pattern Shamir implements, and how a backend verifies a Firebase/Identity-Platform token before trusting it.
→ RFC 8693 — OAuth 2.0 Token Exchange
→ Firebase — verify ID tokens ·
Identity Platform — custom claims
Check yourself (from memory)
Q1. In the exchange, where do the roles and resource_path in the new token come from?
Q2. How do thirty services verify Manabie tokens without calling Shamir each time?
/.well-known/jwks.json and check signatures locally.
Q3. What best describes what Shamir does with a Firebase login token?
signInWithPassword; or OAuth PKCE via GetOAuthUrl). Exchange
(ExchangeToken → Shamir exchangeVerifiedToken, token_reader.go:139):
① verify the external token → ② look up the user in the DB (GetByAuthInfo) → ③ reject
if deactivated → ④ read roles + resource_path from the DB → ⑤ mint. Mint
(verifier.go:166-210): generateNewToken (iss "manabie",
embeds manabie.*+x-hasura-*) → signNewToken (RS256, primary
RSA key). Shamir's two jobs: (1) mint/exchange tokens; (2) host the JWKS
at /.well-known/jwks.json so every service verifies locally with the public key. This
is OAuth token exchange (STS, RFC 8693) — NOT Shamir secret-sharing.1. In-repo: internal/shamir/transports/grpc/token_reader.go:139, internal/shamir/services/verifier.go:166-210, internal/shamir/transports/rest/jwks.go:13,29.