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

Refactor Authentication Flows for consistency and customization #214

Merged
merged 24 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
adc1a42
Accept ACR Values to authentication flows, Part 1
mikenachbaur-okta Dec 10, 2024
7817737
Ensure the authentication flow config is included in all appropriate …
mikenachbaur-okta Dec 11, 2024
655064a
Update tests
mikenachbaur-okta Dec 11, 2024
eeaf5fe
Added a API debugging convenience class, and fixed DirectAuth continu…
mikenachbaur-okta Dec 14, 2024
0046217
Fix lint error
mikenachbaur-okta Dec 14, 2024
1758ef0
Refactor authentication flows for consistency and customization.
mikenachbaur-okta Jan 31, 2025
828b975
Fix some test failures
mikenachbaur-okta Jan 31, 2025
a4727c9
Update minimum Swift version to 5.10
mikenachbaur-okta Jan 31, 2025
2bc85ec
Update and add tests, and establish an explicit connection to the ID …
mikenachbaur-okta Jan 31, 2025
7c54596
More tests, and fixes for localization key collisions
mikenachbaur-okta Jan 31, 2025
e724f7b
Update documentation & function signatures
mikenachbaur-okta Jan 31, 2025
fa5a0b6
Remove an Xcode 16 hint that breaks in Xcode 15.4
mikenachbaur-okta Jan 31, 2025
d3f4c61
Fix semgrep-scan job, and tidy up some things
mikenachbaur-okta Jan 31, 2025
0ac0075
Use a different orb version
mikenachbaur-okta Feb 1, 2025
562fc26
Update platform helper orb
mikenachbaur-okta Feb 1, 2025
7104297
More updates for semgrep scan
mikenachbaur-okta Feb 1, 2025
59454a9
Fix back snyk
rlepage-okta Feb 4, 2025
1a20b80
Fix tests & respond to PR feedback
mikenachbaur-okta Feb 4, 2025
148314c
Fix some automation tests
mikenachbaur-okta Feb 5, 2025
a702c3a
Refactor string-to-snake case conversion for a more complete solution
mikenachbaur-okta Feb 5, 2025
b6f858c
Fix a lint warning that behaves differently in CI
mikenachbaur-okta Feb 5, 2025
0aebc26
Correct some unit test expectations
mikenachbaur-okta Feb 5, 2025
027d66f
Fix problems where Xcode 14.x cannot infer the type of XCUIKeyboardKey
mikenachbaur-okta Feb 5, 2025
0a6f3c7
Another attempt at fixing a CI UI test issue
mikenachbaur-okta Feb 5, 2025
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
5 changes: 2 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
version: 2.1

orbs:
platform-helpers: okta/platform-helpers@1
general-platform-helpers: okta/general-platform-helpers@1.9
macos: circleci/macos@2

Expand Down Expand Up @@ -203,7 +204,6 @@ jobs:
- run:
name: run swift package show dependencies
command: swift package show-dependencies
- general-platform-helpers/step-load-dependencies
- general-platform-helpers/step-run-snyk-monitor:
scan-all-projects: true
skip-unresolved: false
Expand Down Expand Up @@ -234,9 +234,8 @@ workflows:

semgrep:
jobs:
- general-platform-helpers/job-semgrep-scan:
- platform-helpers/job-semgrep-scan:
name: semgrep-scan
resource-class: medium
context:
- static-analysis

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
strategy:
matrix:
os: [macos-latest-large, ubuntu-latest]
swift_version: ["5.9", "5.10"]
swift_version: ["5.10"]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
steps:
Expand Down
2 changes: 1 addition & 1 deletion OktaAuthFoundation.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ Provides the foundation and common features used to authenticate users, managing
s.source = { :git => "https://github.com/okta/okta-mobile-swift.git", :tag => s.version.to_s }
s.source_files = "Sources/AuthFoundation/**/*.swift"
s.resource_bundles = { "AuthFoundation" => "Sources/AuthFoundation/Resources/**/*" }
s.swift_version = "5.9"
s.swift_version = "5.10"
end
2 changes: 1 addition & 1 deletion OktaDirectAuth.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Enables application developers to build native sign in experiences using the Okt
s.source = { :git => "https://github.com/okta/okta-mobile-swift.git", :tag => s.version.to_s }
s.source_files = "Sources/OktaDirectAuth/**/*.swift"
s.resource_bundles = { "OktaDirectAuth" => "Sources/OktaDirectAuth/Resources/**/*" }
s.swift_version = "5.9"
s.swift_version = "5.10"

s.dependency "OktaAuthFoundation", "#{s.version.to_s}"
end
2 changes: 1 addition & 1 deletion OktaOAuth2.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Enables application developers to authenticate users utilizing a variety of OAut
s.source = { :git => "https://github.com/okta/okta-mobile-swift.git", :tag => s.version.to_s }
s.source_files = "Sources/OktaOAuth2/**/*.swift"
s.resource_bundles = { "OktaOAuth2" => "Sources/OktaOAuth2/Resources/**/*" }
s.swift_version = "5.9"
s.swift_version = "5.10"

s.dependency "OktaAuthFoundation", "#{s.version.to_s}"
end
2 changes: 1 addition & 1 deletion OktaWebAuthenticationUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Authenticate users using web-based OIDC.
s.source = { :git => "https://github.com/okta/okta-mobile-swift.git", :tag => s.version.to_s }
s.source_files = "Sources/WebAuthenticationUI/**/*.swift"
s.resource_bundles = { "WebAuthenticationUI" => "Sources/WebAuthenticationUI/Resources/**/*" }
s.swift_version = "5.9"
s.swift_version = "5.10"

s.dependency "OktaAuthFoundation", "#{s.version.to_s}"
s.dependency "OktaOAuth2", "#{s.version.to_s}"
Expand Down
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down Expand Up @@ -48,7 +48,10 @@ var package = Package(
path: "Tests/TestCommon"),
.testTarget(name: "AuthFoundationTests",
dependencies: ["AuthFoundation", "TestCommon"],
resources: [ .copy("MockResponses") ]),
resources: [
.copy("MockResponses"),
.copy("ConfigResources"),
]),
.testTarget(name: "OktaOAuth2Tests",
dependencies: ["OktaOAuth2", "TestCommon"],
resources: [ .copy("MockResponses") ]),
Expand Down
16 changes: 8 additions & 8 deletions Samples/AuthenticationFlows.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ func signInWithWebUsingCustomConfiguration() async throws {
throw SampleError.invalidUrl
}

let auth = WebAuthentication(issuer: issuerUrl,
let auth = WebAuthentication(issuerURL: issuerUrl,
clientId: clientId,
scopes: "openid profile email offline_access device_sso",
scope: "openid profile email offline_access device_sso",
redirectUri: redirectUrl)

// Sign in using the above configuration
Expand All @@ -51,9 +51,9 @@ func signInUsingAuthorizationCode() async throws {
throw SampleError.invalidUrl
}

let flow = AuthorizationCodeFlow(issuer: issuerUrl,
let flow = AuthorizationCodeFlow(issuerURL: issuerUrl,
clientId: clientId,
scopes: "openid profile email offline_access",
scope: "openid profile email offline_access",
redirectUri: redirectUrl)

// Initiate the auth flow, and get the URL to present to the user
Expand All @@ -74,9 +74,9 @@ func signInUsingResourceOwner(username: String, password: String) async throws {
throw SampleError.invalidUrl
}

let flow = ResourceOwnerFlow(issuer: issuerUrl,
let flow = ResourceOwnerFlow(issuerURL: issuerUrl,
clientId: clientId,
scopes: "openid profile email offline_access")
scope: "openid profile email offline_access")

// Sign in using a username & password
let token = try await flow.start(username: username, password: password)
Expand All @@ -91,9 +91,9 @@ func signInUsingDeviceSSO(deviceToken: String, idToken: String) async throws {
}

// Create the flow
let flow = TokenExchangeFlow(issuer: issuerUrl,
let flow = TokenExchangeFlow(issuerURL: issuerUrl,
clientId: clientId,
scopes: "openid profile offline_access",
scope: "openid profile offline_access",
audience: .default)

// Exchange the ID and Device tokens for access tokens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class NativeSignInViewController: UIViewController {
@IBAction func signIn(_ sender: Any) {
guard let username = usernameField.text,
let password = passwordField.text,
let issuer = config?.issuer
let issuer = config?.issuerURL
else {
show("Invalid username or password")
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import SafariServices

extension ViewController {
@IBAction func openBrowser(_ sender: Any) {
guard let url = flow?.context?.verificationUriComplete else { return }
guard let url = flow?.context?.verification?.verificationUriComplete else { return }
let browser = SFSafariViewController(url: url)
present(browser, animated: true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ class ViewController: UIViewController {
if !domain.isEmpty,
let issuerUrl = URL(string: "https://\(domain)")
{
flow = DeviceAuthorizationFlow(issuer: issuerUrl,
flow = DeviceAuthorizationFlow(issuerURL: issuerUrl,
clientId: clientId,
scopes: "openid profile email offline_access")
scope: "openid profile email offline_access")
} else {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Client not configured", message: "Please update ViewController.swift", preferredStyle: .alert)
Expand Down Expand Up @@ -83,7 +83,7 @@ class ViewController: UIViewController {
present(alert, animated: true)
}

func show(_ context: DeviceAuthorizationFlow.Context) {
func show(_ context: DeviceAuthorizationFlow.Verification) {
update(prompt: context.verificationUri)
update(qrCode: context.verificationUriComplete)
update(code: context.userCode)
Expand All @@ -94,7 +94,7 @@ class ViewController: UIViewController {
openAuthenticationButton?.isHidden = false
}

flow?.resume(with: context) { result in
flow?.resume { result in
DispatchQueue.main.async {
switch result {
case .failure(let error):
Expand Down
121 changes: 91 additions & 30 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/ContinuationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//

import SwiftUI
import OktaDirectAuth
@testable import OktaDirectAuth

extension SignInView {
struct ContinuationView: View {
Expand All @@ -30,40 +30,41 @@ extension SignInView {
}
}

var continuationType: DirectAuthenticationFlow.ContinuationType? {
guard case let .continuation(continuationType) = status else {
return nil
}

return continuationType
}

@Binding var error: Error?
@Binding var hasError: Bool

var body: some View {
VStack {
Text("Please continue authenticating.")
.padding(25)

VStack(alignment: .leading, spacing: 1) {
Picker(selection: $selectedFactor, label: EmptyView()) {
ForEach(SignInView.Factor.continuationFactors, id: \.self) {
Text($0.title)
}
}.pickerStyle(.menu)
.accessibilityIdentifier("factor_type_button")
.padding(.horizontal, -10)
.padding(.vertical, -4)

if selectedFactor == .code {
TextField("123456", text: $verificationCode)
.textContentType(.oneTimeCode)
.accessibilityIdentifier("verification_code_button")
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 6)
.stroke(.secondary, lineWidth: 1)
}
switch continuationType {
case .webAuthn(_):
Text("Ignoring WebAuthn type")
.padding()
case .transfer(_, let code):
VStack(alignment: .center, spacing: 8) {
Text("Use the verification code")
.font(.headline)
.multilineTextAlignment(.center)
if #available(iOS 16.0, *) {
Text(code)
.font(.system(.largeTitle, design: .monospaced, weight: .black))
.multilineTextAlignment(.center)
} else {
Text(code)
.font(.largeTitle)
.multilineTextAlignment(.center)
}

if let factor = factor {
Button("Continue") {
ProgressView()
.onAppear {
Task {
do {
status = try await flow.resume(status, with: factor)
status = try await flow.resume(with: .transfer)
if case let .success(token) = status {
Credential.default = try Credential.store(token)
}
Expand All @@ -73,12 +74,72 @@ extension SignInView {
}
}
}
.accessibilityIdentifier("signin_button")
}
case .prompt(_):
VStack(alignment: .leading, spacing: 8) {
Text("Verification code:")
.font(.headline)
.buttonStyle(.borderedProminent)
.multilineTextAlignment(.center)
TextField("123456", text: $verificationCode)
.textContentType(.oneTimeCode)
.accessibilityIdentifier("verification_code_button")
.padding(10)
.overlay {
RoundedRectangle(cornerRadius: 6)
.stroke(.secondary, lineWidth: 1)
}

Button("Continue") {
Task {
do {
status = try await flow.resume(with: .prompt(code: verificationCode))
if case let .success(token) = status {
Credential.default = try Credential.store(token)
}
} catch {
self.error = error
self.hasError = true
}
}
}
.accessibilityIdentifier("signin_button")
.font(.headline)
.buttonStyle(.borderedProminent)
}.padding()
case nil:
Text("Invalid status type")
.padding()
}
}
}
}

extension DirectAuthenticationFlow.ContinuationType {
static let previewTransfer: Self = .transfer(
.init(oobResponse: .init(oobCode: "OOBCODE", expiresIn: 600, interval: 10, channel: .push, bindingMethod: .transfer), mfaContext: nil),
code: "73")
rajdeepnanua-okta marked this conversation as resolved.
Show resolved Hide resolved
static let previewPrompt: Self = .prompt(.init(oobResponse: .init(oobCode: "OOBCODE", expiresIn: 600, interval: 10, channel: .push, bindingMethod: .transfer), mfaContext: nil))
}

#Preview {
struct Preview: View {
var flow = DirectAuthenticationFlow(
// swiftlint:disable:next force_unwrapping
issuerURL: URL(string: "https://example.com/")!,
clientId: "clientid",
scope: "scopes")
@State var error: Error?
@State var hasError: Bool = false
@State var continuationType: DirectAuthenticationFlow.ContinuationType = .previewPrompt

var body: some View {
SignInView.ContinuationView(
flow: flow,
status: .continuation(continuationType),
error: $error,
hasError: $hasError)
}
}

return Preview()
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ extension SignInView {
Button("Continue") {
Task {
do {
status = try await flow.resume(status, with: factor)
status = try await flow.resume(with: factor)
if case let .success(token) = status {
Credential.default = try Credential.store(token)
}
Expand Down
10 changes: 5 additions & 5 deletions Samples/DirectAuthSignIn/DirectAuthSignIn/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//

import SwiftUI
import OktaDirectAuth
@testable import OktaDirectAuth

struct SignInView: View {
let flow: DirectAuthenticationFlow?
Expand Down Expand Up @@ -99,17 +99,17 @@ struct SignInView: View {
// swiftlint:disable force_unwrapping
struct SignInViewPrimary_Previews: PreviewProvider {
static var previews: some View {
SignInView(flow: .init(issuer: URL(string: "https://example.com")!,
SignInView(flow: .init(issuerURL: URL(string: "https://example.com")!,
clientId: "abcd123",
scopes: "openid profile"))
scope: "openid profile"))
}
}

struct SignInViewSecondary_Previews: PreviewProvider {
static var previews: some View {
SignInView(flow: .init(issuer: URL(string: "https://example.com")!,
SignInView(flow: .init(issuerURL: URL(string: "https://example.com")!,
clientId: "abcd123",
scopes: "openid profile"),
scope: "openid profile"),
status: .mfaRequired(.init(supportedChallengeTypes: [],
mfaToken: "abcd1234")))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ class SignInViewController: UIHostingController<SignInView> {

// Workaround to remove the `device_sso` scope, when included in the property list.
if let configuration = try? OAuth2Client.PropertyListConfiguration(),
let ssoRange = configuration.scopes.range(of: "device_sso")
let ssoRange = configuration.scope.range(of: "device_sso")
{
var scopes = configuration.scopes
scopes.removeSubrange(ssoRange)
var scope = configuration.scope
scope.removeSubrange(ssoRange)

flow = DirectAuthenticationFlow(issuer: configuration.issuer,
flow = DirectAuthenticationFlow(issuerURL: configuration.issuerURL,
clientId: configuration.clientId,
scopes: scopes)
scope: scope)
} else {
flow = try? DirectAuthenticationFlow()
}
Expand Down
Loading