Remove roadmap and security docs, update guides and tests

Seto Elkahfi committed May 17, 2026 at 18:04 UTC 14951316fad45fdc3abfef7dd2ea204dcae3fde7
13 files changed +197 -592
.swiftpm/xcode/xcuserdata/setoelkahfi.xcuserdatad/xcschemes/xcschememanagement.plist deleted
-14
@@ -1,14 +0,0 @@
1 -<?xml version="1.0" encoding="UTF-8"?>
2 -<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 -<plist version="1.0">
4 -<dict>
5 - <key>SchemeUserState</key>
6 - <dict>
7 - <key>SmbCloudAuth.xcscheme_^#shared#^_</key>
8 - <dict>
9 - <key>orderHint</key>
10 - <integer>5</integer>
11 - </dict>
12 - </dict>
13 -</dict>
14 -</plist>
Docs/Migration-From-Proxy-Native-Forms.md
-1
@@ -109,4 +109,3 @@ This SDK does not try to replace secure server-side confidential flows for those
109
110 - `Docs/QuickStart-iOS.md`
111 - `Docs/QuickStart-macOS.md`
112 -- `SECURITY.md`
Docs/QuickStart-iOS.md
+31 -20
@@ -1,10 +1,6 @@
1 # Quick Start — iOS
2
3 -This guide shows the hosted smbCloud Auth flow for an iPhone or iPad app using:
4 -
5 -- `SmbCloudWebAuth`
6 -- `SmbCloudCredentialsManager`
7 -- `SmbCloudUserInfoClient`
3 +This guide shows the hosted smbCloud Auth flow for an iPhone or iPad app using `SmbCloudWebAuth` and `SmbCloudCredentialsManager`.
4
5 ## 1. Add the package
6
@@ -12,7 +8,7 @@ Add `https://github.com/smbcloudXYZ/smbcloud-auth-swift` in Xcode or Swift Packa
8
9 ## 2. Register a callback URL scheme
10
15 -Your app needs a custom callback URL such as:
11 +Your app needs a callback URL such as:
12
13 - `myapp://auth/callback`
14
@@ -34,15 +30,24 @@ final class AuthenticationStore: ObservableObject {
30 @Published private(set) var errorMessage: String?
31 @Published private(set) var isLoading = false
32
37 - private let webAuth = try! SmbCloudWebAuth(
38 - domain: "api.smbcloud.xyz",
39 - clientId: "YOUR_PUBLIC_CLIENT_ID",
40 - redirectURL: URL(string: "myapp://auth/callback")!
41 - )
33 + private let webAuth: SmbCloudWebAuth?
34 private let credentialsManager = SmbCloudCredentialsManager(
35 service: "com.example.myapp.smbcloud-auth"
36 )
37
38 + init() {
39 + do {
40 + self.webAuth = try SmbCloudWebAuth(
41 + domain: "api.smbcloud.xyz",
42 + clientId: "YOUR_PUBLIC_CLIENT_ID",
43 + redirectURL: URL(string: "myapp://auth/callback")!
44 + )
45 + } catch {
46 + self.webAuth = nil
47 + self.errorMessage = error.localizedDescription
48 + }
49 + }
50 +
51 func restoreSession() {
52 do {
53 session = try credentialsManager.currentValidSession()
@@ -52,6 +57,11 @@ final class AuthenticationStore: ObservableObject {
57 }
58
59 func signIn(anchor: @escaping () -> SmbCloudPresentationAnchor) async {
60 + guard let webAuth else {
61 + errorMessage = errorMessage ?? "The smbCloud web auth client could not be created."
62 + return
63 + }
64 +
65 isLoading = true
66 errorMessage = nil
67 defer { isLoading = false }
@@ -72,7 +82,7 @@ final class AuthenticationStore: ObservableObject {
82
83 func signOut() {
84 do {
75 - try webAuth.clearSession(credentialsManager: credentialsManager)
85 + try webAuth?.clearSession(credentialsManager: credentialsManager)
86 session = nil
87 email = nil
88 errorMessage = nil
@@ -108,14 +118,15 @@ struct ContentView: View {
118
119 var body: some View {
120 VStack(spacing: 16) {
111 - if let session = authStore.session {
121 + if let email = authStore.email {
122 + Text("Signed in as \(email)")
123 + } else if authStore.session != nil {
124 Text("Signed in")
113 - Text(session.accessToken)
114 - .font(.footnote.monospaced())
115 - .lineLimit(2)
116 - if let email = authStore.email {
117 - Text(email)
118 - }
125 + } else {
126 + Text("Signed out")
127 + }
128 +
129 + if authStore.session != nil {
130 Button("Sign Out") {
131 authStore.signOut()
132 }
@@ -154,6 +165,7 @@ struct ContentView: View {
165 ## Notes
166
167 - Prefer an explicit Keychain `service` value per app.
168 +- Do not show or log raw access tokens in production UI.
169 - `logout()` currently clears local stored credentials. It does not revoke tokens remotely.
170 - Hosted web login requires `ASWebAuthenticationSession`, so use iOS 16+.
171 - If your product needs native email/password forms, send those credentials to your backend or BFF instead of embedding confidential credentials in the app.
@@ -162,4 +174,3 @@ struct ContentView: View {
174
175 - `Docs/QuickStart-macOS.md`
176 - `Docs/Migration-From-Proxy-Native-Forms.md`
165 -- `SECURITY.md`
Docs/QuickStart-macOS.md
+25 -9
@@ -1,6 +1,6 @@
1 # Quick Start — macOS
2
3 -This guide shows hosted smbCloud Auth in a macOS app using `SmbCloudWebAuth`.
3 +This guide shows hosted smbCloud Auth in a macOS app using `SmbCloudWebAuth` and `SmbCloudCredentialsManager`.
4
5 ## 1. Add the package
6
@@ -8,7 +8,7 @@ Add `https://github.com/smbcloudXYZ/smbcloud-auth-swift` to your macOS app.
8
9 ## 2. Configure a callback URL
10
11 -Use a custom callback URL such as:
11 +Use a callback URL such as:
12
13 - `mymacapp://auth/callback`
14
@@ -27,15 +27,24 @@ final class AuthenticationStore: ObservableObject {
27 @Published private(set) var email: String?
28 @Published private(set) var errorMessage: String?
29
30 - private let webAuth = try! SmbCloudWebAuth(
31 - domain: "api.smbcloud.xyz",
32 - clientId: "YOUR_PUBLIC_CLIENT_ID",
33 - redirectURL: URL(string: "mymacapp://auth/callback")!
34 - )
30 + private let webAuth: SmbCloudWebAuth?
31 private let credentialsManager = SmbCloudCredentialsManager(
32 service: "com.example.mymacapp.smbcloud-auth"
33 )
34
35 + init() {
36 + do {
37 + self.webAuth = try SmbCloudWebAuth(
38 + domain: "api.smbcloud.xyz",
39 + clientId: "YOUR_PUBLIC_CLIENT_ID",
40 + redirectURL: URL(string: "mymacapp://auth/callback")!
41 + )
42 + } catch {
43 + self.webAuth = nil
44 + self.errorMessage = error.localizedDescription
45 + }
46 + }
47 +
48 func restoreSession() {
49 do {
50 session = try credentialsManager.currentValidSession()
@@ -45,6 +54,11 @@ final class AuthenticationStore: ObservableObject {
54 }
55
56 func signIn() async {
57 + guard let webAuth else {
58 + errorMessage = errorMessage ?? "The smbCloud web auth client could not be created."
59 + return
60 + }
61 +
62 do {
63 let session = try await webAuth.login(
64 presentationAnchorProvider: {
@@ -53,6 +67,7 @@ final class AuthenticationStore: ObservableObject {
67 credentialsManager: credentialsManager
68 )
69 self.session = session
70 +
71 let user = try await webAuth.userInfo(session: session)
72 email = user.email
73 } catch {
@@ -62,7 +77,7 @@ final class AuthenticationStore: ObservableObject {
77
78 func signOut() {
79 do {
65 - try webAuth.clearSession(credentialsManager: credentialsManager)
80 + try webAuth?.clearSession(credentialsManager: credentialsManager)
81 session = nil
82 email = nil
83 } catch {
@@ -121,7 +136,9 @@ struct ContentView: View {
136 ## Notes
137
138 - Supply a real public client ID from smbCloud Auth.
139 +- Use an explicit Keychain `service` value for your app.
140 - Do not ship an `app_secret` in the macOS app.
141 +- Do not show or log raw access tokens in production UI.
142 - `clearSession()` and `logout()` currently clear local stored credentials only.
143 - If you need native email/password forms, keep confidential auth credentials on your backend.
144
@@ -129,4 +146,3 @@ struct ContentView: View {
146
147 - `Docs/QuickStart-iOS.md`
148 - `Docs/Migration-From-Proxy-Native-Forms.md`
132 -- `SECURITY.md`
Docs/Release.md
+3 -3
@@ -38,7 +38,7 @@ That covers:
38
39 Create and push a semver tag:
40
41 -```/dev/null/bash.txt#L1-L2
41 +```bash
42 git tag v1.0.0
43 git push origin v1.0.0
44 ```
@@ -57,7 +57,7 @@ You can also run the `Release` workflow manually and provide a tag such as `v1.0
57
58 Before tagging, run:
59
60 -```/dev/null/bash.txt#L1-L2
60 +```bash
61 make verify
62 make verify-apple-destinations
63 ```
@@ -66,7 +66,7 @@ make verify-apple-destinations
66
67 If you are working alongside `smbcloud-cli`, you can generate the optional local UniFFI/XCFramework layer with:
68
69 -```/dev/null/bash.txt#L1-L4
69 +```bash
70 make ios
71 make macos
72 make tvos
Examples/HostedLoginExample/README.md
+1 -1
@@ -24,7 +24,7 @@ Before using the example, replace:
24
25 ## Build locally
26
27 -```/dev/null/bash.txt#L1-L1
27 +```bash
28 swift build --package-path Examples/HostedLoginExample
29 ```
30
Examples/HostedLoginExample/Sources/HostedLoginExample/HostedLoginView.swift
-6
@@ -22,12 +22,6 @@ struct HostedLoginView: View {
22 .font(.headline)
23 }
24
25 - if let session = authenticationStore.session {
26 - Text(session.accessToken)
27 - .font(.footnote.monospaced())
28 - .lineLimit(2)
29 - }
30 -
25 HStack {
26 Button(authenticationStore.isLoading ? "Loading…" : "Continue with smbCloud") {
27 Task {
README.md
+56 -185
@@ -1,7 +1,7 @@
1 <h1 align="center">smbCloud Auth for Swift</h1>
2
3 <p align="center">
4 - <strong>Hosted authentication for Apple apps, built around secure public-client patterns.</strong>
4 + <strong>Hosted login for Apple apps.</strong>
5 </p>
6
7 <p align="center">
@@ -13,143 +13,57 @@
13 <a href="https://github.com/smbcloudXYZ/smbcloud-cli">Rust core</a> ·
14 <a href="https://www.npmjs.com/package/@smbcloud/sdk-auth">npm</a> ·
15 <a href="https://pypi.org/project/smbcloud-sdk-auth/">PyPI</a> ·
16 - <a href="https://rubygems.org/gems/smbcloud-auth">RubyGems</a> ·
17 - <a href="https://smbcloud.xyz">Website</a>
16 + <a href="https://rubygems.org/gems/smbcloud-auth">RubyGems</a>
17 </p>
18
19 ---
20
22 -## Positioning
21 +`smbcloud-auth-swift` is the Swift package for smbCloud Auth on Apple platforms.
22
24 -`smbcloud-auth-swift` is intended to become the **Apple-platform client SDK** for smbCloud Auth:
25 -
26 -- iOS, macOS, tvOS, and visionOS support
27 -- hosted auth via system browser sessions
28 -- OIDC Authorization Code + PKCE
29 -- token exchange and session/profile helpers
30 -- **no client secret embedded in the shipped app**
31 -
32 -## Current status
33 -
34 -This package is still a **developer preview**, but the v1 public-client MVP surface, docs, tests, packaged example, and release validation workflows now exist.
35 -
36 -Today it provides:
23 +It gives you:
24
25 - `SmbCloudWebAuth`
26 - `SmbCloudSession`
27 - `SmbCloudCredentialsManager`
28 - `SmbCloudUserInfoClient`
42 -- hosted OIDC login with PKCE
43 -- `ASWebAuthenticationSession` orchestration
44 -- callback parsing + code exchange
45 -- local Keychain-backed session persistence
46 -- userinfo lookup
47 -- local session clearing/logout helper
48 -
49 -The public `SmbCloudAuth` product is now a stable pure-Swift surface for hosted auth on Apple platforms.
50 -The low-level generated UniFFI layer remains available as an optional local `SmbCloudAuthFFI` product when you build the Rust/XCFramework side during sibling-repo development.
51 -
52 -## Security model
53 -
54 -### Recommended for public Apple apps
55 -
56 -Use:
57 -
58 -- hosted auth
59 -- system browser sessions (`ASWebAuthenticationSession`)
60 -- OIDC Authorization Code + PKCE
61 -
62 -This is the correct model for:
63 -
64 -- App Store apps
65 -- open-source apps
66 -- consumer apps
67 -- partner apps you do not fully control
68 -
69 -### Not recommended for shipped public apps
70 -
71 -Do **not** embed an smbCloud Auth `app_secret` in:
72 -
73 -- the app bundle
74 -- `Info.plist`
75 -- local config copied into the app
76 -- the binary itself
77 -
78 -Even if it is not committed to Git, a secret inside a distributed client app is recoverable.
79 -
80 -### If you want native email/password forms
81 -
82 -Use a backend or BFF/proxy that holds the smbCloud Auth secret on the server.
83 -
84 -That pattern is valid when product UX requires native forms, but the server must own the confidential credentials.
85 -
86 -## Why this package exists
87 -
88 -Apple developers want a native SDK, not just protocol details.
89 -
90 -The goal is simple:
91 -
92 -1. start auth
93 -2. open hosted login in a system browser session
94 -3. handle callback
95 -4. exchange code
96 -5. store session
97 -6. load profile or log out
98 -
99 -## Secure integration patterns
100 -
101 -### 1. Hosted auth + PKCE — recommended
29
103 -Best for:
30 +The package is built for public clients:
31
105 -- public apps
106 -- open-source apps
107 -- App Store distribution
32 +- Authorization Code + PKCE
33 +- `ASWebAuthenticationSession`
34 +- Keychain-backed local session storage
35 +- no shipped client secret
36
109 -Flow:
37 +## Status
38
111 -1. build OIDC authorization request
112 -2. open hosted auth in `ASWebAuthenticationSession`
113 -3. receive callback URL
114 -4. parse `code` + `state`
115 -5. exchange code with PKCE verifier
116 -6. fetch user info
117 -7. store the resulting session locally
39 +Developer preview. The hosted-login MVP is in place and ready to try.
40
119 -### 2. Backend/BFF proxy — native forms
120 -
121 -Best for:
122 -
123 -- teams that want a fully native email/password screen
124 -- apps with an existing backend
125 -- managed enterprise deployments
126 -
127 -Flow:
128 -
129 -1. app shows native login/signup UI
130 -2. app sends credentials to your backend
131 -3. backend talks to smbCloud Auth with confidential credentials
132 -4. backend returns an app-safe session/token response
41 +## Installation
42
134 -## Current capabilities
43 +### Xcode
44
136 -The main public-client API now centers on:
45 +Use **File → Add Package Dependencies** and enter:
46
138 -- `SmbCloudWebAuth.login(...)`
139 -- `SmbCloudWebAuth.userInfo(...)`
140 -- `SmbCloudCredentialsManager.store(...)`
141 -- `SmbCloudCredentialsManager.current()`
142 -- `SmbCloudCredentialsManager.currentValidSession(...)`
143 -- `SmbCloudSession`
144 -- `SmbCloudUserInfoClient.userInfo(...)`
47 +```text
48 +https://github.com/smbcloudXYZ/smbcloud-auth-swift
49 +```
50
146 -That gives Apple apps an end-to-end hosted auth flow without writing their own PKCE, callback, token exchange, and Keychain glue.
51 +### Swift Package Manager
52
148 -## Product direction
53 +```swift
54 +dependencies: [
55 + .package(url: "https://github.com/smbcloudXYZ/smbcloud-auth-swift", from: "1.0.1")
56 +],
57 +targets: [
58 + .target(name: "MyApp", dependencies: [
59 + .product(name: "SmbCloudAuth", package: "smbcloud-auth-swift")
60 + ])
61 +]
62 +```
63
150 -The package now exposes an API in this shape:
64 +## Quick example
65
152 -```/dev/null/swift-example.swift#L1-L23
66 +```swift
67 import SmbCloudAuth
68
69 let webAuth = try SmbCloudWebAuth(
@@ -158,7 +72,10 @@ let webAuth = try SmbCloudWebAuth(
72 redirectURL: URL(string: "myapp://auth/callback")!
73 )
74
161 -let credentials = SmbCloudCredentialsManager()
75 +let credentials = SmbCloudCredentialsManager(
76 + service: "com.example.myapp.smbcloud-auth"
77 +)
78 +
79 let session = try await webAuth.login(
80 presentationAnchorProvider: {
81 window
@@ -166,40 +83,8 @@ let session = try await webAuth.login(
83 credentialsManager: credentials
84 )
85
169 -let restored = try credentials.currentValidSession()
86 let user = try await webAuth.userInfo(session: session)
171 -print(user.email ?? "")
172 -```
173 -
174 -And you can fetch profile data without the full web auth wrapper when needed:
175 -
176 -```/dev/null/swift-example.swift#L1-L12
177 -let userInfoClient = SmbCloudUserInfoClient(environment: .production)
178 -let user = try await userInfoClient.userInfo(accessToken: session.accessToken)
179 -print(user.sub)
180 -```
181 -
182 -## Installation
183 -
184 -### Xcode
185 -
186 -**File → Add Package Dependencies**, then enter:
187 -
188 -```/dev/null/spm.txt#L1-L1
189 -https://github.com/smbcloudXYZ/smbcloud-auth-swift
190 -```
191 -
192 -### Swift Package Manager
193 -
194 -```/dev/null/package.swift#L1-L8
195 -dependencies: [
196 - .package(url: "https://github.com/smbcloudXYZ/smbcloud-auth-swift", from: "1.0.1")
197 -],
198 -targets: [
199 - .target(name: "MyApp", dependencies: [
200 - .product(name: "SmbCloudAuth", package: "smbcloud-auth-swift")
201 - ])
202 -]
87 +print(user.email ?? "Signed in")
88 ```
89
90 ## Platforms
@@ -211,58 +96,44 @@ targets: [
96 | tvOS | 16.0 |
97 | visionOS | 1.0 |
98
214 -Hosted web login currently targets `ASWebAuthenticationSession` platforms: iOS, macOS, and visionOS. The shared session, storage, and userinfo helpers remain usable independently.
99 +Hosted web login currently runs on iOS, macOS, and visionOS. The session, storage, and user info helpers can still be used on other supported Apple platforms.
100 +
101 +## Security note
102 +
103 +- Use a public client ID.
104 +- Do not embed an smbCloud Auth `app_secret` in the app.
105 +- Use system browser auth, not embedded webviews.
106 +- If you need native email/password forms, keep confidential credentials on your backend or BFF.
107 +- `logout()` currently clears local credentials only.
108 +
109 +## Guides
110 +
111 +- [Quick Start — iOS](Docs/QuickStart-iOS.md)
112 +- [Quick Start — macOS](Docs/QuickStart-macOS.md)
113 +- [Migration from proxy/native-form auth](Docs/Migration-From-Proxy-Native-Forms.md)
114 +- [Hosted login example](Examples/HostedLoginExample/README.md)
115 +- [Release process](Docs/Release.md)
116
117 ## Local development
118
119 Clone this repo alongside `smbcloud-cli`:
120
220 -```/dev/null/tree.txt#L1-L4
121 +```text
122 Repositories/
123 ├── smbcloud-cli/
124 └── smbcloud-auth-swift/
125 ```
126
226 -Build the optional local XCFramework + UniFFI shim from the Rust source:
127 +Build the optional local XCFramework and UniFFI shim from the Rust source:
128
228 -```/dev/null/bash.txt#L1-L4
129 +```bash
130 make ios
131 make macos
132 make tvos
133 make visionos
134 ```
135
235 -This cross-compiles the Rust Apple bindings, regenerates `Sources/SmbCloudAuthFFI/smbcloud_auth.swift`, and writes the local XCFramework artifact used by the optional local `SmbCloudAuthFFI` product.
236 -
237 -The public `SmbCloudAuth` package can still build without these local Rust artifacts.
238 -
239 -## Guides and examples
240 -
241 -- [Quick Start — iOS](Docs/QuickStart-iOS.md)
242 -- [Quick Start — macOS](Docs/QuickStart-macOS.md)
243 -- [Migration from proxy/native-form auth](Docs/Migration-From-Proxy-Native-Forms.md)
244 -- [Hosted login example](Examples/HostedLoginExample/README.md)
245 -- [Release process](Docs/Release.md)
246 -
247 -You can build the packaged macOS example app locally with:
248 -
249 -```/dev/null/bash.txt#L1-L1
250 -swift build --package-path Examples/HostedLoginExample
251 -```
252 -
253 -And you can validate generic Apple destination builds locally with:
254 -
255 -```/dev/null/bash.txt#L1-L1
256 -make verify-apple-destinations
257 -```
258 -
259 -## Roadmap
260 -
261 -See [ROADMAP.md](ROADMAP.md).
262 -
263 -## Security guidance
264 -
265 -See [SECURITY.md](SECURITY.md).
136 +The public `SmbCloudAuth` product builds without local Rust artifacts. `SmbCloudAuthFFI` is there for sibling-repo development when you need the local bridge.
137
138 ## License
139
ROADMAP.md deleted
-255
@@ -1,255 +0,0 @@
1 -# smbCloud Auth for Swift roadmap
2 -
3 -## Product goal
4 -
5 -Build the default Apple-platform SDK for smbCloud Auth.
6 -
7 -That means developers should be able to integrate authentication into an iPhone, iPad, Mac, Apple TV, or visionOS app with a clear, secure, Apple-native workflow.
8 -
9 -The product needs to be:
10 -
11 -- secure for public clients
12 -- easy to adopt
13 -- easy to document
14 -- easy to distribute via Swift Package Manager
15 -- opinionated around Apple-native auth UX
16 -
17 -## Core positioning
18 -
19 -`smbcloud-auth-swift` should be the **public-client SDK**.
20 -
21 -It should optimize for:
22 -
23 -- hosted auth
24 -- OIDC Authorization Code + PKCE
25 -- `ASWebAuthenticationSession`
26 -- token/session management
27 -- profile lookup
28 -- logout
29 -
30 -It should **not** optimize for embedding confidential-client secrets into shipped apps.
31 -
32 -## Product split
33 -
34 -### Public Apple SDK
35 -
36 -Repository:
37 -
38 -- `smbcloud-auth-swift`
39 -
40 -Audience:
41 -
42 -- iOS developers
43 -- macOS developers
44 -- tvOS developers
45 -- visionOS developers
46 -- open-source app maintainers
47 -- App Store product teams
48 -
49 -Primary value proposition:
50 -
51 -- secure hosted login for Apple platforms without shipping a client secret
52 -
53 -### Confidential / server-side SDKs
54 -
55 -Audience:
56 -
57 -- server-side Swift
58 -- backend services
59 -- internal tooling
60 -- trusted CI / automation
61 -
62 -These flows can continue to exist in Rust or other SDKs, but they should not be the main product story for the public Apple package.
63 -
64 -## Version roadmap
65 -
66 -## v0.x — foundation
67 -
68 -Status:
69 -
70 -- in progress
71 -
72 -What exists today:
73 -
74 -- low-level Rust-backed OIDC helpers
75 -- PKCE request generation
76 -- callback parsing
77 -- token exchange
78 -- userinfo
79 -- Apple Sign-In helpers
80 -
81 -What is missing:
82 -
83 -- polished Apple-native public API
84 -- browser/session orchestration
85 -- credentials storage
86 -- production-ready docs and examples
87 -
88 -## v1.0 — public-client MVP
89 -
90 -Status:
91 -
92 -- v1 MVP complete in developer preview form
93 -- core API surface implemented
94 -- docs, tests, packaged example app target, CI, and release workflow implemented
95 -
96 -Goal:
97 -
98 -Ship the first sellable public-client SDK for Apple apps.
99 -
100 -### Implemented API surface
101 -
102 -- `SmbCloudWebAuth`
103 -- `SmbCloudSession`
104 -- `SmbCloudCredentialsManager`
105 -- `SmbCloudUserInfoClient`
106 -
107 -### Implemented capabilities
108 -
109 -- start hosted login with PKCE
110 -- open `ASWebAuthenticationSession`
111 -- handle callback URL
112 -- exchange authorization code
113 -- fetch user profile
114 -- local session persistence
115 -- local logout/session clearing helper
116 -
117 -### Completed MVP docs and examples
118 -
119 -- Quick Start for iOS
120 -- Quick Start for macOS
121 -- hosted login example
122 -- migration guide for teams coming from proxy/native-form auth
123 -- security guidance refresh for the current API shape
124 -
125 -### Stable-v1 follow-up work
126 -
127 -- release artifact parity across Apple platforms for the optional local `SmbCloudAuthFFI` product
128 -- logout / revocation story beyond local session clearing if smbCloud Auth exposes a public-client-safe revocation flow
129 -
130 -### MVP example shape
131 -
132 -```/dev/null/swift-example.swift#L1-L20
133 -let webAuth = SmbCloudWebAuth(
134 - domain: "auth.smbcloud.xyz",
135 - clientId: "YOUR_PUBLIC_CLIENT_ID",
136 - redirectURL: URL(string: "myapp://auth/callback")!
137 -)
138 -
139 -let session = try await webAuth.login(
140 - presenter: presenter,
141 - scope: ["openid", "profile", "email"]
142 -)
143 -
144 -let user = try await webAuth.userInfo(accessToken: session.accessToken)
145 -try credentialsManager.store(session)
146 -```
147 -
148 -## v1.1 — production polish
149 -
150 -### Developer experience
151 -
152 -- better error types
153 -- strongly typed session models
154 -- clock-skew handling
155 -- refresh token helpers if supported
156 -- cleaner cancellation behavior
157 -- better logging hooks
158 -
159 -### Apple integrations
160 -
161 -- SwiftUI examples
162 -- UIKit examples
163 -- scene-aware callback handling
164 -- universal link guidance
165 -- custom scheme guidance
166 -
167 -### Operations
168 -
169 -- stable release workflow
170 -- signed XCFramework release process
171 -- checksum automation in Swift package repo
172 -
173 -## v1.2 — enterprise / BFF support docs
174 -
175 -This package should still recommend hosted auth for public apps, but document how teams can combine it with a backend/BFF when they want:
176 -
177 -- native login forms
178 -- session cookies
179 -- custom trust boundaries
180 -- server-managed auth orchestration
181 -
182 -This should be a **documented architecture path**, not the core package identity.
183 -
184 -## v2 — broader Apple auth platform
185 -
186 -Potential additions:
187 -
188 -- multi-tenant helpers
189 -- organizations/workspaces support
190 -- passkey/WebAuthn coordination if smbCloud Auth adds it
191 -- richer logout/session revocation flows
192 -- first-class secure token storage abstractions
193 -- diagnostic tools for callback and state mismatches
194 -
195 -## Packaging roadmap
196 -
197 -## Distribution model
198 -
199 -Primary distribution should remain:
200 -
201 -- Swift Package Manager
202 -
203 -Artifact strategy:
204 -
205 -- prebuilt XCFramework zip attached to GitHub releases
206 -- generated Swift bindings checked into repo
207 -- local path development mode for sibling `smbcloud-cli` checkout
208 -
209 -## CI/CD expectations
210 -
211 -- Apple platform build validation
212 -- XCFramework release automation
213 -- checksum publishing
214 -- release notes generation
215 -- tagged semver releases
216 -
217 -## Go-to-market
218 -
219 -## Who buys this
220 -
221 -Not literally as a separate SKU at first — the SDK makes smbCloud Auth itself more sellable.
222 -
223 -It helps sell smbCloud Auth to:
224 -
225 -- indie Apple developers
226 -- startups building Apple-first products
227 -- enterprise teams with internal Apple apps
228 -- agencies shipping branded apps for clients
229 -
230 -## What makes it sellable
231 -
232 -A sellable auth SDK is not just “it compiles”.
233 -It must communicate:
234 -
235 -- secure defaults
236 -- clear docs
237 -- familiar API design
238 -- low integration friction
239 -- a future-proof auth model
240 -
241 -## Product standard
242 -
243 -The product standard is:
244 -
245 -> a junior Apple developer should be able to integrate login in an afternoon without accidentally shipping a secret.
246 -
247 -## Success criteria
248 -
249 -The SDK is on the right track when:
250 -
251 -- public examples never require `app_secret`
252 -- the default sample app uses hosted auth + PKCE
253 -- docs explicitly forbid shipping confidential secrets in clients
254 -- SwiftUI sample apps feel natural
255 -- teams can adopt it without building their own auth glue layer first
SECURITY.md deleted
-95
@@ -1,95 +0,0 @@
1 -# Security guidance for smbCloud Auth Swift
2 -
3 -## Public-client rule
4 -
5 -If your app ships to users, assume anything inside the client can be extracted.
6 -
7 -That includes:
8 -
9 -- values in `Info.plist`
10 -- values in bundled config files
11 -- environment-derived values copied into the app bundle
12 -- strings inside the binary
13 -- local cache or storage values
14 -
15 -Because of that:
16 -
17 -**Do not embed an smbCloud Auth `app_secret` inside a public Apple app.**
18 -
19 -## Recommended model for Apple apps
20 -
21 -Use:
22 -
23 -- hosted auth
24 -- system browser sessions
25 -- OIDC Authorization Code + PKCE
26 -- Keychain-backed local session storage
27 -
28 -On Apple platforms this generally means:
29 -
30 -- `ASWebAuthenticationSession`
31 -- a custom URL scheme or universal link callback
32 -- token exchange after the callback returns
33 -- persisting `SmbCloudSession` with `SmbCloudCredentialsManager`
34 -
35 -## Do not use embedded browser auth
36 -
37 -Avoid running auth inside embedded webviews such as:
38 -
39 -- `WKWebView`
40 -- other in-app embedded browser containers
41 -
42 -Use system browser-backed auth sessions instead.
43 -
44 -## If you need native login forms
45 -
46 -If your product requires a fully native email/password screen, do **not** solve that by shipping a secret in the app.
47 -
48 -Instead:
49 -
50 -- send credentials to your backend/BFF
51 -- keep smbCloud Auth confidential credentials on the server
52 -- let the backend proxy or orchestrate the auth flow
53 -
54 -## SDK positioning
55 -
56 -`smbcloud-auth-swift` should be used as a **public-client SDK**.
57 -
58 -That means the preferred long-term shape is:
59 -
60 -- hosted auth
61 -- PKCE
62 -- session/token helpers
63 -- userinfo/logout helpers
64 -
65 -not:
66 -
67 -- confidential-client secret-based login inside the shipped app
68 -
69 -## Local credential storage
70 -
71 -If you persist sessions locally, prefer Keychain storage.
72 -
73 -The SDK provides `SmbCloudCredentialsManager` for that purpose.
74 -Use an explicit per-app `service` name so multiple apps or test targets do not accidentally share the same stored session namespace.
75 -
76 -## Logout semantics
77 -
78 -The current SDK `logout()` helper is a local session-clearing helper.
79 -It clears persisted credentials through `SmbCloudCredentialsManager`, but it does not yet perform remote token revocation.
80 -
81 -## Operational guidance
82 -
83 -For development:
84 -
85 -- use separate dev credentials
86 -- prefer local-only configuration
87 -- never commit secrets into the repo
88 -- use a development-specific Keychain service name if you store sessions locally
89 -
90 -For production:
91 -
92 -- use public-client identifiers only in the app
93 -- keep confidential secrets in server-side systems
94 -- audit example code to ensure no public sample embeds secrets
95 -- review callback URL handling and Keychain service naming before shipping
Sources/SmbCloudAuth/SmbCloudClientError.swift
+2 -2
@@ -28,8 +28,8 @@ public enum SmbCloudClientError: Error, LocalizedError, Sendable {
28 return "The callback URL did not include an authorization code."
29 case .missingAuthorizationState:
30 return "The callback URL did not include an authorization state value."
31 - case .stateMismatch(let expected, let received):
32 - return "The callback state did not match. Expected \(expected), received \(received)."
31 + case .stateMismatch:
32 + return "The callback state did not match."
33 case .unsupportedPlatform:
34 return "Hosted web authentication is not supported on this platform."
35 case .loginInProgress:
Sources/SmbCloudAuth/SmbCloudOpenIDConnectClient.swift
+38 -1
@@ -99,9 +99,12 @@ internal struct SmbCloudOpenIDConnectClient {
99 let codeChallenge = Self.sha256Base64URL(from: codeVerifier)
100 let normalizedScopes = Self.normalizedScopes(scopes)
101
102 + let authorizeEndpoint = endpointURL(path: "oauth/authorize")
103 + try Self.validateTransportSecurity(for: authorizeEndpoint)
104 +
105 guard
106 var components = URLComponents(
104 - url: endpointURL(path: "oauth/authorize"),
107 + url: authorizeEndpoint,
108 resolvingAgainstBaseURL: false
109 )
110 else {
@@ -216,6 +219,12 @@ internal struct SmbCloudOpenIDConnectClient {
219 func send<Response: Decodable>(_ request: URLRequest, decodeAs: Response.Type) async throws
220 -> Response
221 {
222 + guard let requestURL = request.url else {
223 + throw SmbCloudClientError.invalidResponse
224 + }
225 +
226 + try Self.validateTransportSecurity(for: requestURL)
227 +
228 let data: Data
229 let response: URLResponse
230
@@ -285,6 +294,32 @@ internal struct SmbCloudOpenIDConnectClient {
294 return Data(digest).base64URLEncodedString()
295 }
296
297 + static func validateTransportSecurity(for url: URL) throws {
298 + let normalizedScheme = url.scheme?.lowercased()
299 + if normalizedScheme == "https" {
300 + return
301 + }
302 +
303 + if normalizedScheme == "http", isLoopbackHost(url.host) {
304 + return
305 + }
306 +
307 + throw SmbCloudClientError.invalidBaseURL(
308 + "smbCloud Auth URLs must use HTTPS. Plain HTTP is only allowed for local loopback development."
309 + )
310 + }
311 +
312 + static func isLoopbackHost(_ host: String?) -> Bool {
313 + guard let normalizedHost = host?.lowercased() else {
314 + return false
315 + }
316 +
317 + return normalizedHost == "localhost"
318 + || normalizedHost == "127.0.0.1"
319 + || normalizedHost == "::1"
320 + || normalizedHost == "[::1]"
321 + }
322 +
323 static func formEncodedData(from items: [URLQueryItem]) -> Data? {
324 var components = URLComponents()
325 components.queryItems = items
@@ -318,6 +353,8 @@ internal enum SmbCloudBaseURLFactory {
353 )
354 }
355
356 + try SmbCloudOpenIDConnectClient.validateTransportSecurity(for: url)
357 +
358 return url
359 }
360 }
Tests/SmbCloudAuthTests/SmbCloudAuthTests.swift
+41
@@ -75,6 +75,47 @@ final class SmbCloudAuthTests: XCTestCase {
75 }
76 }
77
78 + func testHttpLoopbackDomainIsAllowed() {
79 + XCTAssertNoThrow(
80 + try SmbCloudWebAuth(
81 + domain: "http://localhost:8088",
82 + clientId: "public-client-id",
83 + redirectURL: URL(string: "myapp://auth/callback")!
84 + )
85 + )
86 + XCTAssertNoThrow(try SmbCloudUserInfoClient(domain: "http://127.0.0.1:8088"))
87 + }
88 +
89 + func testHttpRemoteDomainThrowsInvalidBaseUrlError() {
90 + XCTAssertThrowsError(try SmbCloudUserInfoClient(domain: "http://api.smbcloud.xyz")) {
91 + error in
92 + guard case SmbCloudClientError.invalidBaseURL = error else {
93 + return XCTFail("Expected invalidBaseURL error, got: \(error)")
94 + }
95 + }
96 +
97 + XCTAssertThrowsError(
98 + try SmbCloudWebAuth(
99 + domain: "http://api.smbcloud.xyz",
100 + clientId: "public-client-id",
101 + redirectURL: URL(string: "myapp://auth/callback")!
102 + )
103 + ) { error in
104 + guard case SmbCloudClientError.invalidBaseURL = error else {
105 + return XCTFail("Expected invalidBaseURL error, got: \(error)")
106 + }
107 + }
108 + }
109 +
110 + func testStateMismatchErrorDescriptionDoesNotExposeStateValues() {
111 + let error = SmbCloudClientError.stateMismatch(
112 + expected: "expected-state",
113 + received: "received-state"
114 + )
115 +
116 + XCTAssertEqual(error.errorDescription, "The callback state did not match.")
117 + }
118 +
119 func testDefaultScopesMatchOidcDefaults() {
120 XCTAssertEqual(SmbCloudWebAuth.defaultScopes, ["openid", "profile", "email"])
121 }