Skip to content

Feature: OAuth2 Authorization Server for Third-Party Integrations #3159

@RomainRicord

Description

@RomainRicord

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:

  1. Basic AuthPOST /api/auth/signin with base64(login:password) → JWT
  2. Gitea Token ExchangeGET /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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions