diff --git a/Sources/AuthFoundation/Responses/GrantType.swift b/Sources/AuthFoundation/Responses/GrantType.swift index 7b4d34415..1bc68888b 100644 --- a/Sources/AuthFoundation/Responses/GrantType.swift +++ b/Sources/AuthFoundation/Responses/GrantType.swift @@ -24,6 +24,7 @@ public enum GrantType: Codable, Hashable { case otpMFA case oobMFA case webAuthn + case webAuthnMFA case other(_ type: String) } @@ -38,8 +39,8 @@ private let grantTypeMapping: [String: GrantType] = [ "urn:okta:params:oauth:grant-type:oob": .oob, "http://auth0.com/oauth/grant-type/mfa-otp": .otpMFA, "http://auth0.com/oauth/grant-type/mfa-oob": .oobMFA, - "urn:okta:params:oauth:grant-type:webauthn": .webAuthn - + "urn:okta:params:oauth:grant-type:webauthn": .webAuthn, + "urn:okta:params:oauth:grant-type:mfa-webauthn": .webAuthnMFA, ] extension GrantType: RawRepresentable { @@ -79,6 +80,8 @@ extension GrantType: RawRepresentable { return "http://auth0.com/oauth/grant-type/mfa-oob" case .webAuthn: return "urn:okta:params:oauth:grant-type:webauthn" + case .webAuthnMFA: + return "urn:okta:params:oauth:grant-type:mfa-webauthn" } } } diff --git a/Sources/OktaDirectAuth/DirectAuthFlow.swift b/Sources/OktaDirectAuth/DirectAuthFlow.swift index 9ef0ccbc7..80ffe2309 100644 --- a/Sources/OktaDirectAuth/DirectAuthFlow.swift +++ b/Sources/OktaDirectAuth/DirectAuthFlow.swift @@ -149,6 +149,14 @@ public class DirectAuthenticationFlow: AuthenticationFlow { let oobResponse: OOBResponse } + /// Holds information about a challenge request when initiating a WebAuthn authentication. + public struct WebAuthnContext { + /// The credential request returned from the server. + public let request: WebAuthn.CredentialRequestOptions + + let mfaContext: MFAContext? + } + /// The current status of the authentication flow. /// /// This value is returned from ``DirectAuthenticationFlow/start(_:with:)`` and ``DirectAuthenticationFlow/resume(_:with:)`` to indicate the result of an individual authentication step. This can be used to drive your application's sign-in workflow. @@ -165,7 +173,7 @@ public class DirectAuthenticationFlow: AuthenticationFlow { case mfaRequired(_ context: MFAContext) /// Indicates the user is being prompted with a WebAuthn challenge request. - case webAuthn(request: WebAuthn.CredentialRequestOptions) + case webAuthn(_ context: WebAuthnContext) } /// The OAuth2Client this authentication flow will use. diff --git a/Sources/OktaDirectAuth/Internal/Authentication Factors/AuthenticationFactor.swift b/Sources/OktaDirectAuth/Internal/Authentication Factors/AuthenticationFactor.swift index 6c4667cca..6822ac85f 100644 --- a/Sources/OktaDirectAuth/Internal/Authentication Factors/AuthenticationFactor.swift +++ b/Sources/OktaDirectAuth/Internal/Authentication Factors/AuthenticationFactor.swift @@ -16,19 +16,20 @@ import AuthFoundation /// Defines the additional token parameters that can be introduced through input arguments. protocol HasTokenParameters { /// Parameters to include in the API request. - var tokenParameters: [String: Any]? { get } + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] } /// Defines the common properties and functions shared between factor types. protocol AuthenticationFactor: HasTokenParameters { /// The grant type supported by this factor. - var grantType: GrantType { get } - + func grantType(currentStatus: DirectAuthenticationFlow.Status?) -> GrantType + /// Returns a step handler capable of handling this authentication factor. /// - Parameters: /// - flow: The current flow for this authentication step. /// - openIdConfiguration: OpenID configuration for this org. /// - loginHint: The login hint for this session. + /// - currentStatus: The current status this step is being created from, if applicable. /// - factor: The factor for the step to process. /// - Returns: A step handler capable of processing this authentication factor. func stepHandler(flow: DirectAuthenticationFlow, diff --git a/Sources/OktaDirectAuth/Internal/Authentication Factors/PrimaryFactor.swift b/Sources/OktaDirectAuth/Internal/Authentication Factors/PrimaryFactor.swift index 409c99e97..dba965104 100644 --- a/Sources/OktaDirectAuth/Internal/Authentication Factors/PrimaryFactor.swift +++ b/Sources/OktaDirectAuth/Internal/Authentication Factors/PrimaryFactor.swift @@ -26,7 +26,7 @@ extension DirectAuthenticationFlow.PrimaryFactor { extension DirectAuthenticationFlow.PrimaryFactor: AuthenticationFactor { func stepHandler(flow: DirectAuthenticationFlow, - openIdConfiguration: AuthFoundation.OpenIdConfiguration, + openIdConfiguration: OpenIdConfiguration, loginHint: String? = nil, currentStatus: DirectAuthenticationFlow.Status? = nil, factor: DirectAuthenticationFlow.PrimaryFactor) throws -> StepHandler @@ -36,6 +36,7 @@ extension DirectAuthenticationFlow.PrimaryFactor: AuthenticationFactor { case .password: let request = TokenRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, + currentStatus: currentStatus, loginHint: loginHint, factor: factor, grantTypesSupported: flow.supportedGrantTypes) @@ -47,41 +48,42 @@ extension DirectAuthenticationFlow.PrimaryFactor: AuthenticationFactor { } return try OOBStepHandler(flow: flow, openIdConfiguration: openIdConfiguration, + currentStatus: currentStatus, loginHint: loginHint, - mfaToken: currentStatus?.mfaToken, channel: channel, factor: factor, bindingContext: bindingContext) case .webAuthn: + let mfaContext = currentStatus?.mfaContext let request = try WebAuthnChallengeRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, loginHint: loginHint, - mfaToken: currentStatus?.mfaToken) + mfaToken: mfaContext?.mfaToken) return ChallengeStepHandler(flow: flow, request: request) { - .webAuthn(request: $0) + .webAuthn(.init(request: $0, + mfaContext: mfaContext)) } } } - var tokenParameters: [String: Any]? { + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] { + var result: [String: String] = [ + "grant_type": grantType(currentStatus: currentStatus).rawValue, + ] + switch self { case .otp(code: let code): - return [ - "grant_type": grantType.rawValue, - "otp": code - ] + result["otp"] = code case .password(let password): - return [ - "grant_type": grantType.rawValue, - "password": password - ] - case .oob, .webAuthn: - return nil + result["password"] = password + case .oob: break + case .webAuthn: break } - + + return result } - var grantType: GrantType { + func grantType(currentStatus: DirectAuthenticationFlow.Status?) -> GrantType { switch self { case .otp: return .otp diff --git a/Sources/OktaDirectAuth/Internal/Authentication Factors/SecondaryFactor.swift b/Sources/OktaDirectAuth/Internal/Authentication Factors/SecondaryFactor.swift index e14591bc5..e6bad13b9 100644 --- a/Sources/OktaDirectAuth/Internal/Authentication Factors/SecondaryFactor.swift +++ b/Sources/OktaDirectAuth/Internal/Authentication Factors/SecondaryFactor.swift @@ -28,64 +28,73 @@ extension DirectAuthenticationFlow.SecondaryFactor: AuthenticationFactor { case .otp: let request = TokenRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, + currentStatus: currentStatus, loginHint: loginHint, factor: factor, - mfaToken: currentStatus?.mfaToken, grantTypesSupported: flow.supportedGrantTypes) return TokenStepHandler(flow: flow, request: request) case .oob(channel: let channel): return try OOBStepHandler(flow: flow, openIdConfiguration: openIdConfiguration, + currentStatus: currentStatus, loginHint: loginHint, - mfaToken: currentStatus?.mfaToken, channel: channel, factor: factor, bindingContext: bindingContext) case .webAuthn: + let mfaContext = currentStatus?.mfaContext let request = try WebAuthnChallengeRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, loginHint: loginHint, - mfaToken: currentStatus?.mfaToken) + mfaToken: mfaContext?.mfaToken) return ChallengeStepHandler(flow: flow, request: request) { - .webAuthn(request: $0) + .webAuthn(.init(request: $0, + mfaContext: mfaContext)) } case .webAuthnAssertion(let response): let request = TokenRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, + currentStatus: currentStatus, loginHint: loginHint, factor: factor, - mfaToken: currentStatus?.mfaToken, parameters: response, grantTypesSupported: flow.supportedGrantTypes) return TokenStepHandler(flow: flow, request: request) } } - var tokenParameters: [String: Any]? { + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] { + var result: [String: String] = [ + "grant_type": grantType(currentStatus: currentStatus).rawValue, + ] + + if let context = currentStatus?.mfaContext { + result["mfa_token"] = context.mfaToken + } + switch self { case .otp(code: let code): - return [ - "grant_type": grantType.rawValue, - "otp": code - ] - case .oob, .webAuthn: - return nil - case .webAuthnAssertion(_): - return [ - "grant_type": grantType.rawValue - ] + result["otp"] = code + case .webAuthnAssertion(_): break + case .oob(channel: _): break + case .webAuthn: break } + return result } - var grantType: GrantType { + func grantType(currentStatus: DirectAuthenticationFlow.Status?) -> GrantType { switch self { case .otp: return .otpMFA case .oob: return .oobMFA case .webAuthn, .webAuthnAssertion(_): - return .webAuthn + if currentStatus?.mfaContext?.mfaToken != nil { + return .webAuthnMFA + } else { + return .webAuthn + } } } } diff --git a/Sources/OktaDirectAuth/Internal/Requests/OOBAuthenticateRequest.swift b/Sources/OktaDirectAuth/Internal/Requests/OOBAuthenticateRequest.swift index 9bc1dc402..3749d9e81 100644 --- a/Sources/OktaDirectAuth/Internal/Requests/OOBAuthenticateRequest.swift +++ b/Sources/OktaDirectAuth/Internal/Requests/OOBAuthenticateRequest.swift @@ -36,7 +36,7 @@ struct OOBResponse: Codable, HasTokenParameters { self.bindingCode = bindingCode } - var tokenParameters: [String: Any]? { + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] { ["oob_code": oobCode] } } diff --git a/Sources/OktaDirectAuth/Internal/Requests/TokenRequest.swift b/Sources/OktaDirectAuth/Internal/Requests/TokenRequest.swift index df5ded182..e66cc334f 100644 --- a/Sources/OktaDirectAuth/Internal/Requests/TokenRequest.swift +++ b/Sources/OktaDirectAuth/Internal/Requests/TokenRequest.swift @@ -16,25 +16,25 @@ import AuthFoundation struct TokenRequest { let openIdConfiguration: OpenIdConfiguration let clientConfiguration: OAuth2Client.Configuration + let currentStatus: DirectAuthenticationFlow.Status? let loginHint: String? let factor: any AuthenticationFactor - let mfaToken: String? let parameters: (any HasTokenParameters)? let grantTypesSupported: [GrantType]? init(openIdConfiguration: OpenIdConfiguration, clientConfiguration: OAuth2Client.Configuration, + currentStatus: DirectAuthenticationFlow.Status?, loginHint: String? = nil, factor: any AuthenticationFactor, - mfaToken: String? = nil, parameters: (any HasTokenParameters)? = nil, grantTypesSupported: [GrantType]? = nil) { self.openIdConfiguration = openIdConfiguration self.clientConfiguration = clientConfiguration + self.currentStatus = currentStatus self.loginHint = loginHint self.factor = factor - self.mfaToken = mfaToken self.parameters = parameters self.grantTypesSupported = grantTypesSupported } @@ -47,17 +47,11 @@ extension TokenRequest: OAuth2TokenRequest, OAuth2APIRequest, APIRequestBody { var contentType: APIContentType? { .formEncoded } var acceptsType: APIContentType? { .json } var bodyParameters: [String: Any]? { - var result: [String: Any] = [ - "client_id": clientConfiguration.clientId, - "grant_type": factor.grantType.rawValue, - "scope": clientConfiguration.scopes - ] - - if let mfaToken = mfaToken { - result["mfa_token"] = mfaToken - } + var result = factor.tokenParameters(currentStatus: currentStatus) + result["client_id"] = clientConfiguration.clientId + result["scope"] = clientConfiguration.scopes - if let tokenParameters = parameters?.tokenParameters { + if let tokenParameters = parameters?.tokenParameters(currentStatus: currentStatus) { result.merge(tokenParameters, uniquingKeysWith: { $1 }) } @@ -75,10 +69,6 @@ extension TokenRequest: OAuth2TokenRequest, OAuth2APIRequest, APIRequestBody { result["grant_types_supported"] = grantTypesSupported.joined(separator: " ") } - if let parameters = factor.tokenParameters { - result.merge(parameters, uniquingKeysWith: { $1 }) - } - if let parameters = clientConfiguration.authentication.additionalParameters { result.merge(parameters, uniquingKeysWith: { $1 }) } diff --git a/Sources/OktaDirectAuth/Internal/Requests/WebAuthnRequest.swift b/Sources/OktaDirectAuth/Internal/Requests/WebAuthnRequest.swift index 77c43b86c..3401afd5b 100644 --- a/Sources/OktaDirectAuth/Internal/Requests/WebAuthnRequest.swift +++ b/Sources/OktaDirectAuth/Internal/Requests/WebAuthnRequest.swift @@ -64,7 +64,7 @@ extension WebAuthnChallengeRequest: APIRequest, APIRequestBody { } extension WebAuthn.AuthenticatorAssertionResponse: HasTokenParameters { - var tokenParameters: [String: Any]? { + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] { var result = [ "clientDataJSON": clientDataJSON, "authenticatorData": authenticatorData, diff --git a/Sources/OktaDirectAuth/Internal/Step Handlers/OOBStepHandler.swift b/Sources/OktaDirectAuth/Internal/Step Handlers/OOBStepHandler.swift index d8ed2c768..343bc0d9e 100644 --- a/Sources/OktaDirectAuth/Internal/Step Handlers/OOBStepHandler.swift +++ b/Sources/OktaDirectAuth/Internal/Step Handlers/OOBStepHandler.swift @@ -16,8 +16,8 @@ import AuthFoundation class OOBStepHandler: StepHandler { let flow: DirectAuthenticationFlow let openIdConfiguration: OpenIdConfiguration + let currentStatus: DirectAuthenticationFlow.Status? let loginHint: String? - let mfaToken: String? let channel: DirectAuthenticationFlow.OOBChannel let factor: Factor private let bindingContext: DirectAuthenticationFlow.BindingUpdateContext? @@ -25,16 +25,16 @@ class OOBStepHandler: StepHandler { init(flow: DirectAuthenticationFlow, openIdConfiguration: OpenIdConfiguration, + currentStatus: DirectAuthenticationFlow.Status?, loginHint: String?, - mfaToken: String?, channel: DirectAuthenticationFlow.OOBChannel, factor: Factor, bindingContext: DirectAuthenticationFlow.BindingUpdateContext? = nil) throws { self.flow = flow self.openIdConfiguration = openIdConfiguration + self.currentStatus = currentStatus self.loginHint = loginHint - self.mfaToken = mfaToken self.channel = channel self.factor = factor self.bindingContext = bindingContext @@ -56,12 +56,15 @@ class OOBStepHandler: StepHandler { case .none: self.requestToken(using: response, completion: completion) case .transfer: - guard let bindingCode = response.bindingCode, bindingCode.isEmpty == false else { + guard let bindingCode = response.bindingCode, + bindingCode.isEmpty == false + else { completion(.failure(.bindingCodeMissing)) return } - let context = DirectAuthenticationFlow.BindingUpdateContext(update: .transfer(bindingCode), oobResponse: response) - completion(.success(.bindingUpdate(context))) + + completion(.success(.bindingUpdate(.init(update: .transfer(bindingCode), + oobResponse: response)))) } } } @@ -78,8 +81,8 @@ class OOBStepHandler: StepHandler { } // Request where OOB is used as the secondary factor - else if let mfaToken = mfaToken { - requestOOBCode(mfaToken: mfaToken, completion: completion) + else if case let .mfaRequired(context) = currentStatus { + requestOOBCode(mfaToken: context.mfaToken, completion: completion) } // Cannot create a request @@ -113,10 +116,11 @@ class OOBStepHandler: StepHandler { completion: @escaping (Result) -> Void) { do { + let grantType = factor.grantType(currentStatus: currentStatus) let request = try ChallengeRequest(openIdConfiguration: openIdConfiguration, clientConfiguration: flow.client.configuration, mfaToken: mfaToken, - challengeTypesSupported: [factor.grantType]) + challengeTypesSupported: [grantType]) request.send(to: flow.client) { result in switch result { case .failure(let error): @@ -135,13 +139,13 @@ class OOBStepHandler: StepHandler { } private func requestToken(using response: OOBResponse, completion: @escaping (Result) -> Void) { - let request = TokenRequest(openIdConfiguration: self.openIdConfiguration, - clientConfiguration: self.flow.client.configuration, - factor: self.factor, - mfaToken: self.mfaToken, + let request = TokenRequest(openIdConfiguration: openIdConfiguration, + clientConfiguration: flow.client.configuration, + currentStatus: currentStatus, + factor: factor, parameters: response, - grantTypesSupported: self.flow.supportedGrantTypes) - self.poll = PollingHandler(client: self.flow.client, + grantTypesSupported: flow.supportedGrantTypes) + self.poll = PollingHandler(client: flow.client, request: request, expiresIn: response.expiresIn, interval: response.interval) { pollHandler, result in diff --git a/Sources/OktaDirectAuth/Internal/Utilities/Status+InternalExtensions.swift b/Sources/OktaDirectAuth/Internal/Utilities/Status+InternalExtensions.swift index a09276ffb..affb41c85 100644 --- a/Sources/OktaDirectAuth/Internal/Utilities/Status+InternalExtensions.swift +++ b/Sources/OktaDirectAuth/Internal/Utilities/Status+InternalExtensions.swift @@ -13,9 +13,11 @@ import Foundation extension DirectAuthenticationFlow.Status { - var mfaToken: String? { + var mfaContext: DirectAuthenticationFlow.MFAContext? { if case let .mfaRequired(context) = self { - return context.mfaToken + return context + } else if case let .webAuthn(context) = self { + return context.mfaContext } else { return nil } diff --git a/Sources/OktaDirectAuth/WebAuthn/PublicKeyCredentialDescriptor.swift b/Sources/OktaDirectAuth/WebAuthn/PublicKeyCredentialDescriptor.swift index 8f4fd206f..c555551dd 100644 --- a/Sources/OktaDirectAuth/WebAuthn/PublicKeyCredentialDescriptor.swift +++ b/Sources/OktaDirectAuth/WebAuthn/PublicKeyCredentialDescriptor.swift @@ -26,6 +26,6 @@ extension WebAuthn { public let type: PublicKeyCredentialType /// This OPTIONAL member contains a hint as to how the client might communicate with the managing authenticator of the public key credential the caller is referring to. The values SHOULD be members of AuthenticatorTransport but client platforms MUST ignore unknown values. - public let transports: [AuthenticatorTransport] + public let transports: [AuthenticatorTransport]? } } diff --git a/Sources/OktaDirectAuth/WebAuthn/WebAuthn.swift b/Sources/OktaDirectAuth/WebAuthn/WebAuthn.swift index 1a4ea1ddd..5ad5f31d0 100644 --- a/Sources/OktaDirectAuth/WebAuthn/WebAuthn.swift +++ b/Sources/OktaDirectAuth/WebAuthn/WebAuthn.swift @@ -72,3 +72,7 @@ public struct WebAuthn { public let userHandle: String? } } + +extension WebAuthn.CredentialRequestOptions: JSONDecodable { + public static var jsonDecoder = JSONDecoder() +} diff --git a/Tests/OktaDirectAuthTests/DirectAuthenticationFlowTests.swift b/Tests/OktaDirectAuthTests/DirectAuthenticationFlowTests.swift index f7fdf4fa5..55ebea4bd 100644 --- a/Tests/OktaDirectAuthTests/DirectAuthenticationFlowTests.swift +++ b/Tests/OktaDirectAuthTests/DirectAuthenticationFlowTests.swift @@ -33,12 +33,12 @@ struct TestFactor: AuthenticationFactor { let result: (Result)? let exception: (any Error)? - var grantType: AuthFoundation.GrantType { + func grantType(currentStatus: DirectAuthenticationFlow.Status?) -> GrantType { .implicit } - var tokenParameters: [String : Any]? { - nil + func tokenParameters(currentStatus: DirectAuthenticationFlow.Status?) -> [String: String] { + [:] } func stepHandler(flow: OktaDirectAuth.DirectAuthenticationFlow, diff --git a/Tests/OktaDirectAuthTests/ExtensionTests.swift b/Tests/OktaDirectAuthTests/ExtensionTests.swift index 1fcfe68b6..8254f29cc 100644 --- a/Tests/OktaDirectAuthTests/ExtensionTests.swift +++ b/Tests/OktaDirectAuthTests/ExtensionTests.swift @@ -23,8 +23,15 @@ final class ExtensionTests: XCTestCase { } func testAuthFlowStatus() throws { - XCTAssertNil(Status.success(Token.mockToken()).mfaToken) - XCTAssertEqual(Status.mfaRequired(.init(supportedChallengeTypes: nil, mfaToken: "abc123")).mfaToken, "abc123") + XCTAssertNil(Status.success(Token.mockToken()).mfaContext) + + let mfaContext = DirectAuthenticationFlow.MFAContext(supportedChallengeTypes: [.oob], mfaToken: "abc123") + XCTAssertEqual(Status.mfaRequired(mfaContext).mfaContext?.mfaToken, "abc123") + + let webAuthnContext = DirectAuthenticationFlow.WebAuthnContext( + request: try mock(from: .module, for: "challenge-webauthn", in: "MockResponses"), + mfaContext: mfaContext) + XCTAssertEqual(Status.webAuthn(webAuthnContext).mfaContext?.mfaToken, "abc123") } func testStatusEquality() throws { diff --git a/Tests/OktaDirectAuthTests/FactorPropertyTests.swift b/Tests/OktaDirectAuthTests/FactorPropertyTests.swift index 5402fbbf8..959da5430 100644 --- a/Tests/OktaDirectAuthTests/FactorPropertyTests.swift +++ b/Tests/OktaDirectAuthTests/FactorPropertyTests.swift @@ -24,45 +24,75 @@ final class FactorPropertyTests: XCTestCase { XCTAssertEqual(PrimaryFactor.webAuthn.loginHintKey, "login_hint") } - func testGrantTypes() throws { - XCTAssertEqual(PrimaryFactor.password("foo").grantType, .password) - XCTAssertEqual(PrimaryFactor.otp(code: "123456").grantType, .otp) - XCTAssertEqual(PrimaryFactor.oob(channel: .push).grantType, .oob) - XCTAssertEqual(PrimaryFactor.webAuthn.grantType, .webAuthn) - - XCTAssertEqual(SecondaryFactor.otp(code: "123456").grantType, .otpMFA) - XCTAssertEqual(SecondaryFactor.oob(channel: .push).grantType, .oobMFA) - XCTAssertEqual(SecondaryFactor.webAuthn.grantType, .webAuthn) - XCTAssertEqual(SecondaryFactor.webAuthnAssertion(.init(clientDataJSON: "", - authenticatorData: "", - signature: "", - userHandle: nil)).grantType, .webAuthn) - } - - func testTokenParameters() throws { - XCTAssertEqual(PrimaryFactor.password("foo").tokenParameters as? [String: String], [ + func testPrimaryTokenParameters() throws { + var parameters: [String: String] = [:] + + parameters = PrimaryFactor.password("foo").tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ "grant_type": "password", "password": "foo" ]) - XCTAssertEqual(PrimaryFactor.otp(code: "123456").tokenParameters as? [String: String], [ + + parameters = PrimaryFactor.otp(code: "123456").tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ "grant_type": "urn:okta:params:oauth:grant-type:otp", "otp": "123456" ]) - XCTAssertNil(PrimaryFactor.oob(channel: .push).tokenParameters) - XCTAssertNil(PrimaryFactor.webAuthn.tokenParameters) + + parameters = PrimaryFactor.oob(channel: .push).tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:oob" + ]) + + parameters = PrimaryFactor.webAuthn.tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:webauthn" + ]) + } + + func testSecondaryTokenParameters() throws { + var parameters: [String: String] = [:] - XCTAssertEqual(SecondaryFactor.otp(code: "123456").tokenParameters as? [String: String], [ + parameters = SecondaryFactor.otp(code: "123456").tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ "grant_type": "http://auth0.com/oauth/grant-type/mfa-otp", "otp": "123456" ]) - XCTAssertNil(SecondaryFactor.oob(channel: .push).tokenParameters) - XCTAssertNil(SecondaryFactor.webAuthn.tokenParameters) - XCTAssertEqual(SecondaryFactor.webAuthnAssertion(.init(clientDataJSON: "", - authenticatorData: "", - signature: "", - userHandle: nil)).tokenParameters as? [String: String], - [ - "grant_type": "urn:okta:params:oauth:grant-type:webauthn", - ]) + + parameters = SecondaryFactor.oob(channel: .push).tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ + "grant_type": "http://auth0.com/oauth/grant-type/mfa-oob" + ]) + + parameters = SecondaryFactor.webAuthn.tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:webauthn", + ]) + + parameters = SecondaryFactor.webAuthn.tokenParameters(currentStatus: .mfaRequired(.init(supportedChallengeTypes: nil, mfaToken: "abc123"))) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:mfa-webauthn", + "mfa_token": "abc123", + ]) + + parameters = SecondaryFactor.webAuthnAssertion(.init(clientDataJSON: "", + authenticatorData: "", + signature: "", + userHandle: nil)).tokenParameters(currentStatus: nil) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:webauthn", + ]) + + let context = DirectAuthenticationFlow.WebAuthnContext( + request: try mock(from: .module, for: "challenge-webauthn", in: "MockResponses"), + mfaContext: .init(supportedChallengeTypes: nil, mfaToken: "abc123")) + parameters = SecondaryFactor.webAuthnAssertion(.init(clientDataJSON: "", + authenticatorData: "", + signature: "", + userHandle: nil)).tokenParameters(currentStatus: .webAuthn(context)) + XCTAssertEqual(parameters, [ + "grant_type": "urn:okta:params:oauth:grant-type:mfa-webauthn", + "mfa_token": "abc123", + ]) } } diff --git a/Tests/OktaDirectAuthTests/MockResponses/challenge-webauthn.json b/Tests/OktaDirectAuthTests/MockResponses/challenge-webauthn.json new file mode 100644 index 000000000..3e83f9fb5 --- /dev/null +++ b/Tests/OktaDirectAuthTests/MockResponses/challenge-webauthn.json @@ -0,0 +1,25 @@ +{ + "challengeType": "urn:okta:params:oauth:grant-type:webauthn", + "publicKey": { + "challenge": "", + "userVerification": "preferred", + "extensions": { + "appid": "https://example.okta.com" + }, + "allowCredentials": [ + { + "id": "", + "type": "public-key" + } + ] + }, + "authenticatorEnrollments": [ + { + "credentialId": "", + "displayName": "", + "profile": { + "aaguid": "" + } + } + ] +} diff --git a/Tests/OktaDirectAuthTests/RequestTests.swift b/Tests/OktaDirectAuthTests/RequestTests.swift index 6dd0550e4..12e11584c 100644 --- a/Tests/OktaDirectAuthTests/RequestTests.swift +++ b/Tests/OktaDirectAuthTests/RequestTests.swift @@ -32,6 +32,7 @@ final class RequestTests: XCTestCase { clientConfiguration: try .init(domain: "example.com", clientId: "theClientId", scopes: "openid profile"), + currentStatus: nil, factor: DirectAuthenticationFlow.PrimaryFactor.password("password123")) XCTAssertEqual(request.bodyParameters as? [String: String], [ @@ -47,6 +48,7 @@ final class RequestTests: XCTestCase { clientId: "theClientId", scopes: "openid profile", authentication: .clientSecret("supersecret")), + currentStatus: nil, factor: DirectAuthenticationFlow.PrimaryFactor.password("password123")) XCTAssertEqual(request.bodyParameters as? [String: String], [