Quickstart
iOS (Swift)
Overview · React Native and Expo · Android · iOS · Register an app · API reference · Errors and security · Changelog
Coming soon @seletech/seleid-client is not on npm yet. These pages describe version 0.1.0 so you can plan an integration. See the roadmap.
There is no Swift SDK yet (Planned). The published TypeScript SDK does not run natively. Until a native SDK exists, iOS apps use the protocol directly. The snippets below show the protocol steps; they are not SeleID APIs and are untested against production, where sign-in for outside apps is not open.
The token endpoint answers with the standard snake_case fields (access_token, token_type, expires_in, id_token, refresh_token, scope), so a standard OpenID library such as AppAuth can parse it. Older camelCase aliases are still present for our own apps and are deprecated; do not rely on them. Sign-in for outside apps is Planned: it stays off until SeleTech switches it on for registered apps.
Endpoints
| Purpose | URL |
|---|---|
| Discovery | https://auth.seleid.dev/.well-known/openid-configuration |
| Authorize (browser) | https://auth.seleid.dev/authorize |
| Token (POST, form-encoded) | https://auth.seleid.dev/oauth/token (confidential apps: client_secret_basic or client_secret_post; public apps: PKCE only) |
| Userinfo (GET, Bearer access token; Planned, off by default) | https://auth.seleid.dev/oauth/userinfo |
| Revocation (POST, RFC 7009; Planned, off by default) | https://auth.seleid.dev/oauth/revoke |
| Session check (GET, Bearer access token; Planned, off by default) | https://auth.seleid.dev/v1/session/context |
| Signing keys | https://auth.seleid.dev/.well-known/jwks.json |
Required authorize parameters: client_id, exact redirect_uri, response_type=code, scope containing openid, state, nonce (16 to 256 characters), code_challenge (S256, 43 to 128 characters). Token request: grant_type=authorization_code, client_id, code, redirect_uri, code_verifier; refresh uses grant_type=refresh_token, client_id, refresh_token. Refresh tokens rotate: persist the new one before using it, never replay a refresh request.
PKCE and the authorize request
Pkce.swift
import CryptoKit
import Foundation
func randomURLSafe(bytes: Int = 32) -> String {
var data = Data(count: bytes)
_ = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, bytes, $0.baseAddress!) }
return base64URL(data)
}
func base64URL(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
func challenge(for verifier: String) -> String {
base64URL(Data(SHA256.hash(data: Data(verifier.utf8))))
}
SignIn.swift
import AuthenticationServices
var components = URLComponents(string: "https://auth.seleid.dev/authorize")!
components.queryItems = [
.init(name: "client_id", value: clientId),
.init(name: "redirect_uri", value: "com.example.app:/seleid"),
.init(name: "response_type", value: "code"),
.init(name: "scope", value: "openid profile email"),
.init(name: "state", value: state),
.init(name: "nonce", value: nonce),
.init(name: "code_challenge", value: challenge(for: verifier)),
.init(name: "code_challenge_method", value: "S256"),
]
let session = ASWebAuthenticationSession(url: components.url!, callbackURLScheme: "com.example.app") { callbackURL, error in
guard let callbackURL, error == nil,
let items = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?.queryItems,
items.first(where: { $0.name == "state" })?.value == state,
let code = items.first(where: { $0.name == "code" })?.value else { return }
// POST code + verifier to https://auth.seleid.dev/oauth/token (application/x-www-form-urlencoded)
}
session.presentationContextProvider = contextProvider
session.start()
Token storage
Store the refresh token in the Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (or stricter). Never use UserDefaults or log tokens.
Session checks
The server-side session check (GET /v1/session/context) is Planned. Until it exists, treat a refusal on refresh (invalid_grant) as signed out and a timeout or 5xx as “unknown” (retry, do not sign out). A Swift SDK mirroring the TypeScript one is Planned.