Skip to content

feat: change client secret to optional for public client#14

Merged
sirily11 merged 2 commits intomainfrom
client-secret
Jan 29, 2026
Merged

feat: change client secret to optional for public client#14
sirily11 merged 2 commits intomainfrom
client-secret

Conversation

@sirily11
Copy link
Contributor

No description provided.

- Updated `createOAuthClient` function to handle client type (public/confidential).
- Introduced new test suite for public client flow in `public-client.spec.ts`.
- Added database migration to include `client_type` in `oauth_clients` table.
- Modified validation schemas to accommodate public clients without client secrets.
- Updated token request handling to allow authorization code and refresh token flows without client secrets for public clients.
- Adjusted basic auth parsing to allow undefined client secrets for public clients.
Copilot AI review requested due to automatic review settings January 29, 2026 02:46
@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
rxlab-auth Ready Ready Preview, Comment Jan 29, 2026 2:46am

Request Review

@autopilot-project-manager autopilot-project-manager bot added the enhancement New feature or request label Jan 29, 2026
@sirily11 sirily11 merged commit 6199ad7 into main Jan 29, 2026
9 checks passed
@sirily11 sirily11 deleted the client-secret branch January 29, 2026 02:53
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enables OAuth “public clients” by making client_secret optional in validation and persistence, adding a clientType attribute to clients, and enforcing the correct behavior at the token/revoke endpoints.

Changes:

  • Make client_secret optional for authorization_code/refresh_token (and revoke) validation to support public clients.
  • Add clientType (public/confidential) to OAuth client creation/UI and persist it in the DB (with secret nullable for public clients).
  • Add E2E coverage for public-client flows and switch in-memory E2E DB setup to run Drizzle migrations.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/validations/oauth.ts Loosens request validation to allow missing client_secret for public-client flows.
lib/validations/oauth.test.ts Updates schema tests to reflect optional client_secret and adds coverage for public-client cases.
lib/validations/admin.ts Adds clientType to OAuth client creation validation.
lib/oauth/jwt.ts Adds jti to access tokens.
lib/oauth/basic-auth.ts Adjusts Basic auth parsing to allow empty secrets (now normalized).
lib/db/schema/oauth-clients.ts Adds clientType column and makes secret nullable for public clients.
lib/db/migrations/meta/_journal.json Registers the new migration.
lib/db/migrations/meta/0003_snapshot.json Captures updated schema snapshot including client_type and nullable secret.
lib/db/migrations/0003_chilly_husk.sql Attempts to migrate schema for public clients (currently problematic on SQLite).
lib/db/index.ts Runs migrations automatically for in-memory E2E DB instead of creating tables manually.
e2e/oauth/public-client.spec.ts Adds full E2E public-client authorization/refresh/revoke coverage.
e2e/fixtures/test-helpers.ts Extends helper to create public vs confidential clients and handle missing secrets.
components/admin/client-form.tsx Adds client type selection and hides secret display for public clients.
app/api/oauth/token/route.ts Enforces public-client restrictions and conditionally requires client_secret.
app/api/oauth/revoke/route.ts Allows public clients to revoke without a secret; confidential clients still require it.
actions/admin/clients/create.ts Creates public clients without generating/storing a secret; persists clientType.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 24 to 26
client_id: z.string().min(1, "client_id is required"),
client_secret: z.string().min(1, "client_secret is required"),
client_secret: z.string().optional(),
}),
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

client_secret: z.string().optional() will accept an empty string (""), which is typically not desired for OAuth client authentication fields. If the intent is to allow omission for public clients while still rejecting empty secrets when provided, use a non-empty constraint (e.g., z.string().min(1).optional()), and consider adding a test for the empty-string case.

Copilot uses AI. Check for mistakes.
Comment on lines 30 to 33
refresh_token: z.string().min(1, "refresh_token is required"),
client_id: z.string().min(1, "client_id is required"),
client_secret: z.string().min(1, "client_secret is required"),
client_secret: z.string().optional(),
scope: z.string().optional(),
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

Same issue as above: client_secret: z.string().optional() allows an empty string. If empty secrets should not be treated as valid input, change this to require non-empty when present (e.g., min(1).optional()).

Copilot uses AI. Check for mistakes.
token_type_hint: z.enum(["access_token", "refresh_token"]).optional(),
client_id: z.string().min(1, "client_id is required"),
client_secret: z.string().min(1, "client_secret is required"),
client_secret: z.string().optional(),
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

client_secret: z.string().optional() in the revoke schema allows an empty string to pass validation. If you want to allow omission for public clients but reject empty secrets when sent, prefer z.string().min(1).optional() (and keep route behavior of always returning 200 per RFC 7009).

Suggested change
client_secret: z.string().optional(),
client_secret: z.string().min(1).optional(),

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +22
DROP INDEX "email_whitelist_email_idx";--> statement-breakpoint
ALTER TABLE `oauth_clients` ALTER COLUMN "secret" TO "secret" text;--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);--> statement-breakpoint
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

This migration uses ALTER TABLE ... ALTER COLUMN, which is not supported by SQLite/libSQL. As written, applying migrations to SQLite (including the in-memory E2E DB) will fail at this statement. To make oauth_clients.secret nullable you’ll need a table-rebuild migration pattern (create new table with nullable secret + new client_type column, copy data, drop old table, rename), or regenerate the migration using a strategy compatible with SQLite.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant