Summary
Add an OAuth2 Authorization Code flow to the platform, allowing third-party applications to authenticate users without handling passwords or raw Gitea tokens.
Current Auth Architecture
The platform currently supports two authentication methods:
- Basic Auth —
POST /api/auth/signin with base64(login:password) → JWT
- Gitea Token Exchange —
GET /api/auth/token?token={gitea_sha1} → JWT (used by @01-edu/api)
The @01-edu/api package implements the second method: a Gitea access token is exchanged for a Hasura JWT, which is auto-refreshed via GET /api/auth/refresh.
Problem
Both methods have limitations for third-party integrations:
- Basic Auth: apps must collect and transmit user passwords
- Gitea Token: users must manually create tokens in Gitea settings and copy/paste SHA1 strings into apps — no standard flow, no consent screen, no scoped access
- No granular permissions: any valid JWT grants full role access — there is no way to limit an app to read-only profile data
- No per-app revocation: revoking a Gitea token breaks all apps using it
- No standardized discovery: developers must reverse-engineer the auth flow from
@01-edu/api source code
Proposed Solution
Add an OAuth2 Authorization Code flow (RFC 6749) on top of the existing infrastructure. All existing building blocks stay unchanged:
| Existing Component |
How OAuth2 Uses It |
/api/auth/signin |
User authenticates on the consent screen via existing login |
/api/auth/token (Gitea → JWT) |
OAuth2 token endpoint issues JWTs using the same signing key |
/api/auth/refresh |
OAuth2 refresh uses the same mechanism |
| Hasura JWT validation |
Scoped JWTs work unchanged — same signature, same claims |
@01-edu/api |
Continues working as-is for server-to-server integrations |
The Flow
1. App redirects user → /api/oauth/authorize?client_id=X&redirect_uri=Y&response_type=code&scope=profile&state=Z
2. User is already logged in on the platform (or logs in via existing signin)
→ Sees consent screen: "App X wants to access your profile and XP data"
→ Clicks Authorize
3. Platform redirects back with a code → app exchanges it for a scoped JWT via POST /api/oauth/token
4. App uses the scoped JWT with GraphQL (same as today)
The user's password and Gitea tokens never leave the platform.
What This Needs
- 5 new endpoints:
/api/oauth/authorize, /token, /revoke, /userinfo, /.well-known/openid-configuration
- 3 new tables:
oauth_client, oauth_authorization_code, oauth_consent
- Scope-to-role mapping: OAuth2 scopes (
profile, progress:read, xp:read) mapped to Hasura role permissions
Zero Breaking Changes
/api/auth/signin, /api/auth/token, /api/auth/refresh — all unchanged
HASURA_GRAPHQL_JWT_SECRET — unchanged (same signing key)
@01-edu/api package — continues working as-is
- New
/api/oauth/* endpoints are purely additive
Precedent
- The
discordToken table already stores accessToken/refreshToken — OAuth token storage has existed before
@01-edu/api already implements a token exchange pattern (Gitea token → JWT), analogous to an OAuth2 token endpoint
- The
hubspot integration repo uses @01-edu/api for server-to-server auth
Use Cases
- Discord bots: verify student identity and display XP/progress
- Student portfolio apps: personal dashboards pulling real-time data
- Campus tools: admin dashboards, analytics
- Mobile apps: native apps with OAuth2 PKCE flow
Full Technical Specification
See the accompanying PR with complete spec: architecture diagram, database schema, JWT claims, scope mapping, security considerations.
Summary
Add an OAuth2 Authorization Code flow to the platform, allowing third-party applications to authenticate users without handling passwords or raw Gitea tokens.
Current Auth Architecture
The platform currently supports two authentication methods:
POST /api/auth/signinwithbase64(login:password)→ JWTGET /api/auth/token?token={gitea_sha1}→ JWT (used by@01-edu/api)The
@01-edu/apipackage implements the second method: a Gitea access token is exchanged for a Hasura JWT, which is auto-refreshed viaGET /api/auth/refresh.Problem
Both methods have limitations for third-party integrations:
@01-edu/apisource codeProposed Solution
Add an OAuth2 Authorization Code flow (RFC 6749) on top of the existing infrastructure. All existing building blocks stay unchanged:
/api/auth/signin/api/auth/token(Gitea → JWT)/api/auth/refresh@01-edu/apiThe Flow
The user's password and Gitea tokens never leave the platform.
What This Needs
/api/oauth/authorize,/token,/revoke,/userinfo,/.well-known/openid-configurationoauth_client,oauth_authorization_code,oauth_consentprofile,progress:read,xp:read) mapped to Hasura role permissionsZero Breaking Changes
/api/auth/signin,/api/auth/token,/api/auth/refresh— all unchangedHASURA_GRAPHQL_JWT_SECRET— unchanged (same signing key)@01-edu/apipackage — continues working as-is/api/oauth/*endpoints are purely additivePrecedent
discordTokentable already storesaccessToken/refreshToken— OAuth token storage has existed before@01-edu/apialready implements a token exchange pattern (Gitea token → JWT), analogous to an OAuth2 token endpointhubspotintegration repo uses@01-edu/apifor server-to-server authUse Cases
Full Technical Specification
See the accompanying PR with complete spec: architecture diagram, database schema, JWT claims, scope mapping, security considerations.