Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): support third-party token exchange #41

Merged
merged 1 commit into from
Jan 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,45 @@ Requests a sign-in, but with a specific authentication provider (e.g., Sign in w
Supported values:
- `.appleId` - Prompt user to sign in with their Apple ID

### Rownd.getAccessToken() async throws -> String?
Assuming a user is signed-in, returns a valid access token, refreshing the current one if needed.
If an access token cannot be returned due to a temporary condition (e.g., inaccessible network), this function will `throw`.
If an access token cannot be returned because the refresh token is invalid, `nil` will be returned and the Rownd state
will sign out the user.

Example:

```swift
do {
let accessToken = try await Rownd.getAccessToken()
} catch {
// Alert the user that they should try again due to some recoverable error
}
}
```

### Rownd.getAccessToken(_ token: String) async -> String?
When possible, exchanges a non-Rownd access token for a Rownd access token. This is primarily used in scenarios
where an app is migrating from some other authentication mechanism to Rownd. Using Rownd integrations,
the system will accept a third-party token. If it successfully validates, Rownd will sign-in the user and
return a fresh Rownd access token to the caller.

This API returns `nil` if the token could not be validated and exchanged. If that occurs, it's likely
that the user should sign-in normally via `Rownd.requestSignIn()`.

> NOTE: This API is typically used once. After a Rownd token is available, other tokens should be discarded.
Example:

```swift
// Assume `oldToken` was retrieved from some prior authenticator.
let accessToken = await Rownd.getAccessToken(oldToken)
if (accessToken != nil) {
// Navigate to the UI that a user should typically see
} else {
Rownd.requestSignIn()
}
```

### Rownd.transferEncryptionKey() -> Void
Displays a bottom sheet enabling a user to transfer their encryption key and account to another device. When the user is authenticated, they will see options to generate a QR code that can be scanned by a different device or to scan a QR code generated on another device. When a user is not signed in, the only option available is to scan a QR code generated by another device.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<key>Rownd.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
<integer>1</integer>
</dict>
<key>RowndFrameworkTestApp.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>2</integer>
</dict>
<key>RowndTests.xcscheme_^#shared#^_</key>
<dict>
Expand Down
11 changes: 9 additions & 2 deletions Sources/Rownd/Models/Context/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,20 @@ struct TokenResource: APIResource {


class Auth {

static func fetchToken(_ token: String) async -> String? {
await withCheckedContinuation { continuation in
fetchToken(idToken: token) { tokenResp in
continuation.resume(returning: tokenResp?.accessToken)
}
}
}

static func fetchToken(idToken: String, withCompletion completion: @escaping (AuthState?) -> Void) -> Void {
guard let appId = store.state.appConfig.id else { return completion(nil) }
let tokenRequest = TokenRequest(idToken: idToken, appId: appId)
return fetchToken(tokenRequest: tokenRequest, withCompletion: completion)
}

static func fetchToken(tokenRequest: TokenRequest, withCompletion completion: @escaping (AuthState?) -> Void) -> Void {
var resource = TokenResource()
resource.headers = [
Expand Down
6 changes: 5 additions & 1 deletion Sources/Rownd/Rownd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,11 @@ public class Rownd: NSObject {
@discardableResult public static func getAccessToken() async throws -> String? {
return try await store.state.auth.getAccessToken()
}


@discardableResult public static func getAccessToken(token: String) async -> String? {
return await Auth.fetchToken(token)
}

public func state() -> Store<RowndState> {
return store
}
Expand Down