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+:

swift
.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

swift
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:

  1. 1
    Register your redirect scheme

    Add your custom scheme (e.g. yourapp) to the app target's Info.plist CFBundleURLTypes, so ASWebAuthenticationSession can receive the platform's OAuth callback at yourapp://phosra-link.

  2. 2
    Authenticate 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_parent cookie via issueParentSession). That token is the sessionToken you pass the SDK. Never accept it from the client.

  3. 3
    Stand up the 3 BFF routes

    POST /connect/init | /connect/complete | /connect/bind, each wrapping @phosra/link server-side — see the reference BFF. Your bind route signs the consent with your OCSS key and returns { grant_id }.

  4. 4
    Present the sheet

    Build PhosraLinkConfiguration (the Quickstart above) and call PhosraLink(configuration:).present(from:onSuccess:onExit:).

  5. 5
    Handle the result

    onSuccess delivers the grantId — persist it and drive rule directives with @phosra/link's directive(...). onExit means 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:

RouteWraps (@phosra/link)
POST /connect/initinitPlatformOAuth{ authorizeUrl, state, sessionId }
POST /connect/completecompletePlatformOAuth{ sessionId, childProfiles }
POST /connect/bindbindProfilerunConnectCeremony{ 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:

swift
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 accountVerified 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.init takes your own ConnectTransport if you route the three calls differently.
  • Fresh platform login. PhosraLinkConfiguration(prefersEphemeralSession: true) forces a new platform sign-in (no shared cookies).
  • Granular events. Pass onEvent to observe each ceremony transition.

Notes

  • iOS 16+. The core (controller, transport, sheet) is UI-framework-agnostic and unit-tested; the ASWebAuthenticationSession presenter 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.