Skip to content

feat(api): Implement authentication - #1385

Open
Christopher MANEU (cmaneu) wants to merge 11 commits into
mainfrom
feat/api-authentication
Open

Christopher MANEU (cmaneu) wants to merge 11 commits into
mainfrom
feat/api-authentication

Conversation

@cmaneu

Copy link
Copy Markdown
Member

Here is it, adding the authentication to the API (Authorization is out of scope of this PR).

Attention points

No token / bad token discrepancy

There is a discrepancy in the answers:

  • If there is no Authorization: Bearer, you have an "error" JSON
  • If there is a bad Bearer token, you get an HTTP 401 error.

This was not intentional. Moreover, it seems we're not aligning on something like RFC 9457.

@cmaneu ➜ /workspaces/scope (feat/api-authentication) $ /scop -i -H "Authorization: Bearer invalid" http://localhost:3100/api/v1/users/metp://localhost:3100/api/v1/users/me
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 59
ETag: W/"3b-cyhLN/7LJeR4qyngihbvuX6D114"
Date: Mon, 31 Aug 2026 14:53:44 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"error":"Invalid or expired token","code":"invalid_token"}@cmaneu ➜ /workspaces/scope (fea@cmaneu ➜ /workspaces/scope (feat/api-authentication) $ er inhttp://localhost:3100/api/v1/users/me
{"error":"Authentication required"}

@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #36

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
2652 2652 0 0 0 0 0 3m7s

🎉 All tests passed!

Github Test Reporter

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #39

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
2652 2652 0 0 0 0 0 3m13s

🎉 All tests passed!

Github Test Reporter

@cmaneu

Copy link
Copy Markdown
Member Author

Successfully tested locally

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Token validation, Cosmos uniqueness, JWKS failure handling, and local Portal integration contain blocking correctness and reliability issues.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds identity-only Microsoft Entra authentication to the API.

Changes:

  • Adds JWT verification, authentication middleware, JIT user provisioning, and /users/me.
  • Adds the users collection migration and authentication configuration.
  • Adds tests, OpenAPI updates, documentation, and the jose dependency.
File summaries
File Description
packages/shared/src/index.ts Exports authentication APIs.
packages/shared/src/auth/user-schema.ts Defines persisted users.
packages/shared/src/auth/user-schema.test.ts Tests user validation.
packages/shared/src/auth/types.ts Defines authentication interfaces and errors.
packages/shared/src/auth/index.ts Exports auth modules.
packages/shared/src/auth/entra.ts Implements Entra token verification.
packages/shared/src/auth/entra.test.ts Tests token verification.
packages/shared/src/auth/config.ts Loads authentication configuration.
packages/shared/src/auth/config.test.ts Tests configuration loading.
packages/shared/src/auth/claims-enricher.ts Maps claims to profiles.
packages/shared/src/auth/claims-enricher.test.ts Tests claim enrichment.
packages/shared/package.json Adds jose.
packages/db-migrations/src/required-migrations.ts Registers migration 029.
packages/db-migrations/src/migrations/029-create-users-collection.ts Creates user indexes.
packages/db-migrations/src/check-migrations.test.ts Updates migration expectations.
NOTICE Adds the jose license.
ENV_VARIABLES.md Documents API authentication settings.
docs/architecture/auth-rbac.md Updates client configuration names.
apps/api/src/test-helpers.ts Adds mock users storage.
apps/api/src/routes/users.ts Adds the current-user endpoint.
apps/api/src/routes/users.test.ts Tests the current-user endpoint.
apps/api/src/route-context.ts Adds authentication dependencies.
apps/api/src/index.ts Initializes and registers authentication.
apps/api/src/auth/user-store.ts Implements JIT user persistence.
apps/api/src/auth/user-store.test.ts Tests user persistence.
apps/api/src/auth/types.ts Defines request principals.
apps/api/src/auth/middleware.ts Authenticates bearer tokens globally.
apps/api/src/auth/middleware.test.ts Tests authentication middleware.
apps/api/src/__snapshots__/openapi-snapshot.test.ts.snap Records the new endpoint.
.env.local.example Documents local emulator configuration.
.env.example Adds general authentication examples.
Review details
  • Files reviewed: 31/33 changed files
  • Comments generated: 11
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/db-migrations/src/migrations/029-create-users-collection.ts Outdated
Comment thread packages/shared/src/auth/entra.ts Outdated
Comment thread .env.example Outdated
Comment thread .env.local.example
Comment thread apps/api/src/auth/middleware.ts Outdated
Comment thread packages/shared/src/auth/entra.ts Outdated
Comment thread packages/shared/src/auth/entra.ts
Comment thread .env.example Outdated
Comment thread ENV_VARIABLES.md Outdated
Comment thread apps/api/src/routes/users.ts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of the updated authentication implementation. Five findings and two non-blocking notes are attached inline.

Comment thread packages/shared/src/auth/entra.ts Outdated
Comment thread docker-compose.yml Outdated
Comment thread docker-compose.yml Outdated
Comment thread apps/api/src/auth/user-store.ts Outdated
Comment thread apps/api/src/routes/users.ts
Comment thread apps/api/src/routes/users.ts
Comment thread docs/architecture/auth-rbac.md

@cedricvidal Cedric Vidal (cedricvidal) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this! I’m aligned with Wassim Chegham (@manekinekko)’s comments on the TypeScript mismatch and TLS configuration. One additional deployment detail for migration 029:

Our Cosmos accounts already use continuous backup—the shared dev infrastructure configures Continuous7Days, and the operations guide records Continuous30Days for integration and production.

With that configuration, Cosmos requires unique indexes to be supplied when creating the collection through its CreateCollection extension command. The current implicit collection creation through createIndex() therefore needs a Cosmos-compatible path alongside the native MongoDB implementation.

Earlier migrations work around related restrictions by falling back to non-unique indexes and application-level duplicate handling. Here, keeping the database-enforced identity constraint would help ensure concurrent logins always resolve to the same user.

The existing shared dev infrastructure guide provides a convenient way to exercise this on real CosmosDB, with an isolated database per worktree. That would let us confirm both that migration 029 completes and that the resulting index rejects duplicate identity triples.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the issues are need to be fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focused follow-up on per-request user persistence: reliability, profile freshness, and login timestamp semantics.

Comment thread apps/api/src/auth/user-store.ts Outdated
Comment thread apps/api/src/auth/user-store.ts
Comment thread apps/api/src/auth/user-store.ts
@duffney

Copy link
Copy Markdown
Collaborator

The environment-specific auth configuration is being split into repository-owned work:

  • growth-ecosystems/scope-core-infra#136 reads the app registration IDs from GitHub Environment variables and publishes them through the Flux infra-outputs ConfigMap.
  • growth-ecosystems/scope-core#1415 consumes those Flux values in the API and Portal Kubernetes deployment manifests.

For integration, scope-core-infra now stores AUTH_API_CLIENT_ID and AUTH_PORTAL_CLIENT_ID as GitHub Environment variables and reuses the existing AZURE_TENANT_ID. The infra deployment will derive the authority and API scope, then Flux will inject the resulting values into the application manifests.

The API in this PR already reads runtime process environment variables. The remaining Portal work in this PR is to read its client ID, authority, and scopes from runtime /config.js rather than environment-specific VITE_AUTH_* build values. This keeps the Portal image build-once/promote-many across integration and production.

- Reject empty and malformed Authorization headers while preserving
  the existing anonymous rollout.
- Correct authentication deployment and local development documentation.
- Replace local TLS verification bypasses with trusted development CA
  certificates and restore API queue declarations needed for dev builds.
- Pass Portal authentication settings through Docker build arguments,
  Compose, and CI while preserving Vite development configuration.
- Bootstrap admins using verified identity and tenant allowlists without
  requiring email verification; preserve the email-storage policy.
- Declare HTTP bearer security only for GET /api/v1/users/me and update
  the generated OpenAPI specification and snapshot.
- Add regression coverage for the corrected behavior.

Refs: #1385

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #67

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
2958 2958 0 0 0 0 0 3m18s

🎉 All tests passed!

Github Test Reporter

@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #68

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
2998 2998 0 0 0 0 0 3m2s

🎉 All tests passed!

Github Test Reporter

@cedricvidal

Copy link
Copy Markdown
Contributor

Christopher MANEU (@cmaneu), I agree with Josh's suggestion. Could you have the Portal read its client ID, authority, and scopes from runtime /config.js, populated from the container's environment, rather than relying on the VITE_AUTH_* build values?

That would let us build the image once and promote it from integration to production, with each deployment supplying its own auth settings. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rework. The overall direction looks sound: MSAL with PKCE, token verification before looking up application access, and a Redis cache with fixed expiration and MongoDB fallback. I don't think the Redis approach needs a redesign.

A few design points I'd like us to address or explicitly agree on:

  1. Use POST for explicit enrollment. Could we keep GET /users/me read-only and move enrollment to a POST endpoint? The login=true path intentionally creates/updates users and can apply bootstrap-admin promotion. Cache-Control: no-store is helpful, but it doesn't change the safe-method semantics of GET. POST would avoid relying on every client to remember not to prefetch or automatically replay this request.

  2. Complete the Entra signing-key issuer check. The token issuer is already checked, but the generic JWKS resolver doesn't enforce the selected key's issuer restriction. Could we add that check for tenant-independent metadata, following Microsoft's validation guidance? This is separate from validating the token's iss claim; I'm not claiming a demonstrated bypass.

  3. Confirm the access-revocation window. The five-minute cache lifetime and possible stale role/disabled status are clearly documented. Could we confirm that this delay is acceptable for the intended deployment? Future role/disable mutation endpoints should invalidate the matching cache entry, as the design already calls out.

I understand that full RBAC and ownership enforcement are outside this PR. Keeping that distinction explicit is important: the Portal login gate is not yet a deny-by-default API authorization boundary.

@github-actions

Copy link
Copy Markdown

Test Results (Node.js 22)

test: Run #69

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️ Pending ⏳ Other ❓ Flaky 🍂 Duration ⏱️
2994 2994 0 0 0 0 0 1m6s

🎉 All tests passed!

Github Test Reporter

@cmaneu

Copy link
Copy Markdown
Member Author

Christopher MANEU (Christopher MANEU (@cmaneu)), I agree with Josh's suggestion. Could you have the Portal read its client ID, authority, and scopes from runtime /config.js, populated from the container's environment, rather than relying on the VITE_AUTH_* build values?

That would let us build the image once and promote it from integration to production, with each deployment supplying its own auth settings. Thanks!

Cedric Vidal (@cedricvidal) I do agree with the objective (build image once an promote it everywhere), and it's quite essential with #1407. However, I'm in favor of having this change as a separate PR to have time to discuss with Wassim Chegham (@manekinekko) about all the other values, and validate how we release scope publicly. I have created #1408 to track this.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants