Skip to content

feat(ios): sign in with apple - #8

Merged
MalcolmTomisin merged 11 commits into
mainfrom
feat/apple-sign-in
Oct 9, 2026
Merged

MalcolmTomisin merged 11 commits into
mainfrom
feat/apple-sign-in

Conversation

@MalcolmTomisin

@MalcolmTomisin MalcolmTomisin commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Sign in with Apple feature on iOS

ScreenRecording_10-09-2026.19-25-32_1.MP4

Introduces the `AppleSignIn` runtime API under `src/apple/`, mirroring the
per-provider layout of `src/google/`.

Decisions taken at this milestone:
- iOS-only. Apple has no native Android SDK, so `NativeAppleSignIn` uses
  `TurboModuleRegistry.get` (nullable) rather than `getEnforcing`: importing the
  spec must never throw on Android. The wrapper translates a missing native
  module into a typed `NOT_SUPPORTED` error.
- The API deliberately diverges from Google's instead of being forced to match.
  Apple has no client-side sign-out and no persistent in-memory user, so there
  is no `signOut`/`revokeAccess`/`getCurrentUser`. Instead the surface is
  `configure`, `signIn`, `getCredentialState(userId)` and `isAvailable()`.
- `AppleUser.email` and `fullName` are typed nullable because Apple returns them
  only on the first authorization; this is called out in the JSDoc so callers
  persist them server-side on first sign-in.
Implements the `AppleSignIn` TurboModule in Objective-C++ against the
codegen-generated `NativeAppleSignInSpec`.

Decisions taken at this milestone:
- Objective-C++ (not Swift), to mirror the existing GoogleSignIn module and
  avoid the Swift/TurboModule interop the library does not set up. The Obj-C
  class name matches the JS module name so RN resolves it via
  `NSClassFromString` (the library uses no `RCT_EXPORT_MODULE`).
- Uses the system `AuthenticationServices` framework, declared in the podspec;
  no third-party pod is added. The minimum deployment target is iOS 15.1, so no
  `@available` guards are required.
- The `ASAuthorizationController`, its delegate (self) and the pending
  resolve/reject blocks are retained as ivars for the duration of the async
  flow, because the controller keeps only a weak delegate reference.
- `ASAuthorizationError` codes are mapped to the library's platform-agnostic
  string codes (cancelled / failed / invalid-response / not-handled).
Adds `AppleSignInButton`, exported from the
`@thoughtbot/react-native-social-auth/apple-button` subpath (parallel to the
existing `google-button` entry point).

Decisions taken at this milestone:
- Uses Apple's native `ASAuthorizationAppleIDButton`, bridged as a Fabric
  component, rather than a pure-JS/SVG button. This guarantees the button always
  matches Apple's Human Interface Guidelines and localizes automatically, and it
  needs no `react-native-svg` dependency.
- `codegenConfig.type` is switched from `modules` to `all`, and an
  `ios.componentProvider` map is added. On RN 0.83 the app-level
  `RCTThirdPartyComponentsProvider` only registers a third-party Fabric view
  that is listed in `componentProvider`; a library with no component annotations
  is never reached by the deprecated source-crawl fallback, so without the map
  the component is silently dropped from the registry.
- The friendly string props (`type`, `buttonStyle`) map to Apple's integer enums
  in JS. The button renders `null` on Android, where Apple Sign-In is
  unsupported.
Adds an `enableAppleSignIn` prop to the config plugin.

Decisions taken at this milestone:
- Apple needs only the `com.apple.developer.applesignin` entitlement — no URL
  scheme, no AppDelegate URL forwarding, and no modular headers (all of which
  Google requires). The plugin therefore adds just that entitlement via
  `withEntitlementsPlist`.
- The plugin is restructured so the Google (`iosClientId`) and Apple
  (`enableAppleSignIn`) configuration apply independently. The previous
  early-return when `iosClientId` was absent would have skipped Apple setup for
  Apple-only or Android-Google + iOS-Apple apps.
- The entitlement merge is extracted as a pure `applyAppleSignInEntitlement`
  helper and unit-tested, matching the plugin's existing "export pure helpers for
  tests" pattern.
Adds the native Apple buttons (three type/style variants) and a sign-in handler
to the example, and sets `enableAppleSignIn: true` on the plugin entry so
`expo prebuild` writes the entitlement.

Decision: the handler persists nothing and simply surfaces the returned
credential in an alert, keeping the demo self-contained; `SIGN_IN_CANCELLED` is
ignored, consistent with the Google example's handling.
Adds a "Sign in with Apple" section to the README and updates the intro,
feature list, platform-support line and Expo plugin props table.

Covers the decisions made during implementation so consumers understand the
shape: iOS-only scope and the Android `NOT_SUPPORTED` behaviour, entitlement
setup (Expo `enableAppleSignIn` prop and the bare-RN Xcode capability), the
`AppleSignIn` API, `AppleSignInButton` props, the error-code table, and the
first-authorization-only caveat for email/name.
Each provider's API is now importable on its own subpath, cleanly separating
Apple from Google at the import level:

    import { GoogleSignIn } from '@thoughtbot/react-native-social-auth/google';
    import { AppleSignIn } from '@thoughtbot/react-native-social-auth/apple';

Decisions taken at this milestone:
- The new `./google` and `./apple` exports point at the existing per-provider
  barrels (`src/google/index.ts`, `src/apple/index.ts`), which already export
  only each provider's API. No new source files.
- The buttons stay on their own `./google-button` and `./apple-button`
  subpaths so `react-native-svg` remains isolated to the Google button rather
  than being pulled into a provider's core import.
- The package root `.` is kept as a convenience aggregate (non-breaking) and is
  simplified to `export * from './google'` / `'./apple'` so it can never drift
  from the barrels.
CI runs `yarn typecheck` before `yarn prepare`, so there is no built `lib/`.
The root tsconfig only aliased the bare package name and `/google-button` to
source, so the `/google`, `/apple` and `/apple-button` subpaths fell back to the
`exports` `types` condition (`./lib/typescript/...`), which does not exist in CI
— producing TS2307 "cannot find module" and cascading TS18046 "error is of type
unknown" (the failed type-guard imports left `error` un-narrowed).

Add the missing subpath aliases to the root tsconfig `paths`, mirroring the
existing `/google-button` entry, so `tsc` resolves every entry point to source
without needing a build.
@MalcolmTomisin
MalcolmTomisin merged commit 37c2dad into main Oct 9, 2026
6 checks passed
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.

1 participant