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
}