Skip to content

feat: Native PKCE Auth for Tetrate via Custom URL Scheme#6445

Closed
raj-subhankar wants to merge 20 commits intoblock:mainfrom
raj-subhankar:feat/tetrate-native-auth
Closed

feat: Native PKCE Auth for Tetrate via Custom URL Scheme#6445
raj-subhankar wants to merge 20 commits intoblock:mainfrom
raj-subhankar:feat/tetrate-native-auth

Conversation

@raj-subhankar
Copy link
Collaborator

@raj-subhankar raj-subhankar commented Jan 12, 2026

Summary

This PR moves Tetrate authentication from a server-driven flow (Rust PKCE + localhost callback) to a client-driven flow (Electron PKCE + goose:// deep-link callback).
It also adds a /tetrate/verify endpoint so the Electron client can send the authorization code and PKCE verifier for server-side token exchange.

Type of Change

  • Feature
  • Bug fix
  • Refactor / Code quality
  • Performance improvement
  • Documentation
  • Tests
  • Security fix
  • Build / Release
  • Other (specify below)

AI Assistance

  • This PR was created or reviewed with AI assistance

Testing

Related Issues

Relates to #ISSUE_ID
Discussion: LINK (if any)

Screenshots/Demos (for UX changes)

Before:

Goose.Tetrate.login.before.mov

After:

Custom.url.mov

Copilot AI review requested due to automatic review settings January 12, 2026 16:00
@raj-subhankar raj-subhankar marked this pull request as draft January 12, 2026 16:00
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from 24bc358 to f6b9fd9 Compare January 12, 2026 16:05
Copy link
Contributor

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 implements native PKCE (Proof Key for Code Exchange) authentication for Tetrate using macOS's ASWebAuthenticationSession API, with a fallback to browser-based authentication on other platforms. The implementation eliminates the need for a temporary callback server by leveraging the native OS authentication flow.

Changes:

  • Adds a new TypeScript authentication module implementing PKCE flow with state validation and timeout handling
  • Implements a native macOS Node.js addon using Objective-C++ to interface with ASWebAuthenticationSession
  • Updates Rust backend to support PKCE code verification through a new /tetrate/verify endpoint
  • Configures the macOS app bundle to handle goose:// URL scheme for OAuth callbacks

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
ui/desktop/src/tetrateAuth.ts Core authentication logic implementing PKCE flow, callback handling, and native session integration
ui/desktop/src/tetrateAuth.test.ts Unit tests covering PKCE pair creation, URL parsing, and callback handling
ui/desktop/src/native/auth_session/src/auth_session.mm Native macOS module wrapping ASWebAuthenticationSession with N-API bindings
ui/desktop/src/native/auth_session/binding.gyp Build configuration for the native module targeting macOS 12.0+
ui/desktop/scripts/build-auth-session.js Build script to compile the native addon during development and packaging
ui/desktop/src/preload.ts Adds IPC bridge for startTetrateAuth electron API
ui/desktop/src/main.ts Integrates callback URL handling in protocol handler and adds IPC handler for auth initiation
ui/desktop/src/utils/tetrateSetup.ts Simplified to call native auth through electron API instead of backend
ui/desktop/package.json Adds native build scripts and @electron/node-gyp dependency
ui/desktop/forge.config.ts Configures URL scheme handler and includes native modules in build artifacts
crates/goose/src/config/signup_tetrate/mod.rs Adds static method for code exchange accepting external verifier
crates/goose-server/src/routes/setup.rs Adds new verify endpoint accepting code and code_verifier
ui/desktop/openapi.json OpenAPI schema additions for TetrateVerifyRequest endpoint
ui/desktop/src/api/*.gen.ts Auto-generated API client types and functions
Files not reviewed (1)
  • ui/desktop/package-lock.json: Language not supported

@DOsinga DOsinga requested a review from michaelneale January 12, 2026 16:13
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch 3 times, most recently from 53ce7e1 to 9336b80 Compare January 13, 2026 08:53
"defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
"xcode_settings": {
"CLANG_ENABLE_OBJC_ARC": "YES",
"MACOSX_DEPLOYMENT_TARGET": "12.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make sure this is aligned with the Mac OSX versions supported by goose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Goose uses Electron 39, which has a minimum macOS 12 requirement, so this should be fine.

match TetrateAuth::exchange_code_with_verifier(request.code, request.code_verifier).await {
Ok(api_key) => api_key,
Err(e) => {
return Ok(Json(SetupResponse {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we return a different status code here rather than a 200 on failure?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Returning 200 with success: false was an existing behavior and matches the pattern used by the Openrouter setup flow.

Comment on lines 30 to 31
static NSMutableSet<ASWebAuthenticationSession *> *gActiveSessions = nil;
static GooseAuthPresentationContextProvider *gPresentationContextProvider = nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

Will these globals cause issues if multiple auth flows are triggered at the same time? Is it possible to trigger multiple at the same time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ASWebAuthenticationSession only supports a single login session at a time, so I think this should be fine.

@michaelneale
Copy link
Collaborator

oh interesting, looks like some merge conflicts (most are generated code though). I didn't know about this on macos - is there an example of this? seems a fair bit of work, but macos is pretty common so may be ok (OTOH spinning up a little server isn't the end of the world if smooth enough)

@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch 2 times, most recently from 66bd6e9 to 33a51fd Compare January 19, 2026 06:22
@raj-subhankar
Copy link
Collaborator Author

hey @michaelneale 👋

Yeah, spinning up a server for auth is totally fine. The main goal here was to keep the user in the app, instead of opening a browser tab and leaving them there. To make that work, we had to change the server based approach as well.
Claude Desktop comes to mind as a good example of this.

Also I’ve fixed the conflicts and updated the PR description.

@raj-subhankar raj-subhankar marked this pull request as ready for review January 19, 2026 07:36
Copilot AI review requested due to automatic review settings January 19, 2026 07:36
Copy link
Contributor

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

Copilot reviewed 18 out of 19 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • ui/desktop/package-lock.json: Language not supported

@michaelneale
Copy link
Collaborator

yeah this did work when I tried it - is nice. May be overkill as I think previous was was similarly smooth (but with native browser I guess)

@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from 5d502ad to b074a82 Compare January 22, 2026 03:14
@raj-subhankar
Copy link
Collaborator Author

@michaelneale I agree this is a significant change, and for most cases the previous login flow did feel smooth.

That said, I do run into issues on when using Goose alongside local dev projects. Since Goose spins up the login server on localhost:3000, if you already have something running on that port the login just fails silently, with no indication of why.

While we could fix this by not hardcoding the port, the new flow addresses it as well and has the added benefit of feeling more native.

@michaelneale
Copy link
Collaborator

@raj-subhankar is there a way to do it with a dynamic port (or this macos native way) but use the native browser vs a window?

@raj-subhankar
Copy link
Collaborator Author

@michaelneale Yes, we could do this with a custom URL scheme using the external browser.

However, I’m advocating for ASWebAuthenticationSession because it’s the best of both worlds.

To clarify: on macOS this uses the user’s default browser (Chrome, Safari, etc.), not an embedded webview, and it shares the browser’s cookies, so you still get the same SSO experience.

The “window” you see is the OS delegating the auth request to the default browser. The key advantage is lifecycle management: the system lets us automatically dismiss the window as soon as authentication completes.

With a external browser flow, the user is left with a “Success” tab they have to manually close before returning to the app.

Given that this is Apple’s recommended approach for native app authentication, and the standard used by apps like Slack and VS Code, is there a specific UX concern you have with the modal window?

@raj-subhankar raj-subhankar marked this pull request as draft February 10, 2026 09:57
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from f7d15a8 to eecf26c Compare February 14, 2026 06:26
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from eecf26c to 5ba1688 Compare February 14, 2026 06:36
@github-actions
Copy link
Contributor

🔍 Recipe Security Scan Results

Status: APPROVED - All recipes passed security scan

📊 Scan Summary:

  • Total recipes scanned: 4

📋 Individual Recipe Results:
✅ Recipe 1: APPROVED (LOW risk)
✅ Recipe 2: APPROVED (LOW risk)
✅ Recipe 3: APPROVED (LOW risk)
✅ Recipe 4: APPROVED (LOW risk)

🔗 View detailed scan results in the workflow artifacts.

Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from ad83134 to a807094 Compare February 14, 2026 07:04
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar changed the title feat: Native auth flow for Tetrate on macOS feat: Native PKCE Auth for Tetrate via Custom URL Scheme Feb 14, 2026
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from 2210bca to dc3f2f5 Compare February 14, 2026 18:38
Signed-off-by: raj-subhankar <subhankar.rj@gmail.com>
@raj-subhankar raj-subhankar force-pushed the feat/tetrate-native-auth branch from c48841f to b25af59 Compare February 15, 2026 05:17
@michaelneale
Copy link
Collaborator

I think we can close this, with a port fix and possibly #7166 for responding to low balance I think we could be in a good place.

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.

3 participants

Comments