stack/sdks/implementations/swift/Examples/StackAuthMacOS
BilalG1 c0fefd3b7a
feat(backend): dual-accept hexclave-mobile-oauth-url:// alongside legacy scheme (#1501)
## What

1. **Backend dual-accept**: `isAcceptedNativeAppUrl()` accepts both
`stack-auth-mobile-oauth-url://` (legacy) and
`hexclave-mobile-oauth-url://` (canonical).
2. **Swift SDK switches to the canonical scheme**: `StackAuth` Swift SDK
now emits and intercepts `hexclave-mobile-oauth-url://` for native-app
OAuth callbacks.

Before this PR, `hexclave-mobile-oauth-url` existed only inside
`RENAME-TO-HEXCLAVE.md` — not in any code.

## Why the Swift SDK change is safe

The Swift SDK uses
`ASWebAuthenticationSession(url:callbackURLScheme:completion:)`
([StackClientApp.swift:197-199](sdks/implementations/swift/Sources/StackAuth/StackClientApp.swift#L197)).
With this API, iOS intercepts the callback scheme **ephemerally** — no
`Info.plist` registration is required. The Swift SDK source has no
`Info.plist`, and the example apps' `pbxproj` registers no
`CFBundleURLSchemes`. So:

- New customer builds against the updated SDK → emit new scheme →
backend accepts → `ASWebAuthenticationSession` intercepts on new scheme
→ works.
- Already-shipped customer App Store binaries on older SDK versions →
emit old scheme → backend still accepts → works.
- **No customer ever has to update an `Info.plist`.**

The only real backward-compat constraint is that the backend can never
drop the old scheme (already-shipped customer binaries have the constant
baked into them). Hence the dual-accept.

(Note: `RENAME-TO-HEXCLAVE.md` line 88 incorrectly attributes the
constraint to `Info.plist` registration. That's not how the SDK works —
the scheme is baked into the SDK binary, not the customer's plist. The
fix described in that doc is essentially the right shape; only the
mechanism description is wrong.)

## Changes

| File | Change |
|---|---|
| `packages/stack-shared/src/utils/redirect-urls.tsx` |
`isAcceptedNativeAppUrl()` accepts either protocol. |
| `apps/backend/src/lib/redirect-urls.test.tsx` | Adds positive
assertions for the new scheme in `isAcceptedNativeAppUrl`; parity
negative assertions in `validateRedirectUrl`. |
| `sdks/implementations/swift/Sources/StackAuth/StackClientApp.swift` |
`callbackScheme` → `"hexclave-mobile-oauth-url"`; fatalError example
strings updated. |
| `sdks/implementations/swift/Tests/StackAuthTests/OAuthTests.swift` |
Test fixture URLs updated (no assertions depend on the scheme literal).
|
|
`sdks/implementations/swift/Examples/StackAuthiOS/.../StackAuthiOSApp.swift`
| Default values in the example UI. |
|
`sdks/implementations/swift/Examples/StackAuthMacOS/.../StackAuthMacOSApp.swift`
| Default values in the example UI. |
| `sdks/implementations/swift/README.md` | Documents the new canonical
scheme; compat note for the legacy one. |
| `sdks/spec/src/apps/client-app.spec.md` | New scheme is canonical;
legacy is "accepted indefinitely for already-shipped customer app
binaries built against older SDK versions." |

## Verification

- `pnpm test run apps/backend/src/lib/redirect-urls.test.tsx` — 34/34
passing (was 33; one new `it` block plus parity assertions).
- `pnpm --filter @stackframe/stack-shared --filter @stackframe/backend
run lint` — clean.
- `pnpm --filter @stackframe/stack-shared --filter @stackframe/backend
run typecheck` — clean.
- Swift assertions in `OAuthTests.swift` do not check the scheme literal
— they only check `oauth/authorize/<provider>`, state/verifier
non-emptiness, and that `redirectUrl` round-trips. The fixture-value
change is mechanical.

## Risk

Low. Backend behavior strictly widens (every URL accepted before is
still accepted). Swift SDK change is internal to OAuth callback
handling, requires no customer migration, and is paired with the backend
dual-accept landing in the same PR.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Adopted the canonical OAuth callback scheme
"hexclave-mobile-oauth-url://" for native apps while continuing to
accept the legacy "stack-auth-mobile-oauth-url://".

* **Documentation**
* Updated SDK docs, examples, and spec guidance to reference the
canonical callback scheme and clarify legacy acceptance.

* **Tests & Samples**
* Updated tests and example apps to use and validate the canonical
scheme.

* **Style**
* Rebranded the dev-tool trigger icon to the new Hexclave monochrome
logo.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1501?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-27 15:44:06 -07:00
..
StackAuthMacOS feat(backend): dual-accept hexclave-mobile-oauth-url:// alongside legacy scheme (#1501) 2026-05-27 15:44:06 -07:00
Package.resolved [Fix] [Feat] Update OAuth Sign-In and Get Token Functions to Work (#1130) 2026-01-28 02:17:27 +00:00
Package.swift [Fix] [Feat] Update OAuth Sign-In and Get Token Functions to Work (#1130) 2026-01-28 02:17:27 +00:00
README.md feat(hexclave): PR 2 — visible rebrand (Hexclave brand goes public) (#1481) 2026-05-26 19:18:20 -07:00

Hexclave macOS Example

A comprehensive macOS SwiftUI application for testing all Hexclave SDK functions interactively.

Prerequisites

  • macOS 14.0+
  • Swift 5.9+
  • A running Hexclave backend (default: http://localhost:8102)

Running the Example

  1. Start the Hexclave backend:

    cd /path/to/stack-2
    pnpm run dev
    
  2. Open and run the example:

    cd Examples/StackAuthMacOS
    swift run
    

    Or open in Xcode:

    open Package.swift
    

Features

The example app provides a sidebar navigation with the following sections:

Configuration

  • Settings: Configure API base URL, project ID, and API keys
  • Logs: View real-time logs of all SDK operations

Client App Testing

  • Authentication

    • Sign up with email/password
    • Sign in with credentials
    • Sign in with wrong password (error testing)
    • Sign out
    • Get current user
    • Get user (or throw)
  • User Management

    • Set display name
    • Update client metadata
    • Update password
    • Get access/refresh tokens
    • Get auth headers
    • Get partial user from token
  • Teams

    • Create team
    • List user's teams
    • Get team by ID
    • List team members
  • Contact Channels

    • List contact channels
  • OAuth

    • Generate OAuth URLs for Google, GitHub, Microsoft
    • Test PKCE code generation
  • Tokens

    • Get access token (JWT format)
    • Get refresh token
    • Get auth headers
    • Test different token stores

Server App Testing

  • Server Users

    • Create user (basic and with all options)
    • List users with pagination
    • Get user by ID
    • Delete user
  • Server Teams

    • Create team
    • List all teams
    • Add/remove users from teams
    • List team users
    • Delete team
  • Sessions

    • Create session (impersonation)
    • Use session tokens with client app

Default Configuration

The example is pre-configured for local development:

  • Base URL: http://localhost:8102
  • Project ID: internal
  • Publishable Key: this-publishable-client-key-is-for-local-development-only
  • Secret Key: this-secret-server-key-is-for-local-development-only

SDK Functions Covered

Category Functions
Auth signUpWithCredential, signInWithCredential, signOut, getUser, getOAuthUrl
User setDisplayName, update (metadata), updatePassword, getAccessToken, getRefreshToken, getAuthHeaders, getPartialUser
Teams createTeam, listTeams, getTeam, listUsers (team members)
Contact listContactChannels
Server Users createUser, listUsers, getUser, delete, update (metadata, password)
Server Teams createTeam, listTeams, getTeam, addUser, removeUser, listUsers, delete
Sessions createSession
Errors EmailPasswordMismatchError, UserNotSignedInError, PasswordConfirmationMismatchError