Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit c250f93

Browse files
authored
feat: add sso (#358)
2 parents 0d58002 + e225504 commit c250f93

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

gotrue/_async/gotrue_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
model_dump_json,
3232
model_validate,
3333
parse_auth_response,
34+
parse_sso_response,
3435
parse_user_response,
3536
)
3637
from ..http_clients import AsyncClient
@@ -253,6 +254,63 @@ async def sign_in_with_password(
253254
self._notify_all_subscribers("SIGNED_IN", response.session)
254255
return response
255256

257+
async def sign_in_with_sso(self, credentials: SignInWithSSOCredentials):
258+
"""
259+
Attempts a single-sign on using an enterprise Identity Provider. A
260+
successful SSO attempt will redirect the current page to the identity
261+
provider authorization page. The redirect URL is implementation and SSO
262+
protocol specific.
263+
264+
You can use it by providing a SSO domain. Typically you can extract this
265+
domain by asking users for their email address. If this domain is
266+
registered on the Auth instance the redirect will use that organization's
267+
currently active SSO Identity Provider for the login.
268+
If you have built an organization-specific login page, you can use the
269+
organization's SSO Identity Provider UUID directly instead.
270+
"""
271+
self._remove_session()
272+
provider_id = credentials.get("provider_id")
273+
domain = credentials.get("domain")
274+
options = credentials.get("options", {})
275+
redirect_to = options.get("redirect_to")
276+
captcha_token = options.get("captcha_token")
277+
# HTTPX currently does not follow redirects: https://www.python-httpx.org/compatibility/
278+
# Additionally, unlike the JS client, Python is a server side language and it's not possible
279+
# to automatically redirect in browser for hte user
280+
skip_http_redirect = options.get("skip_http_redirect", True)
281+
282+
if domain:
283+
return self._request(
284+
"POST",
285+
"sso",
286+
body={
287+
"domain": domain,
288+
"skip_http_redirect": skip_http_redirect,
289+
"gotrue_meta_security": {
290+
"captcha_token": captcha_token,
291+
},
292+
},
293+
redirect_to=redirect_to,
294+
xform=parse_sso_response,
295+
)
296+
if provider_id:
297+
return self._request(
298+
"POST",
299+
"sso",
300+
body={
301+
"provider_id": provider_id,
302+
"skip_http_redirect": skip_http_redirect,
303+
"gotrue_meta_security": {
304+
"captcha_token": captcha_token,
305+
},
306+
},
307+
redirect_to=redirect_to,
308+
xform=parse_sso_response,
309+
)
310+
raise AuthInvalidCredentialsError(
311+
"You must provide either a domain or provider_id"
312+
)
313+
256314
async def sign_in_with_oauth(
257315
self,
258316
credentials: SignInWithOAuthCredentials,

gotrue/_sync/gotrue_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
model_dump_json,
3232
model_validate,
3333
parse_auth_response,
34+
parse_sso_response,
3435
parse_user_response,
3536
)
3637
from ..http_clients import SyncClient
@@ -253,6 +254,63 @@ def sign_in_with_password(
253254
self._notify_all_subscribers("SIGNED_IN", response.session)
254255
return response
255256

257+
def sign_in_with_sso(self, credentials: SignInWithSSOCredentials):
258+
"""
259+
Attempts a single-sign on using an enterprise Identity Provider. A
260+
successful SSO attempt will redirect the current page to the identity
261+
provider authorization page. The redirect URL is implementation and SSO
262+
protocol specific.
263+
264+
You can use it by providing a SSO domain. Typically you can extract this
265+
domain by asking users for their email address. If this domain is
266+
registered on the Auth instance the redirect will use that organization's
267+
currently active SSO Identity Provider for the login.
268+
If you have built an organization-specific login page, you can use the
269+
organization's SSO Identity Provider UUID directly instead.
270+
"""
271+
self._remove_session()
272+
provider_id = credentials.get("provider_id")
273+
domain = credentials.get("domain")
274+
options = credentials.get("options", {})
275+
redirect_to = options.get("redirect_to")
276+
captcha_token = options.get("captcha_token")
277+
# HTTPX currently does not follow redirects: https://www.python-httpx.org/compatibility/
278+
# Additionally, unlike the JS client, Python is a server side language and it's not possible
279+
# to automatically redirect in browser for hte user
280+
skip_http_redirect = options.get("skip_http_redirect", True)
281+
282+
if domain:
283+
return self._request(
284+
"POST",
285+
"sso",
286+
body={
287+
"domain": domain,
288+
"skip_http_redirect": skip_http_redirect,
289+
"gotrue_meta_security": {
290+
"captcha_token": captcha_token,
291+
},
292+
},
293+
redirect_to=redirect_to,
294+
xform=parse_sso_response,
295+
)
296+
if provider_id:
297+
return self._request(
298+
"POST",
299+
"sso",
300+
body={
301+
"provider_id": provider_id,
302+
"skip_http_redirect": skip_http_redirect,
303+
"gotrue_meta_security": {
304+
"captcha_token": captcha_token,
305+
},
306+
},
307+
redirect_to=redirect_to,
308+
xform=parse_sso_response,
309+
)
310+
raise AuthInvalidCredentialsError(
311+
"You must provide either a domain or provider_id"
312+
)
313+
256314
def sign_in_with_oauth(
257315
self,
258316
credentials: SignInWithOAuthCredentials,

gotrue/helpers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
GenerateLinkProperties,
1818
GenerateLinkResponse,
1919
Session,
20+
SSOResponse,
2021
User,
2122
UserResponse,
2223
)
@@ -91,6 +92,10 @@ def parse_user_response(data: Any) -> UserResponse:
9192
return model_validate(UserResponse, data)
9293

9394

95+
def parse_sso_response(data: Any) -> SSOResponse:
96+
return model_validate(SSOResponse, data)
97+
98+
9499
def get_error_message(error: Any) -> str:
95100
props = ["msg", "message", "error_description", "error"]
96101
filter = (

gotrue/types.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ class OAuthResponse(BaseModel):
9494
url: str
9595

9696

97+
class SSOResponse(BaseModel):
98+
url: str
99+
100+
97101
class UserResponse(BaseModel):
98102
user: User
99103

@@ -323,6 +327,17 @@ class SignInWithOAuthCredentials(TypedDict):
323327
options: NotRequired[SignInWithOAuthCredentialsOptions]
324328

325329

330+
class SignInWithSSOCredentials(TypedDict):
331+
provider_id: NotRequired[str]
332+
domain: NotRequired[str]
333+
options: NotRequired[SignInWithSSOOptions]
334+
335+
336+
class SignInWithSSOOptions(TypedDict):
337+
redirect_to: NotRequired[str]
338+
skip_http_redirect: NotRequired[bool]
339+
340+
326341
class VerifyOtpParamsOptions(TypedDict):
327342
redirect_to: NotRequired[str]
328343
captcha_token: NotRequired[str]

0 commit comments

Comments
 (0)