Skip to content

Commit

Permalink
feat(auth): add signInWithSSO method (#289)
Browse files Browse the repository at this point in the history
* feat(auth): add signInWithSSO method

* test: add tests for signInWithSSO
  • Loading branch information
grdsdev authored Mar 28, 2024
1 parent 600c400 commit 5847800
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 19 deletions.
108 changes: 89 additions & 19 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,76 @@ public actor AuthClient {
)
}

/// Attempts a single-sign on using an enterprise Identity Provider.
/// - Parameters:
/// - domain: The email domain to use for signing in.
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party
/// provider.
/// - captchaToken: The captcha token to be used for captcha verification.
/// - Returns: A URL that you can use to initiate the provider's authentication flow.
public func signInWithSSO(
domain: String,
redirectTo: URL? = nil,
captchaToken: String? = nil
) async throws -> SSOResponse {
await sessionManager.remove()

let (codeChallenge, codeChallengeMethod) = prepareForPKCE()

return try await api.execute(
Request(
path: "/sso",
method: .post,
body: configuration.encoder.encode(
SignInWithSSORequest(
providerId: nil,
domain: domain,
redirectTo: redirectTo,
gotrueMetaSecurity: captchaToken.map { AuthMetaSecurity(captchaToken: $0) },
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
)
)
)
)
.decoded(decoder: configuration.decoder)
}

/// Attempts a single-sign on using an enterprise Identity Provider.
/// - Parameters:
/// - providerId: The ID of the SSO provider to use for signing in.
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party
/// provider.
/// - captchaToken: The captcha token to be used for captcha verification.
/// - Returns: A URL that you can use to initiate the provider's authentication flow.
public func signInWithSSO(
providerId: String,
redirectTo: URL? = nil,
captchaToken: String? = nil
) async throws -> SSOResponse {
await sessionManager.remove()

let (codeChallenge, codeChallengeMethod) = prepareForPKCE()

return try await api.execute(
Request(
path: "/sso",
method: .post,
body: configuration.encoder.encode(
SignInWithSSORequest(
providerId: providerId,
domain: nil,
redirectTo: redirectTo,
gotrueMetaSecurity: captchaToken.map { AuthMetaSecurity(captchaToken: $0) },
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
)
)
)
)
.decoded(decoder: configuration.decoder)
}

/// Log in an existing user by exchanging an Auth Code issued during the PKCE flow.
public func exchangeCodeForSession(authCode: String) async throws -> Session {
guard let codeVerifier = try codeVerifierStorage.getCodeVerifier() else {
Expand Down Expand Up @@ -945,29 +1015,29 @@ public actor AuthClient {
}

private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) {
if configuration.flowType == .pkce {
let codeVerifier = PKCE.generateCodeVerifier()

do {
try codeVerifierStorage.storeCodeVerifier(codeVerifier)
} catch {
assertionFailure(
"""
An error occurred while storing the code verifier,
PKCE flow may not work as expected.
Error: \(error.localizedDescription)
"""
)
}
guard configuration.flowType == .pkce else {
return (nil, nil)
}

let codeVerifier = PKCE.generateCodeVerifier()

let codeChallenge = PKCE.generateCodeChallenge(from: codeVerifier)
let codeChallengeMethod = codeVerifier == codeChallenge ? "plain" : "s256"
do {
try codeVerifierStorage.storeCodeVerifier(codeVerifier)
} catch {
assertionFailure(
"""
An error occurred while storing the code verifier,
PKCE flow may not work as expected.
return (codeChallenge, codeChallengeMethod)
Error: \(error.localizedDescription)
"""
)
}

return (nil, nil)
let codeChallenge = PKCE.generateCodeChallenge(from: codeVerifier)
let codeChallengeMethod = codeVerifier == codeChallenge ? "plain" : "s256"

return (codeChallenge, codeChallengeMethod)
}

private func isImplicitGrantFlow(url: URL) -> Bool {
Expand Down
15 changes: 15 additions & 0 deletions Sources/Auth/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,18 @@ public enum MessagingChannel: String, Codable, Sendable {
case sms
case whatsapp
}

struct SignInWithSSORequest: Encodable {
let providerId: String?
let domain: String?
let redirectTo: URL?
let gotrueMetaSecurity: AuthMetaSecurity?
let codeChallenge: String?
let codeChallengeMethod: String?
}

public struct SSOResponse: Codable, Hashable, Sendable {
/// URL to open in a browser which will complete the sign-in flow by taking the user to the
/// identity provider's authentication flow.
public let url: URL
}
24 changes: 24 additions & 0 deletions Tests/AuthTests/RequestsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,30 @@ final class RequestsTests: XCTestCase {
}
}

func testSignInWithSSOUsingDomain() async {
let sut = makeSUT()

await assert {
_ = try await sut.signInWithSSO(
domain: "supabase.com",
redirectTo: URL(string: "https://supabase.com"),
captchaToken: "captcha-token"
)
}
}

func testSignInWithSSOUsingProviderId() async {
let sut = makeSUT()

await assert {
_ = try await sut.signInWithSSO(
providerId: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F",
redirectTo: URL(string: "https://supabase.com"),
captchaToken: "captcha-token"
)
}
}

private func assert(_ block: () async throws -> Void) async {
do {
try await block()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
curl \
--request POST \
--header "Apikey: dummy.api.key" \
--header "Content-Type: application/json" \
--header "X-Client-Info: gotrue-swift/x.y.z" \
--data "{\"domain\":\"supabase.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"redirect_to\":\"https:\/\/supabase.com\"}" \
"http://localhost:54321/auth/v1/sso"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
curl \
--request POST \
--header "Apikey: dummy.api.key" \
--header "Content-Type: application/json" \
--header "X-Client-Info: gotrue-swift/x.y.z" \
--data "{\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"provider_id\":\"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\",\"redirect_to\":\"https:\/\/supabase.com\"}" \
"http://localhost:54321/auth/v1/sso"

0 comments on commit 5847800

Please sign in to comment.