Skip to content

Commit 5847800

Browse files
authored
feat(auth): add signInWithSSO method (#289)
* feat(auth): add signInWithSSO method * test: add tests for signInWithSSO
1 parent 600c400 commit 5847800

File tree

5 files changed

+142
-19
lines changed

5 files changed

+142
-19
lines changed

Sources/Auth/AuthClient.swift

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,76 @@ public actor AuthClient {
457457
)
458458
}
459459

460+
/// Attempts a single-sign on using an enterprise Identity Provider.
461+
/// - Parameters:
462+
/// - domain: The email domain to use for signing in.
463+
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party
464+
/// provider.
465+
/// - captchaToken: The captcha token to be used for captcha verification.
466+
/// - Returns: A URL that you can use to initiate the provider's authentication flow.
467+
public func signInWithSSO(
468+
domain: String,
469+
redirectTo: URL? = nil,
470+
captchaToken: String? = nil
471+
) async throws -> SSOResponse {
472+
await sessionManager.remove()
473+
474+
let (codeChallenge, codeChallengeMethod) = prepareForPKCE()
475+
476+
return try await api.execute(
477+
Request(
478+
path: "/sso",
479+
method: .post,
480+
body: configuration.encoder.encode(
481+
SignInWithSSORequest(
482+
providerId: nil,
483+
domain: domain,
484+
redirectTo: redirectTo,
485+
gotrueMetaSecurity: captchaToken.map { AuthMetaSecurity(captchaToken: $0) },
486+
codeChallenge: codeChallenge,
487+
codeChallengeMethod: codeChallengeMethod
488+
)
489+
)
490+
)
491+
)
492+
.decoded(decoder: configuration.decoder)
493+
}
494+
495+
/// Attempts a single-sign on using an enterprise Identity Provider.
496+
/// - Parameters:
497+
/// - providerId: The ID of the SSO provider to use for signing in.
498+
/// - redirectTo: The URL to redirect the user to after they sign in with the third-party
499+
/// provider.
500+
/// - captchaToken: The captcha token to be used for captcha verification.
501+
/// - Returns: A URL that you can use to initiate the provider's authentication flow.
502+
public func signInWithSSO(
503+
providerId: String,
504+
redirectTo: URL? = nil,
505+
captchaToken: String? = nil
506+
) async throws -> SSOResponse {
507+
await sessionManager.remove()
508+
509+
let (codeChallenge, codeChallengeMethod) = prepareForPKCE()
510+
511+
return try await api.execute(
512+
Request(
513+
path: "/sso",
514+
method: .post,
515+
body: configuration.encoder.encode(
516+
SignInWithSSORequest(
517+
providerId: providerId,
518+
domain: nil,
519+
redirectTo: redirectTo,
520+
gotrueMetaSecurity: captchaToken.map { AuthMetaSecurity(captchaToken: $0) },
521+
codeChallenge: codeChallenge,
522+
codeChallengeMethod: codeChallengeMethod
523+
)
524+
)
525+
)
526+
)
527+
.decoded(decoder: configuration.decoder)
528+
}
529+
460530
/// Log in an existing user by exchanging an Auth Code issued during the PKCE flow.
461531
public func exchangeCodeForSession(authCode: String) async throws -> Session {
462532
guard let codeVerifier = try codeVerifierStorage.getCodeVerifier() else {
@@ -945,29 +1015,29 @@ public actor AuthClient {
9451015
}
9461016

9471017
private func prepareForPKCE() -> (codeChallenge: String?, codeChallengeMethod: String?) {
948-
if configuration.flowType == .pkce {
949-
let codeVerifier = PKCE.generateCodeVerifier()
950-
951-
do {
952-
try codeVerifierStorage.storeCodeVerifier(codeVerifier)
953-
} catch {
954-
assertionFailure(
955-
"""
956-
An error occurred while storing the code verifier,
957-
PKCE flow may not work as expected.
958-
959-
Error: \(error.localizedDescription)
960-
"""
961-
)
962-
}
1018+
guard configuration.flowType == .pkce else {
1019+
return (nil, nil)
1020+
}
1021+
1022+
let codeVerifier = PKCE.generateCodeVerifier()
9631023

964-
let codeChallenge = PKCE.generateCodeChallenge(from: codeVerifier)
965-
let codeChallengeMethod = codeVerifier == codeChallenge ? "plain" : "s256"
1024+
do {
1025+
try codeVerifierStorage.storeCodeVerifier(codeVerifier)
1026+
} catch {
1027+
assertionFailure(
1028+
"""
1029+
An error occurred while storing the code verifier,
1030+
PKCE flow may not work as expected.
9661031
967-
return (codeChallenge, codeChallengeMethod)
1032+
Error: \(error.localizedDescription)
1033+
"""
1034+
)
9681035
}
9691036

970-
return (nil, nil)
1037+
let codeChallenge = PKCE.generateCodeChallenge(from: codeVerifier)
1038+
let codeChallengeMethod = codeVerifier == codeChallenge ? "plain" : "s256"
1039+
1040+
return (codeChallenge, codeChallengeMethod)
9711041
}
9721042

9731043
private func isImplicitGrantFlow(url: URL) -> Bool {

Sources/Auth/Types.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,18 @@ public enum MessagingChannel: String, Codable, Sendable {
664664
case sms
665665
case whatsapp
666666
}
667+
668+
struct SignInWithSSORequest: Encodable {
669+
let providerId: String?
670+
let domain: String?
671+
let redirectTo: URL?
672+
let gotrueMetaSecurity: AuthMetaSecurity?
673+
let codeChallenge: String?
674+
let codeChallengeMethod: String?
675+
}
676+
677+
public struct SSOResponse: Codable, Hashable, Sendable {
678+
/// URL to open in a browser which will complete the sign-in flow by taking the user to the
679+
/// identity provider's authentication flow.
680+
public let url: URL
681+
}

Tests/AuthTests/RequestsTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,30 @@ final class RequestsTests: XCTestCase {
375375
}
376376
}
377377

378+
func testSignInWithSSOUsingDomain() async {
379+
let sut = makeSUT()
380+
381+
await assert {
382+
_ = try await sut.signInWithSSO(
383+
domain: "supabase.com",
384+
redirectTo: URL(string: "https://supabase.com"),
385+
captchaToken: "captcha-token"
386+
)
387+
}
388+
}
389+
390+
func testSignInWithSSOUsingProviderId() async {
391+
let sut = makeSUT()
392+
393+
await assert {
394+
_ = try await sut.signInWithSSO(
395+
providerId: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F",
396+
redirectTo: URL(string: "https://supabase.com"),
397+
captchaToken: "captcha-token"
398+
)
399+
}
400+
}
401+
378402
private func assert(_ block: () async throws -> Void) async {
379403
do {
380404
try await block()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
curl \
2+
--request POST \
3+
--header "Apikey: dummy.api.key" \
4+
--header "Content-Type: application/json" \
5+
--header "X-Client-Info: gotrue-swift/x.y.z" \
6+
--data "{\"domain\":\"supabase.com\",\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"redirect_to\":\"https:\/\/supabase.com\"}" \
7+
"http://localhost:54321/auth/v1/sso"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
curl \
2+
--request POST \
3+
--header "Apikey: dummy.api.key" \
4+
--header "Content-Type: application/json" \
5+
--header "X-Client-Info: gotrue-swift/x.y.z" \
6+
--data "{\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"},\"provider_id\":\"E621E1F8-C36C-495A-93FC-0C247A3E6E5F\",\"redirect_to\":\"https:\/\/supabase.com\"}" \
7+
"http://localhost:54321/auth/v1/sso"

0 commit comments

Comments
 (0)