Skip to main content
Function Signature
A PortalAuth method and the single completion path for every redirect-based flow: magic links and the loginWithGoogle / loginWithApple browser path. Forward every URL your app receives to it. signInWithGoogle / signInWithApple call it internally. Parameters
  • url: The URL iOS delivered to your app, as a URL (from onOpenURL, your scene delegate or your app delegate) or a String. The URL overload forwards absoluteString, so percent-encoding stays intact and the grant is decoded exactly once.
Returns
  • AuthResult?:
    • nil: The URL is not for this instance: it does not match redirectUrl, carries no token, carries no recognized auth-method marker, is empty, or is longer than 8192 UTF-16 code units. Let your other URL handlers try.
    • .authenticated(AuthenticatedResult): The sign-in is complete. session is already saved to the Keychain; pass it to Portal(credentials:).
    • .totpRequired(TotpRequiredResult): A two-factor code is required. Nothing is persisted; complete with verifyTotp.
Throws
  • PortalAuthError.authenticationFailed(error:): The URL matches redirectUrl and carries an error parameter, for example oauth_failed. Thrown even if a token is also present, and before any network call. The value is bounded to 100 characters.
  • PortalAuthError.invalidGrantResponse: The exchange returned neither a session token nor a userJwt.
  • PortalAuthError.malformedResponse(path:missing:): The response did not carry the documented fields.
  • PortalAuthError.sessionStorageFailure(message:): The session could not be saved to the Keychain, so the sign-in is not returned. The grant is already spent; calling handleRedirect again with the same URL on the same instance retries the write instead of re-sending the grant.
  • PortalRequestsError.unauthorized: Portal rejected the grant: it expired (15 minutes), was already exchanged, was issued for a different environment, or its sign-in method has since been disabled. Also thrown when a remembered session has since been invalidated. Offer the user a new sign-in.
  • Other transport errors unchanged.
Notes
  • Safe to call twice with the same URL. The instance remembers the last grant the backend accepted and replays the same AuthResult (and the same PortalSession instance) when iOS re-delivers the redirect, instead of failing Portal’s single-use check. A redirect that failed on a dropped connection is not remembered, so it can be retried; if the backend spent the grant before the connection dropped, the retry throws PortalRequestsError.unauthorized and the user needs a new sign-in. The memory lives on the instance and in this process: clearPersistedSession() drops it, and after the app is terminated the recovery is restoreSession().
  • A grant that stopped at .totpRequired replays as the same step. Once verifyTotp accepts the code, the same grant replays as the .authenticated result it resolved to.
  • Matching is case-insensitive on scheme and host and exact on path, after dropping the query, the fragment and trailing slashes. Register fragment-free redirect URLs.
  • Routing: authMethod=EMAIL_MAGIC_LINK goes to the magic-link exchange; login_type=GOOGLE or login_type=APPLE goes to the OAuth exchange.
  • A redirect delivered through the OS can reach another app that registers the same custom scheme. Prefer signInWith* for OAuth, and a Universal Link for magic links; see Register your redirect.
  • Never log the raw URL; it contains the grant.
  • Available starting from SDK version 8.0.0.
Example Usage
Related Documentation