Phosra Link for iOS
PhosraLinkKit is the native-iOS Phosra Link — a Phosra-branded Connect
sheet a parental-controls app (PCA) presents in about ten lines, with
onSuccess / onExit callbacks. It's the iOS peer of the web/React-Native
@phosra/connect drop-in, and it drives the same
@phosra/link ceremony — init → complete → bind → grant — through
your own backend (BFF).
The parent's authentication and your OCSS signing key never leave your app (self-custody by default). The SDK holds no keys; your BFF signs the consent attestation. Same trust model as the rest of OCSS, same ergonomics as Plaid's iOS SDK.
Install
Swift Package Manager, iOS 16+:
.package(url: "https://github.com/Phosra-Inc/phosra-link-kit-ios.git", from: "0.1.0")During monorepo development you can also depend on it by path
(.package(path: "packages/ios-connect")).
Quickstart
import PhosraLinkKit
let config = PhosraLinkConfiguration(
bffBaseURL: URL(string: "https://app.yourpca.com")!, // your backend
sessionToken: myParentSessionToken, // the token YOUR login issued
platform: ConnectPlatform(did: "did:ocss:loopline", name: "Loopline"),
rules: [
ConnectRule(category: "addictive_pattern_block", label: "Turn off the infinite feed"),
ConnectRule(category: "dm_restriction", label: "Limit who can message them"),
],
grantedScope: ["addictive_pattern_block", "dm_restriction"],
redirectUri: "yourapp://phosra-link", // a custom scheme your app registers
childId: "child:uuid" // optional
)
let handler = PhosraLink(configuration: config)
handler.present(
from: self, // any UIViewController
onSuccess: { success in
print("granted:", success.grantId) // the OCSS consent grant
},
onExit: { exit in
// .userCanceled or .error — no rules were changed
}
)That's the whole integration. Exactly one of onSuccess / onExit fires per
presentation — including if the presenter is torn down out from under the sheet —
and you do not need to retain the handler (it self-retains while presented).
The platform's OAuth runs in a system ASWebAuthenticationSession, so your app
never sees the parent's platform credentials. The Connect sheet renders in your
app — there is no Phosra-hosted page or web view.
The examples above use did:ocss:loopline — the DID with a live sandbox connect
config, so the ceremony actually loads against the production sandbox census. Other DIDs
return the "known fixture but unconfigured" 404; email
[email protected] to have one published. See
How platforms are resolved.
End-to-end
The whole integration on one page — client and backend:
- 1Register your redirect scheme
Add your custom scheme (e.g.
yourapp) to the app target'sInfo.plistCFBundleURLTypes, soASWebAuthenticationSessioncan receive the platform's OAuth callback atyourapp://phosra-link. - 2Authenticate the parent (your own login)
The parent signs in with your app's auth — Phosra never sees their credentials. On success, mint a server-side session token (the reference BFF issues a signed
phosra_parentcookie viaissueParentSession). That token is thesessionTokenyou pass the SDK. Never accept it from the client. - 3Stand up the 3 BFF routes
POST /connect/init | /connect/complete | /connect/bind, each wrapping@phosra/linkserver-side — see the reference BFF. Yourbindroute signs the consent with your OCSS key and returns{ grant_id }. - 4Present the sheet
Build
PhosraLinkConfiguration(the Quickstart above) and callPhosraLink(configuration:).present(from:onSuccess:onExit:). - 5Handle the result
onSuccessdelivers thegrantId— persist it and drive rule directives with@phosra/link'sdirective(...).onExitmeans the parent canceled or errored; nothing changed.
Metered census usage requires a Phosra API key — provision one in the developer
console or via POST /api/v1/developers/orgs/{orgID}/keys.
What your backend provides
PhosraLinkKit never talks to Phosra directly. It calls your BFF — the three
routes the reference BFF exposes, each wrapping @phosra/link
server-side:
| Route | Wraps (@phosra/link) |
|---|---|
POST /connect/init | initPlatformOAuth → { authorizeUrl, state, sessionId } |
POST /connect/complete | completePlatformOAuth → { sessionId, childProfiles } |
POST /connect/bind | bindProfile → runConnectCeremony → { grant_id } |
Your bind route signs the consent attestation with your OCSS key and posts it
to the census. The SDK relays your parent-session token (a phosra_parent cookie by
default; override sessionHeaderName / sessionHeaderValue for a bearer). This is
Plaid's link_token pattern — a thin backend mints the session and finalizes the
signature.
Platform logos
The sandbox sheet can show a logo from synthetic provider metadata. This is presentation data,
not proof of accreditation or production identity. GET /api/v1/providers/{did}/connect
returns the test provider's name and icon_url; pass that URL into
ConnectPlatform:
ConnectPlatform(
did: "did:ocss:loopline",
name: "Loopline",
logoURL: URL(string: providerIconURL) // from /providers/{did}/connect → icon_url
)The sheet renders it, falling back to a generic glyph while it loads or if it's
missing. For a bundled asset instead of a URL, pass platformLogo: on the
configuration. In sandbox, the logo comes from synthetic provider metadata. Treat it as a
test fixture, not a verified production identity or accreditation cue.
The honesty contract
The sheet ports the approved design's honesty contract verbatim, and the SDK cannot show a fake green:
- The header reads phosra · OCSS to identify the implementation and draft protocol. Do not display an accreditation or certification claim: no current independent program is claimed.
- In sandbox, success should read "Verified test signature" only after the sample grant and its documented test key are checked. It must not imply production trust.
- On failure: "No rules were changed — you can try again."
Load-bearing invariant: in the documented sandbox context, return verified: true only after
the test signature verifies. Label the result "Verified test signature"; never convert this
signal into an accreditation or production-trust badge.
States
The sheet walks: Connect (intro + sandbox context) → What gets applied (the rules preview) → the synthetic provider's OAuth → Choose account → Verified test signature — or an honest error at any step. All rendered natively (SwiftUI); the marks (the Phosra spark, the wordmark, the OCSS glyph) ship as source and tint by color, so there is no asset bundle to manage.
Advanced
- Custom transport. The 4-argument
PhosraLinkConfiguration.inittakes your ownConnectTransportif you route the three calls differently. - Fresh platform login.
PhosraLinkConfiguration(prefersEphemeralSession: true)forces a new platform sign-in (no shared cookies). - Granular events. Pass
onEventto observe each ceremony transition.
Notes
- iOS 16+. The core (controller, transport, sheet) is UI-framework-agnostic and
unit-tested; the
ASWebAuthenticationSessionpresenter is iOS-only. - MIT-licensed. All signing and verification are your backend's; metered census usage requires a Phosra API key, billed server-side — never in this package.