Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Use PKCE for an Apple Data Connection Flow

PKCE — Proof Key for Code Exchange — ties each authorization code to the specific client request that produced it. Without it, an intercepted code can be exchanged by an attacker, not your app. Add PKCE to every Figment Apple Connect flow: RFC 9700 makes it mandatory for all public OAuth clients, and S256 is the only safe hashing method.

Why PKCE closes the authorization-code injection gap

A public client — a single-page app, a native desktop app, any client that cannot keep a client secret — cannot prove its identity to the token endpoint using a secret. When the authorization server redirects back to your URI with the authorization code, there are scenarios where a malicious app sharing a URI scheme or redirect pattern can intercept that code. With the code in hand, the attacker calls the token endpoint and receives a valid access token for your user's data.

PKCE removes that opportunity. Before the authorization redirect, your client generates a one-time random string called the code verifier. It sends a hashed derivative — the code challenge — with the authorization request, keeping the original verifier private. The token endpoint requires the original verifier at exchange time, derives the hash itself, and compares. Only the client that started the flow holds the verifier.

RFC 9700 states that public clients must use PKCE (RFC 9700). Use S256 exclusively. RFC 7636 is explicit: the plain method offers no protection against an observer who can read the authorization request, and exists only for compatibility with constrained environments that cannot support S256 (RFC 7636). A client that falls back to plain after attempting S256 should treat that as a potential downgrade attack, not a compatibility accommodation.

Figment Apple Connect issues each app a scoped grant. PKCE protects the authorization code for that grant. Revoking your app's grant stops that app's access and leaves other connected apps unaffected.

Generate a code verifier and challenge

RFC 7636 specifies the verifier as 43–128 characters from the unreserved set [A-Za-z0-9\-._~], with at least 256 bits of entropy. Encode 32 cryptographically random bytes as base64url — no padding — and you get exactly 43 characters satisfying that requirement.

# Unexecuted example — test output before production use
import secrets, hashlib, base64

# 32 bytes = 256 bits; base64url without padding = 43 characters
raw = secrets.token_bytes(32)
code_verifier = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()

# S256 challenge per RFC 7636 §4.2: BASE64URL(SHA256(ASCII(code_verifier)))
digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()

Generate a fresh pair for each authorization attempt. Discard the verifier after a successful exchange — retaining it longer than necessary increases exposure without benefit.

Build the authorization request

Redirect the user's browser to https://figment.so/apple-connect/authorize with the following parameters. Register your app and obtain a client_id at /apple-connect/developers.

ParameterValueNote
response_typecodeRequired for authorization-code flow
client_idyour app's client IDAssigned during app registration
redirect_uriexact registered HTTPS URIRFC 9700: exact string match, no wildcards
scopespace-separated listAvailable scopes: messages:read, messages:send, contacts:read, calendar:read, reminders:read; request only what your app needs
staterandom, session-bound tokenBinds the callback to the initiating session
code_challengebase64url(SHA-256(verifier))Computed above
code_challenge_methodS256Never plain; always include this parameter explicitly

Always include code_challenge_method explicitly. RFC 7636 specifies that if the parameter is omitted, the server defaults to plain. Sending S256 explicitly prevents an unintentional downgrade regardless of server defaults.

Redirect URI matching is strict. RFC 9700 requires exact string matching between the URI registered at app setup and the URI sent in both the authorization request and the token exchange. Register the exact URI — including scheme, host, path, and any fixed query string — and do not rely on prefix or wildcard rules. (RFC 9700 provides one narrow exception: variable port numbers for localhost redirect URIs in native apps, following RFC 8252.)

Persist state before you redirect. Store the value in the user's session. Reject any callback where the returned state does not match the stored value. This binds the callback to the session that initiated the flow and rules out cross-site request forgery.

Exchange the code

The authorization server redirects to your registered URI with a code parameter. Post that code to the token endpoint along with the original verifier and the same redirect_uri:

# Token exchange — unexecuted example; replace placeholders before use
POST https://figment.so/api/apple-connect/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
&client_id=YOUR_CLIENT_ID
&code_verifier=CODE_VERIFIER

A successful response includes:

{
  "access_token": "ACCESS_TOKEN",
  "refresh_token": "REFRESH_TOKEN",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "messages:read",
  "connection_id": "con_..."
}

The server derives BASE64URL(SHA256(verifier)), compares it to the challenge stored with the code, and issues a token only on a match. A verifier mismatch returns an error. Attempting a second exchange for the same code returns invalid_grant — authorization codes are single-use.

Treat an unknown outcome as an error. If the token exchange call times out or returns an ambiguous response, do not silently retry. You cannot determine from the client whether the code was consumed. Restart the full authorization flow rather than reusing the code.

Manage access and refresh tokens

Access tokens expire after 900 seconds. Refresh tokens are separate rotating credentials. After every refresh call, store the new refresh token and serialize refresh operations per grant to avoid concurrent invalidation. Refresh using grant_type=refresh_token with your client_id and current refresh_token. Figment tokens are opaque strings — do not parse them as JWTs.

Apple account authorization and connection states

PKCE protects your app's authorization code. The Apple account sign-in itself — trusted-device confirmation, Mac permission prompts, iCloud sync — happens inside Figment's hosted connection setup, separate from your app's OAuth flow. The developer's app never receives or stores the Apple account password.

Apple supports third-party authorization for Mail, Calendar, and Contacts through approved apps. Users can review and revoke individual grants at account.apple.com under Sign-In and Security > Account Data Sharing (Apple support). Figment's app-consent grant is distinct from that Apple-issued grant; Figment is the issuer of the app-access token and connection record.

After a successful grant, Figment tracks connection state per service. A completed OAuth exchange does not mean data is immediately available. Check the connection before making data requests:

GET https://figment.so/api/apple-connect/v1/connections/con_.../
Authorization: Bearer ACCESS_TOKEN

The response uses the form {connection:{id, mode, services, ...}}. Each service in connection.services is an object with state, reason, and observedAt (epoch milliseconds); only granted services appear. Possible states:

StateWhat it means
connectingInitial handshake in progress
needs_approvalA Mac permission or trusted-device step is pending
syncingData indexing in progress
readyService is responding to requests
offlineService is unreachable
reconnect_requiredGrant needs re-establishment
revokedAccess has been revoked

The endpoint enforces authorization and can return a non-2xx response for revoked or non-ready connections rather than a diagnostic state object — handle non-2xx responses in your code. A ready state does not guarantee complete history or successful delivery. Use /apple-connect/account to view and manage setup status.

Authorization-code sequence and state/PKCE test worksheet

Use this worksheet before shipping. Each row maps a flow event to the parameter it produces and the assertion your test should make.

StepActionParameter or checkAssertion
1Generate verifiercode_verifierLength 43–128; characters in [A-Za-z0-9\-._~] only
2Derive S256 challengecode_challengeEquals BASE64URL(SHA256(verifier)); no = padding
3Authorization requestcode_challenge_methodValue is S256; parameter is present; server must reject plain
4Authorization requeststateCryptographically random; stored in session before redirect
5Callback receivedstate checkReject if returned value does not match stored session value
6Token exchangecode_verifierIdentical string to step 1; any altered value returns an error
7Code reusesecond exchange attemptServer returns invalid_grant on the second request
8Redirect URI consistencyredirect_uriByte-for-byte identical in authorization request and token request
9Connection stateGET /v1/connections/{connection_id}connection.services.[service].state is ready before data operations proceed
10RevocationApple Account Data Sharing revokeService state changes; check /apple-connect/account for current connection status

Steps 6 and 7 together confirm that the server treats the verifier as a one-time proof and the code as single-use. If either assertion fails in staging, the authorization server configuration needs review before connecting real user accounts.


Register your app and start the connection flow at Connect Apple Account. For the full consent and scoping model: OAuth for iCloud: Connect Apple Data to Your App. For framework-specific setup: Add Connect Apple Account to a Next.js App.


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms