fief_client

Fief client for Python.

 1"Fief client for Python."
 2
 3from fief_client.client import (
 4    Fief,
 5    FiefAccessTokenACRTooLow,
 6    FiefAccessTokenExpired,
 7    FiefAccessTokenInfo,
 8    FiefAccessTokenInvalid,
 9    FiefAccessTokenMissingPermission,
10    FiefAccessTokenMissingScope,
11    FiefACR,
12    FiefAsync,
13    FiefError,
14    FiefIdTokenInvalid,
15    FiefRequestError,
16    FiefTokenResponse,
17    FiefUserInfo,
18)
19
20__version__ = "0.20.0"
21
22__all__ = [
23    "Fief",
24    "FiefACR",
25    "FiefAsync",
26    "FiefTokenResponse",
27    "FiefAccessTokenInfo",
28    "FiefUserInfo",
29    "FiefError",
30    "FiefAccessTokenACRTooLow",
31    "FiefAccessTokenExpired",
32    "FiefAccessTokenMissingPermission",
33    "FiefAccessTokenMissingScope",
34    "FiefAccessTokenInvalid",
35    "FiefIdTokenInvalid",
36    "FiefRequestError",
37    "crypto",
38    "pkce",
39    "integrations",
40]
class Fief(fief_client.client.BaseFief):
498class Fief(BaseFief):
499    """Sync Fief authentication client."""
500
501    def __init__(
502        self,
503        base_url: str,
504        client_id: str,
505        client_secret: Optional[str] = None,
506        *,
507        encryption_key: Optional[str] = None,
508        host: Optional[str] = None,
509        verify: VerifyTypes = True,
510        cert: Optional[CertTypes] = None,
511    ) -> None:
512        super().__init__(
513            base_url,
514            client_id,
515            client_secret,
516            encryption_key=encryption_key,
517            host=host,
518            verify=verify,
519            cert=cert,
520        )
521
522    def auth_url(
523        self,
524        redirect_uri: str,
525        *,
526        state: Optional[str] = None,
527        scope: Optional[list[str]] = None,
528        code_challenge: Optional[str] = None,
529        code_challenge_method: Optional[str] = None,
530        lang: Optional[str] = None,
531        extras_params: Optional[Mapping[str, str]] = None,
532    ) -> str:
533        """
534        Return an authorization URL.
535
536        :param redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
537        :param state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
538        :param scope: Optional list of scopes to ask for.
539        :param code_challenge: Optional code challenge for
540        [PKCE process](https://docs.fief.dev/going-further/pkce/).
541        :param code_challenge_method: Method used to hash the PKCE code challenge.
542        :param lang: Optional parameter to set the user locale.
543        Should be a valid [RFC 3066](https://www.rfc-editor.org/rfc/rfc3066) language identifier, like `fr` or `pt-PT`.
544        If not provided, the user locale is determined by their browser settings.
545        :param extras_params: Optional dictionary containing [specific parameters](https://docs.fief.dev/going-further/authorize-url/).
546
547        **Example:**
548
549        ```py
550        auth_url = fief.auth_url("http://localhost:8000/callback", scope=["openid"])
551        ```
552        """
553        openid_configuration = self._get_openid_configuration()
554        return self._auth_url(
555            openid_configuration,
556            redirect_uri,
557            state=state,
558            scope=scope,
559            code_challenge=code_challenge,
560            code_challenge_method=code_challenge_method,
561            lang=lang,
562            extras_params=extras_params,
563        )
564
565    def auth_callback(
566        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
567    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
568        """
569        Return a `FiefTokenResponse` and `FiefUserInfo` in exchange of an authorization code.
570
571        :param code: The authorization code.
572        :param redirect_uri: The exact same `redirect_uri` you passed to the authorization URL.
573        :param code_verifier:  The raw
574        [PKCE](https://docs.fief.dev/going-further/pkce/) code used to generate the code challenge during authorization.
575
576        **Example:**
577
578        ```py
579        tokens, userinfo = fief.auth_callback("CODE", "http://localhost:8000/callback")
580        ```
581        """
582        token_response = self._auth_exchange_token(
583            code, redirect_uri, code_verifier=code_verifier
584        )
585        jwks = self._get_jwks()
586        userinfo = self._decode_id_token(
587            token_response["id_token"],
588            jwks,
589            code=code,
590            access_token=token_response.get("access_token"),
591        )
592        return token_response, userinfo
593
594    def auth_refresh_token(
595        self, refresh_token: str, *, scope: Optional[list[str]] = None
596    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
597        """
598        Return fresh `FiefTokenResponse` and `FiefUserInfo` in exchange of a refresh token
599
600        :param refresh_token: A valid refresh token.
601        :param scope: Optional list of scopes to ask for.
602        If not provided, the access token will share the same list of scopes as requested the first time.
603        Otherwise, it should be a subset of the original list of scopes.
604
605        **Example:**
606
607        ```py
608        tokens, userinfo = fief.auth_refresh_token("REFRESH_TOKEN")
609        ```
610        """
611        token_endpoint = self._get_endpoint_url(
612            self._get_openid_configuration(), "token_endpoint"
613        )
614        with self._get_httpx_client() as client:
615            request = self._get_auth_refresh_token_request(
616                client,
617                endpoint=token_endpoint,
618                refresh_token=refresh_token,
619                scope=scope,
620            )
621            response = client.send(request)
622
623            self._handle_request_error(response)
624
625            token_response = response.json()
626        jwks = self._get_jwks()
627        userinfo = self._decode_id_token(
628            token_response["id_token"],
629            jwks,
630            access_token=token_response.get("access_token"),
631        )
632        return token_response, userinfo
633
634    def validate_access_token(
635        self,
636        access_token: str,
637        *,
638        required_scope: Optional[list[str]] = None,
639        required_acr: Optional[FiefACR] = None,
640        required_permissions: Optional[list[str]] = None,
641    ) -> FiefAccessTokenInfo:
642        """
643        Check if an access token is valid and optionally that it has a required list of scopes,
644        or a required list of [permissions](https://docs.fief.dev/getting-started/access-control/).
645        Returns a `FiefAccessTokenInfo`.
646
647        :param access_token: The access token to validate.
648        :param required_scope: Optional list of scopes to check for.
649        :param required_acr: Optional minimum ACR level required.
650        Read more: https://docs.fief.dev/going-further/acr/
651        :param required_permissions: Optional list of permissions to check for.
652
653        **Example: Validate access token with required scopes**
654
655        ```py
656        try:
657            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
658        except FiefAccessTokenInvalid:
659            print("Invalid access token")
660        except FiefAccessTokenExpired:
661            print("Expired access token")
662        except FiefAccessTokenMissingScope:
663            print("Missing required scope")
664
665        print(access_token_info)
666        ```
667
668        **Example: Validate access token with minimum ACR level**
669
670        ```py
671        try:
672            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
673        except FiefAccessTokenInvalid:
674            print("Invalid access token")
675        except FiefAccessTokenExpired:
676            print("Expired access token")
677        except FiefAccessTokenACRTooLow:
678            print("ACR too low")
679
680        print(access_token_info)
681        ```
682
683        **Example: Validate access token with required permissions**
684
685        ```py
686        try:
687            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
688        except FiefAccessTokenInvalid:
689            print("Invalid access token")
690        except FiefAccessTokenExpired:
691            print("Expired access token")
692        except FiefAccessTokenMissingPermission:
693            print("Missing required permission")
694
695        print(access_token_info)
696        ```
697        """
698        jwks = self._get_jwks()
699        return self._validate_access_token(
700            access_token,
701            jwks,
702            required_scope=required_scope,
703            required_acr=required_acr,
704            required_permissions=required_permissions,
705        )
706
707    def userinfo(self, access_token: str) -> FiefUserInfo:
708        """
709        Return fresh `FiefUserInfo` from the Fief API using a valid access token.
710
711        :param access_token: A valid access token.
712
713        **Example:**
714
715        ```py
716        userinfo = fief.userinfo("ACCESS_TOKEN")
717        ```
718        """
719        userinfo_endpoint = self._get_endpoint_url(
720            self._get_openid_configuration(), "userinfo_endpoint"
721        )
722        with self._get_httpx_client() as client:
723            request = self._get_userinfo_request(
724                client, endpoint=userinfo_endpoint, access_token=access_token
725            )
726            response = client.send(request)
727
728            self._handle_request_error(response)
729
730            return response.json()
731
732    def update_profile(self, access_token: str, data: dict[str, Any]) -> FiefUserInfo:
733        """
734        Update user information with the Fief API using a valid access token.
735
736        :param access_token: A valid access token.
737        :param data: A dictionary containing the data to update.
738
739        **Example: Update user field**
740
741        To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
742
743        ```py
744        userinfo = fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
745        ```
746        """
747        update_profile_endpoint = f"{self.base_url}/api/profile"
748
749        with self._get_httpx_client() as client:
750            request = self._get_update_profile_request(
751                client,
752                endpoint=update_profile_endpoint,
753                access_token=access_token,
754                data=data,
755            )
756            response = client.send(request)
757
758            self._handle_request_error(response)
759
760            return response.json()
761
762    def change_password(self, access_token: str, new_password: str) -> FiefUserInfo:
763        """
764        Change the user password with the Fief API using a valid access token.
765
766        **An access token with an ACR of at least level 1 is required.**
767
768        :param access_token: A valid access token.
769        :param new_password: The new password.
770
771        **Example**
772
773        ```py
774        userinfo = fief.change_password("ACCESS_TOKEN", "herminetincture")
775        ```
776        """
777        change_password_profile_endpoint = f"{self.base_url}/api/password"
778
779        with self._get_httpx_client() as client:
780            request = self._get_change_password_request(
781                client,
782                endpoint=change_password_profile_endpoint,
783                access_token=access_token,
784                new_password=new_password,
785            )
786            response = client.send(request)
787
788            self._handle_request_error(response)
789
790            return response.json()
791
792    def email_change(self, access_token: str, email: str) -> FiefUserInfo:
793        """
794        Request an email change with the Fief API using a valid access token.
795
796        The user will receive a verification code on this new email address.
797        It shall be used with the method `email_verify` to complete the modification.
798
799        **An access token with an ACR of at least level 1 is required.**
800
801        :param access_token: A valid access token.
802        :param email: The new email address.
803
804        **Example**
805
806        ```py
807        userinfo = fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
808        ```
809        """
810        email_change_endpoint = f"{self.base_url}/api/email/change"
811
812        with self._get_httpx_client() as client:
813            request = self._get_email_change_request(
814                client,
815                endpoint=email_change_endpoint,
816                access_token=access_token,
817                email=email,
818            )
819            response = client.send(request)
820
821            self._handle_request_error(response)
822
823            return response.json()
824
825    def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
826        """
827        Verify the user email with the Fief API using a valid access token and verification code.
828
829        **An access token with an ACR of at least level 1 is required.**
830
831        :param access_token: A valid access token.
832        :param code: The verification code received by email.
833
834        **Example**
835
836        ```py
837        userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
838        ```
839        """
840        email_verify_endpoint = f"{self.base_url}/api/email/verify"
841
842        with self._get_httpx_client() as client:
843            request = self._get_email_verify_request(
844                client,
845                endpoint=email_verify_endpoint,
846                access_token=access_token,
847                code=code,
848            )
849            response = client.send(request)
850
851            self._handle_request_error(response)
852
853            return response.json()
854
855    def logout_url(self, redirect_uri: str) -> str:
856        """
857        Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.
858
859        **You're still responsible for clearing your own session mechanism if any.**
860
861        :param redirect_uri: A valid URL where the user will be redirected after the logout process.
862
863        **Example:**
864
865        ```py
866        logout_url = fief.logout_url("http://localhost:8000")
867        ```
868        """
869        params = {"redirect_uri": redirect_uri}
870        return f"{self.base_url}/logout?{urlencode(params)}"
871
872    @contextlib.contextmanager
873    def _get_httpx_client(self):
874        headers = {}
875        if self.host is not None:
876            headers["Host"] = self.host
877
878        with httpx.Client(
879            base_url=self.base_url, headers=headers, verify=self.verify, cert=self.cert
880        ) as client:
881            yield client
882
883    def _get_openid_configuration(self) -> dict[str, Any]:
884        if self._openid_configuration is not None:
885            return self._openid_configuration
886
887        with self._get_httpx_client() as client:
888            request = self._get_openid_configuration_request(client)
889            response = client.send(request)
890            json = response.json()
891            self._openid_configuration = json
892            return json
893
894    def _get_jwks(self) -> jwk.JWKSet:
895        if self._jwks is not None:
896            return self._jwks
897
898        jwks_uri = self._get_endpoint_url(self._get_openid_configuration(), "jwks_uri")
899        with self._get_httpx_client() as client:
900            response = client.get(jwks_uri)
901            self._jwks = jwk.JWKSet.from_json(response.text)
902            return self._jwks
903
904    def _auth_exchange_token(
905        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
906    ) -> FiefTokenResponse:
907        token_endpoint = self._get_endpoint_url(
908            self._get_openid_configuration(), "token_endpoint"
909        )
910        with self._get_httpx_client() as client:
911            request = self._get_auth_exchange_token_request(
912                client,
913                endpoint=token_endpoint,
914                code=code,
915                redirect_uri=redirect_uri,
916                code_verifier=code_verifier,
917            )
918            response = client.send(request)
919
920            self._handle_request_error(response)
921
922            return response.json()

Sync Fief authentication client.

Fief( base_url: str, client_id: str, client_secret: Optional[str] = None, *, encryption_key: Optional[str] = None, host: Optional[str] = None, verify: Union[str, bool, ssl.SSLContext] = True, cert: Union[str, Tuple[str, Optional[str]], Tuple[str, Optional[str], Optional[str]], NoneType] = None)
501    def __init__(
502        self,
503        base_url: str,
504        client_id: str,
505        client_secret: Optional[str] = None,
506        *,
507        encryption_key: Optional[str] = None,
508        host: Optional[str] = None,
509        verify: VerifyTypes = True,
510        cert: Optional[CertTypes] = None,
511    ) -> None:
512        super().__init__(
513            base_url,
514            client_id,
515            client_secret,
516            encryption_key=encryption_key,
517            host=host,
518            verify=verify,
519            cert=cert,
520        )

Initialize the client.

Parameters
  • base_url: Base URL of your Fief tenant.
  • client_id: ID of your Fief client.
  • client_secret: Secret of your Fief client. If you're implementing a desktop app, it's not recommended to use it, since it can be easily found by the end-user in the source code. The recommended way is to use a Public client.
  • encryption_key: Encryption key of your Fief client. Necessary only if ID Token encryption is enabled.
  • **verify: Corresponds to the verify parameter of HTTPX. Useful to customize SSL connection handling.
  • **cert: Corresponds to the cert parameter of HTTPX. Useful to customize SSL connection handling.
def auth_url( self, redirect_uri: str, *, state: Optional[str] = None, scope: Optional[list[str]] = None, code_challenge: Optional[str] = None, code_challenge_method: Optional[str] = None, lang: Optional[str] = None, extras_params: Optional[Mapping[str, str]] = None) -> str:
522    def auth_url(
523        self,
524        redirect_uri: str,
525        *,
526        state: Optional[str] = None,
527        scope: Optional[list[str]] = None,
528        code_challenge: Optional[str] = None,
529        code_challenge_method: Optional[str] = None,
530        lang: Optional[str] = None,
531        extras_params: Optional[Mapping[str, str]] = None,
532    ) -> str:
533        """
534        Return an authorization URL.
535
536        :param redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
537        :param state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
538        :param scope: Optional list of scopes to ask for.
539        :param code_challenge: Optional code challenge for
540        [PKCE process](https://docs.fief.dev/going-further/pkce/).
541        :param code_challenge_method: Method used to hash the PKCE code challenge.
542        :param lang: Optional parameter to set the user locale.
543        Should be a valid [RFC 3066](https://www.rfc-editor.org/rfc/rfc3066) language identifier, like `fr` or `pt-PT`.
544        If not provided, the user locale is determined by their browser settings.
545        :param extras_params: Optional dictionary containing [specific parameters](https://docs.fief.dev/going-further/authorize-url/).
546
547        **Example:**
548
549        ```py
550        auth_url = fief.auth_url("http://localhost:8000/callback", scope=["openid"])
551        ```
552        """
553        openid_configuration = self._get_openid_configuration()
554        return self._auth_url(
555            openid_configuration,
556            redirect_uri,
557            state=state,
558            scope=scope,
559            code_challenge=code_challenge,
560            code_challenge_method=code_challenge_method,
561            lang=lang,
562            extras_params=extras_params,
563        )

Return an authorization URL.

Parameters
  • redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
  • state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
  • scope: Optional list of scopes to ask for.
  • code_challenge: Optional code challenge for PKCE process.
  • code_challenge_method: Method used to hash the PKCE code challenge.
  • lang: Optional parameter to set the user locale. Should be a valid RFC 3066 language identifier, like fr or pt-PT. If not provided, the user locale is determined by their browser settings.
  • **extras_params: Optional dictionary containing specific parameters.

Example:

auth_url = fief.auth_url("http://localhost:8000/callback", scope=["openid"])
def auth_callback( self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None) -> tuple[FiefTokenResponse, FiefUserInfo]:
565    def auth_callback(
566        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
567    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
568        """
569        Return a `FiefTokenResponse` and `FiefUserInfo` in exchange of an authorization code.
570
571        :param code: The authorization code.
572        :param redirect_uri: The exact same `redirect_uri` you passed to the authorization URL.
573        :param code_verifier:  The raw
574        [PKCE](https://docs.fief.dev/going-further/pkce/) code used to generate the code challenge during authorization.
575
576        **Example:**
577
578        ```py
579        tokens, userinfo = fief.auth_callback("CODE", "http://localhost:8000/callback")
580        ```
581        """
582        token_response = self._auth_exchange_token(
583            code, redirect_uri, code_verifier=code_verifier
584        )
585        jwks = self._get_jwks()
586        userinfo = self._decode_id_token(
587            token_response["id_token"],
588            jwks,
589            code=code,
590            access_token=token_response.get("access_token"),
591        )
592        return token_response, userinfo

Return a FiefTokenResponse and FiefUserInfo in exchange of an authorization code.

Parameters
  • code: The authorization code.
  • redirect_uri: The exact same redirect_uri you passed to the authorization URL.
  • code_verifier: The raw PKCE code used to generate the code challenge during authorization.

Example:

tokens, userinfo = fief.auth_callback("CODE", "http://localhost:8000/callback")
def auth_refresh_token( self, refresh_token: str, *, scope: Optional[list[str]] = None) -> tuple[FiefTokenResponse, FiefUserInfo]:
594    def auth_refresh_token(
595        self, refresh_token: str, *, scope: Optional[list[str]] = None
596    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
597        """
598        Return fresh `FiefTokenResponse` and `FiefUserInfo` in exchange of a refresh token
599
600        :param refresh_token: A valid refresh token.
601        :param scope: Optional list of scopes to ask for.
602        If not provided, the access token will share the same list of scopes as requested the first time.
603        Otherwise, it should be a subset of the original list of scopes.
604
605        **Example:**
606
607        ```py
608        tokens, userinfo = fief.auth_refresh_token("REFRESH_TOKEN")
609        ```
610        """
611        token_endpoint = self._get_endpoint_url(
612            self._get_openid_configuration(), "token_endpoint"
613        )
614        with self._get_httpx_client() as client:
615            request = self._get_auth_refresh_token_request(
616                client,
617                endpoint=token_endpoint,
618                refresh_token=refresh_token,
619                scope=scope,
620            )
621            response = client.send(request)
622
623            self._handle_request_error(response)
624
625            token_response = response.json()
626        jwks = self._get_jwks()
627        userinfo = self._decode_id_token(
628            token_response["id_token"],
629            jwks,
630            access_token=token_response.get("access_token"),
631        )
632        return token_response, userinfo

Return fresh FiefTokenResponse and FiefUserInfo in exchange of a refresh token

Parameters
  • refresh_token: A valid refresh token.
  • scope: Optional list of scopes to ask for. If not provided, the access token will share the same list of scopes as requested the first time. Otherwise, it should be a subset of the original list of scopes.

Example:

tokens, userinfo = fief.auth_refresh_token("REFRESH_TOKEN")
def validate_access_token( self, access_token: str, *, required_scope: Optional[list[str]] = None, required_acr: Optional[FiefACR] = None, required_permissions: Optional[list[str]] = None) -> FiefAccessTokenInfo:
634    def validate_access_token(
635        self,
636        access_token: str,
637        *,
638        required_scope: Optional[list[str]] = None,
639        required_acr: Optional[FiefACR] = None,
640        required_permissions: Optional[list[str]] = None,
641    ) -> FiefAccessTokenInfo:
642        """
643        Check if an access token is valid and optionally that it has a required list of scopes,
644        or a required list of [permissions](https://docs.fief.dev/getting-started/access-control/).
645        Returns a `FiefAccessTokenInfo`.
646
647        :param access_token: The access token to validate.
648        :param required_scope: Optional list of scopes to check for.
649        :param required_acr: Optional minimum ACR level required.
650        Read more: https://docs.fief.dev/going-further/acr/
651        :param required_permissions: Optional list of permissions to check for.
652
653        **Example: Validate access token with required scopes**
654
655        ```py
656        try:
657            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
658        except FiefAccessTokenInvalid:
659            print("Invalid access token")
660        except FiefAccessTokenExpired:
661            print("Expired access token")
662        except FiefAccessTokenMissingScope:
663            print("Missing required scope")
664
665        print(access_token_info)
666        ```
667
668        **Example: Validate access token with minimum ACR level**
669
670        ```py
671        try:
672            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
673        except FiefAccessTokenInvalid:
674            print("Invalid access token")
675        except FiefAccessTokenExpired:
676            print("Expired access token")
677        except FiefAccessTokenACRTooLow:
678            print("ACR too low")
679
680        print(access_token_info)
681        ```
682
683        **Example: Validate access token with required permissions**
684
685        ```py
686        try:
687            access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
688        except FiefAccessTokenInvalid:
689            print("Invalid access token")
690        except FiefAccessTokenExpired:
691            print("Expired access token")
692        except FiefAccessTokenMissingPermission:
693            print("Missing required permission")
694
695        print(access_token_info)
696        ```
697        """
698        jwks = self._get_jwks()
699        return self._validate_access_token(
700            access_token,
701            jwks,
702            required_scope=required_scope,
703            required_acr=required_acr,
704            required_permissions=required_permissions,
705        )

Check if an access token is valid and optionally that it has a required list of scopes, or a required list of permissions. Returns a FiefAccessTokenInfo.

Parameters
  • access_token: The access token to validate.
  • required_scope: Optional list of scopes to check for.
  • required_acr: Optional minimum ACR level required. Read more: https://docs.fief.dev/going-further/acr/
  • required_permissions: Optional list of permissions to check for.

Example: Validate access token with required scopes

try:
    access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenMissingScope:
    print("Missing required scope")

print(access_token_info)

Example: Validate access token with minimum ACR level

try:
    access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenACRTooLow:
    print("ACR too low")

print(access_token_info)

Example: Validate access token with required permissions

try:
    access_token_info = fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenMissingPermission:
    print("Missing required permission")

print(access_token_info)
def userinfo(self, access_token: str) -> FiefUserInfo:
707    def userinfo(self, access_token: str) -> FiefUserInfo:
708        """
709        Return fresh `FiefUserInfo` from the Fief API using a valid access token.
710
711        :param access_token: A valid access token.
712
713        **Example:**
714
715        ```py
716        userinfo = fief.userinfo("ACCESS_TOKEN")
717        ```
718        """
719        userinfo_endpoint = self._get_endpoint_url(
720            self._get_openid_configuration(), "userinfo_endpoint"
721        )
722        with self._get_httpx_client() as client:
723            request = self._get_userinfo_request(
724                client, endpoint=userinfo_endpoint, access_token=access_token
725            )
726            response = client.send(request)
727
728            self._handle_request_error(response)
729
730            return response.json()

Return fresh FiefUserInfo from the Fief API using a valid access token.

Parameters
  • access_token: A valid access token.

Example:

userinfo = fief.userinfo("ACCESS_TOKEN")
def update_profile( self, access_token: str, data: dict[str, typing.Any]) -> FiefUserInfo:
732    def update_profile(self, access_token: str, data: dict[str, Any]) -> FiefUserInfo:
733        """
734        Update user information with the Fief API using a valid access token.
735
736        :param access_token: A valid access token.
737        :param data: A dictionary containing the data to update.
738
739        **Example: Update user field**
740
741        To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
742
743        ```py
744        userinfo = fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
745        ```
746        """
747        update_profile_endpoint = f"{self.base_url}/api/profile"
748
749        with self._get_httpx_client() as client:
750            request = self._get_update_profile_request(
751                client,
752                endpoint=update_profile_endpoint,
753                access_token=access_token,
754                data=data,
755            )
756            response = client.send(request)
757
758            self._handle_request_error(response)
759
760            return response.json()

Update user information with the Fief API using a valid access token.

Parameters
  • access_token: A valid access token.
  • data: A dictionary containing the data to update.

Example: Update user field

To update user field values, you need to nest them into a fields dictionary, indexed by their slug.

userinfo = fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
def change_password( self, access_token: str, new_password: str) -> FiefUserInfo:
762    def change_password(self, access_token: str, new_password: str) -> FiefUserInfo:
763        """
764        Change the user password with the Fief API using a valid access token.
765
766        **An access token with an ACR of at least level 1 is required.**
767
768        :param access_token: A valid access token.
769        :param new_password: The new password.
770
771        **Example**
772
773        ```py
774        userinfo = fief.change_password("ACCESS_TOKEN", "herminetincture")
775        ```
776        """
777        change_password_profile_endpoint = f"{self.base_url}/api/password"
778
779        with self._get_httpx_client() as client:
780            request = self._get_change_password_request(
781                client,
782                endpoint=change_password_profile_endpoint,
783                access_token=access_token,
784                new_password=new_password,
785            )
786            response = client.send(request)
787
788            self._handle_request_error(response)
789
790            return response.json()

Change the user password with the Fief API using a valid access token.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • new_password: The new password.

Example

userinfo = fief.change_password("ACCESS_TOKEN", "herminetincture")
def email_change(self, access_token: str, email: str) -> FiefUserInfo:
792    def email_change(self, access_token: str, email: str) -> FiefUserInfo:
793        """
794        Request an email change with the Fief API using a valid access token.
795
796        The user will receive a verification code on this new email address.
797        It shall be used with the method `email_verify` to complete the modification.
798
799        **An access token with an ACR of at least level 1 is required.**
800
801        :param access_token: A valid access token.
802        :param email: The new email address.
803
804        **Example**
805
806        ```py
807        userinfo = fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
808        ```
809        """
810        email_change_endpoint = f"{self.base_url}/api/email/change"
811
812        with self._get_httpx_client() as client:
813            request = self._get_email_change_request(
814                client,
815                endpoint=email_change_endpoint,
816                access_token=access_token,
817                email=email,
818            )
819            response = client.send(request)
820
821            self._handle_request_error(response)
822
823            return response.json()

Request an email change with the Fief API using a valid access token.

The user will receive a verification code on this new email address. It shall be used with the method email_verify to complete the modification.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • email: The new email address.

Example

userinfo = fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
825    def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
826        """
827        Verify the user email with the Fief API using a valid access token and verification code.
828
829        **An access token with an ACR of at least level 1 is required.**
830
831        :param access_token: A valid access token.
832        :param code: The verification code received by email.
833
834        **Example**
835
836        ```py
837        userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
838        ```
839        """
840        email_verify_endpoint = f"{self.base_url}/api/email/verify"
841
842        with self._get_httpx_client() as client:
843            request = self._get_email_verify_request(
844                client,
845                endpoint=email_verify_endpoint,
846                access_token=access_token,
847                code=code,
848            )
849            response = client.send(request)
850
851            self._handle_request_error(response)
852
853            return response.json()

Verify the user email with the Fief API using a valid access token and verification code.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • code: The verification code received by email.

Example

userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
def logout_url(self, redirect_uri: str) -> str:
855    def logout_url(self, redirect_uri: str) -> str:
856        """
857        Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.
858
859        **You're still responsible for clearing your own session mechanism if any.**
860
861        :param redirect_uri: A valid URL where the user will be redirected after the logout process.
862
863        **Example:**
864
865        ```py
866        logout_url = fief.logout_url("http://localhost:8000")
867        ```
868        """
869        params = {"redirect_uri": redirect_uri}
870        return f"{self.base_url}/logout?{urlencode(params)}"

Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.

You're still responsible for clearing your own session mechanism if any.

Parameters
  • redirect_uri: A valid URL where the user will be redirected after the logout process.

Example:

logout_url = fief.logout_url("http://localhost:8000")
class FiefACR(builtins.str, enum.Enum):
19class FiefACR(str, Enum):
20    """
21    List of defined Authentication Context Class Reference.
22    """
23
24    LEVEL_ZERO = "0"
25    """Level 0. No authentication was performed, a previous session was used."""
26    LEVEL_ONE = "1"
27    """Level 1. Password authentication was performed."""
28
29    def __lt__(self, other: object) -> bool:
30        return self._compare(other, True, True)
31
32    def __le__(self, other: object) -> bool:
33        return self._compare(other, False, True)
34
35    def __gt__(self, other: object) -> bool:
36        return self._compare(other, True, False)
37
38    def __ge__(self, other: object) -> bool:
39        return self._compare(other, False, False)
40
41    def _compare(self, other: object, strict: bool, asc: bool) -> bool:
42        if not isinstance(other, FiefACR):
43            return NotImplemented  # pragma: no cover
44
45        if self == other:
46            return not strict
47
48        for elem in FiefACR:
49            if self == elem:
50                return asc
51            elif other == elem:
52                return not asc
53        raise RuntimeError()  # pragma: no cover

List of defined Authentication Context Class Reference.

LEVEL_ZERO = <FiefACR.LEVEL_ZERO: '0'>

Level 0. No authentication was performed, a previous session was used.

LEVEL_ONE = <FiefACR.LEVEL_ONE: '1'>

Level 1. Password authentication was performed.

class FiefAsync(fief_client.client.BaseFief):
 925class FiefAsync(BaseFief):
 926    """Async Fief authentication client."""
 927
 928    def __init__(
 929        self,
 930        base_url: str,
 931        client_id: str,
 932        client_secret: Optional[str] = None,
 933        *,
 934        encryption_key: Optional[str] = None,
 935        host: Optional[str] = None,
 936        verify: VerifyTypes = True,
 937        cert: Optional[CertTypes] = None,
 938    ) -> None:
 939        super().__init__(
 940            base_url,
 941            client_id,
 942            client_secret,
 943            encryption_key=encryption_key,
 944            host=host,
 945            verify=verify,
 946            cert=cert,
 947        )
 948
 949    async def auth_url(
 950        self,
 951        redirect_uri: str,
 952        *,
 953        state: Optional[str] = None,
 954        scope: Optional[list[str]] = None,
 955        code_challenge: Optional[str] = None,
 956        code_challenge_method: Optional[str] = None,
 957        lang: Optional[str] = None,
 958        extras_params: Optional[Mapping[str, str]] = None,
 959    ) -> str:
 960        """
 961        Return an authorization URL.
 962
 963        :param redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
 964        :param state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
 965        :param scope: Optional list of scopes to ask for.
 966        :param code_challenge: Optional code challenge for
 967        [PKCE process](https://docs.fief.dev/going-further/pkce/).
 968        :param code_challenge_method: Method used to hash the PKCE code challenge.
 969        :param lang: Optional parameter to set the user locale.
 970        Should be a valid [RFC 3066](https://www.rfc-editor.org/rfc/rfc3066) language identifier, like `fr` or `pt-PT`.
 971        If not provided, the user locale is determined by their browser settings.
 972        :param extras_params: Optional dictionary containing [specific parameters](https://docs.fief.dev/going-further/authorize-url/).
 973
 974        **Example:**
 975
 976        ```py
 977        auth_url = await fief.auth_url("http://localhost:8000/callback", scope=["openid"])
 978        ```
 979        """
 980        openid_configuration = await self._get_openid_configuration()
 981        return self._auth_url(
 982            openid_configuration,
 983            redirect_uri,
 984            state=state,
 985            scope=scope,
 986            code_challenge=code_challenge,
 987            code_challenge_method=code_challenge_method,
 988            lang=lang,
 989            extras_params=extras_params,
 990        )
 991
 992    async def auth_callback(
 993        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
 994    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
 995        """
 996        Return a `FiefTokenResponse` and `FiefUserInfo` in exchange of an authorization code.
 997
 998        :param code: The authorization code.
 999        :param redirect_uri: The exact same `redirect_uri` you passed to the authorization URL.
1000        :param code_verifier:  The raw
1001        [PKCE](https://docs.fief.dev/going-further/pkce/) code used to generate the code challenge during authorization.
1002
1003        **Example:**
1004
1005        ```py
1006        tokens, userinfo = await fief.auth_callback("CODE", "http://localhost:8000/callback")
1007        ```
1008        """
1009        token_response = await self._auth_exchange_token(
1010            code, redirect_uri, code_verifier=code_verifier
1011        )
1012        jwks = await self._get_jwks()
1013        userinfo = self._decode_id_token(
1014            token_response["id_token"],
1015            jwks,
1016            code=code,
1017            access_token=token_response.get("access_token"),
1018        )
1019        return token_response, userinfo
1020
1021    async def auth_refresh_token(
1022        self, refresh_token: str, *, scope: Optional[list[str]] = None
1023    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
1024        """
1025        Return fresh `FiefTokenResponse` and `FiefUserInfo` in exchange of a refresh token
1026
1027        :param refresh_token: A valid refresh token.
1028        :param scope: Optional list of scopes to ask for.
1029        If not provided, the access token will share the same list of scopes as requested the first time.
1030        Otherwise, it should be a subset of the original list of scopes.
1031
1032        **Example:**
1033
1034        ```py
1035        tokens, userinfo = await fief.auth_refresh_token("REFRESH_TOKEN")
1036        ```
1037        """
1038        token_endpoint = self._get_endpoint_url(
1039            await self._get_openid_configuration(), "token_endpoint"
1040        )
1041        async with self._get_httpx_client() as client:
1042            request = self._get_auth_refresh_token_request(
1043                client,
1044                endpoint=token_endpoint,
1045                refresh_token=refresh_token,
1046                scope=scope,
1047            )
1048            response = await client.send(request)
1049
1050            self._handle_request_error(response)
1051
1052            token_response = response.json()
1053
1054        jwks = await self._get_jwks()
1055        userinfo = self._decode_id_token(
1056            token_response["id_token"],
1057            jwks,
1058            access_token=token_response.get("access_token"),
1059        )
1060        return token_response, userinfo
1061
1062    async def validate_access_token(
1063        self,
1064        access_token: str,
1065        *,
1066        required_scope: Optional[list[str]] = None,
1067        required_acr: Optional[FiefACR] = None,
1068        required_permissions: Optional[list[str]] = None,
1069    ) -> FiefAccessTokenInfo:
1070        """
1071        Check if an access token is valid and optionally that it has a required list of scopes,
1072        or a required list of [permissions](https://docs.fief.dev/getting-started/access-control/).
1073        Returns a `FiefAccessTokenInfo`.
1074
1075        :param access_token: The access token to validate.
1076        :param required_scope: Optional list of scopes to check for.
1077        :param required_acr: Optional minimum ACR level required.
1078        Read more: https://docs.fief.dev/going-further/acr/
1079        :param required_permissions: Optional list of permissions to check for.
1080
1081        **Example: Validate access token with required scopes**
1082
1083        ```py
1084        try:
1085            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
1086        except FiefAccessTokenInvalid:
1087            print("Invalid access token")
1088        except FiefAccessTokenExpired:
1089            print("Expired access token")
1090        except FiefAccessTokenMissingScope:
1091            print("Missing required scope")
1092
1093        print(access_token_info)
1094        ```
1095
1096        **Example: Validate access token with minimum ACR level**
1097
1098        ```py
1099        try:
1100            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
1101        except FiefAccessTokenInvalid:
1102            print("Invalid access token")
1103        except FiefAccessTokenExpired:
1104            print("Expired access token")
1105        except FiefAccessTokenACRTooLow:
1106            print("ACR too low")
1107
1108        print(access_token_info)
1109        ```
1110
1111        **Example: Validate access token with required permissions**
1112
1113        ```py
1114        try:
1115            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
1116        except FiefAccessTokenInvalid:
1117            print("Invalid access token")
1118        except FiefAccessTokenExpired:
1119            print("Expired access token")
1120        except FiefAccessTokenMissingPermission:
1121            print("Missing required permission")
1122
1123        print(access_token_info)
1124        ```
1125        """
1126        jwks = await self._get_jwks()
1127        return self._validate_access_token(
1128            access_token,
1129            jwks,
1130            required_scope=required_scope,
1131            required_acr=required_acr,
1132            required_permissions=required_permissions,
1133        )
1134
1135    async def userinfo(self, access_token: str) -> FiefUserInfo:
1136        """
1137        Return fresh `FiefUserInfo` from the Fief API using a valid access token.
1138
1139        :param access_token: A valid access token.
1140
1141        **Example:**
1142
1143        ```py
1144        userinfo = await fief.userinfo("ACCESS_TOKEN")
1145        ```
1146        """
1147        userinfo_endpoint = self._get_endpoint_url(
1148            await self._get_openid_configuration(), "userinfo_endpoint"
1149        )
1150        async with self._get_httpx_client() as client:
1151            request = self._get_userinfo_request(
1152                client, endpoint=userinfo_endpoint, access_token=access_token
1153            )
1154            response = await client.send(request)
1155
1156            self._handle_request_error(response)
1157
1158            return response.json()
1159
1160    async def update_profile(
1161        self, access_token: str, data: dict[str, Any]
1162    ) -> FiefUserInfo:
1163        """
1164        Update user information with the Fief API using a valid access token.
1165
1166        :param access_token: A valid access token.
1167        :param data: A dictionary containing the data to update.
1168
1169        **Example: Update user field**
1170
1171        To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
1172
1173        ```py
1174        userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
1175        ```
1176        """
1177        update_profile_endpoint = f"{self.base_url}/api/profile"
1178
1179        async with self._get_httpx_client() as client:
1180            request = self._get_update_profile_request(
1181                client,
1182                endpoint=update_profile_endpoint,
1183                access_token=access_token,
1184                data=data,
1185            )
1186            response = await client.send(request)
1187
1188            self._handle_request_error(response)
1189
1190            return response.json()
1191
1192    async def change_password(
1193        self, access_token: str, new_password: str
1194    ) -> FiefUserInfo:
1195        """
1196        Change the user password with the Fief API using a valid access token.
1197
1198        **An access token with an ACR of at least level 1 is required.**
1199
1200        :param access_token: A valid access token.
1201        :param new_password: The new password.
1202
1203        **Example**
1204
1205        ```py
1206        userinfo = await fief.change_password("ACCESS_TOKEN", "herminetincture")
1207        ```
1208        """
1209        change_password_profile_endpoint = f"{self.base_url}/api/password"
1210
1211        async with self._get_httpx_client() as client:
1212            request = self._get_change_password_request(
1213                client,
1214                endpoint=change_password_profile_endpoint,
1215                access_token=access_token,
1216                new_password=new_password,
1217            )
1218            response = await client.send(request)
1219
1220            self._handle_request_error(response)
1221
1222            return response.json()
1223
1224    async def email_change(self, access_token: str, email: str) -> FiefUserInfo:
1225        """
1226        Request an email change with the Fief API using a valid access token.
1227
1228        The user will receive a verification code on this new email address.
1229        It shall be used with the method `email_verify` to complete the modification.
1230
1231        **An access token with an ACR of at least level 1 is required.**
1232
1233        :param access_token: A valid access token.
1234        :param email: The new email address.
1235
1236        **Example**
1237
1238        ```py
1239        userinfo = await fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
1240        ```
1241        """
1242        email_change_endpoint = f"{self.base_url}/api/email/change"
1243
1244        async with self._get_httpx_client() as client:
1245            request = self._get_email_change_request(
1246                client,
1247                endpoint=email_change_endpoint,
1248                access_token=access_token,
1249                email=email,
1250            )
1251            response = await client.send(request)
1252
1253            self._handle_request_error(response)
1254
1255            return response.json()
1256
1257    async def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
1258        """
1259        Verify the user email with the Fief API using a valid access token and verification code.
1260
1261        **An access token with an ACR of at least level 1 is required.**
1262
1263        :param access_token: A valid access token.
1264        :param code: The verification code received by email.
1265
1266        **Example**
1267
1268        ```py
1269        userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
1270        ```
1271        """
1272        email_verify_endpoint = f"{self.base_url}/api/email/verify"
1273
1274        async with self._get_httpx_client() as client:
1275            request = self._get_email_verify_request(
1276                client,
1277                endpoint=email_verify_endpoint,
1278                access_token=access_token,
1279                code=code,
1280            )
1281            response = await client.send(request)
1282
1283            self._handle_request_error(response)
1284
1285            return response.json()
1286
1287    async def logout_url(self, redirect_uri: str) -> str:
1288        """
1289        Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.
1290
1291        **You're still responsible for clearing your own session mechanism if any.**
1292
1293        :param redirect_uri: A valid URL where the user will be redirected after the logout process:
1294
1295        **Example:**
1296
1297        ```py
1298        logout_url = await fief.logout_url("http://localhost:8000")
1299        ```
1300        """
1301        params = {"redirect_uri": redirect_uri}
1302        return f"{self.base_url}/logout?{urlencode(params)}"
1303
1304    @contextlib.asynccontextmanager
1305    async def _get_httpx_client(self):
1306        headers = {}
1307        if self.host is not None:
1308            headers["Host"] = self.host
1309
1310        async with httpx.AsyncClient(
1311            base_url=self.base_url, headers=headers, verify=self.verify, cert=self.cert
1312        ) as client:
1313            yield client
1314
1315    async def _get_openid_configuration(self) -> dict[str, Any]:
1316        if self._openid_configuration is not None:
1317            return self._openid_configuration
1318
1319        async with self._get_httpx_client() as client:
1320            request = self._get_openid_configuration_request(client)
1321            response = await client.send(request)
1322            json = response.json()
1323            self._openid_configuration = json
1324            return json
1325
1326    async def _get_jwks(self) -> jwk.JWKSet:
1327        if self._jwks is not None:
1328            return self._jwks
1329
1330        jwks_uri = self._get_endpoint_url(
1331            await self._get_openid_configuration(), "jwks_uri"
1332        )
1333        async with self._get_httpx_client() as client:
1334            response = await client.get(jwks_uri)
1335            self._jwks = jwk.JWKSet.from_json(response.text)
1336            return self._jwks
1337
1338    async def _auth_exchange_token(
1339        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
1340    ) -> FiefTokenResponse:
1341        token_endpoint = self._get_endpoint_url(
1342            await self._get_openid_configuration(), "token_endpoint"
1343        )
1344        async with self._get_httpx_client() as client:
1345            request = self._get_auth_exchange_token_request(
1346                client,
1347                endpoint=token_endpoint,
1348                code=code,
1349                redirect_uri=redirect_uri,
1350                code_verifier=code_verifier,
1351            )
1352            response = await client.send(request)
1353
1354            self._handle_request_error(response)
1355
1356            return response.json()

Async Fief authentication client.

FiefAsync( base_url: str, client_id: str, client_secret: Optional[str] = None, *, encryption_key: Optional[str] = None, host: Optional[str] = None, verify: Union[str, bool, ssl.SSLContext] = True, cert: Union[str, Tuple[str, Optional[str]], Tuple[str, Optional[str], Optional[str]], NoneType] = None)
928    def __init__(
929        self,
930        base_url: str,
931        client_id: str,
932        client_secret: Optional[str] = None,
933        *,
934        encryption_key: Optional[str] = None,
935        host: Optional[str] = None,
936        verify: VerifyTypes = True,
937        cert: Optional[CertTypes] = None,
938    ) -> None:
939        super().__init__(
940            base_url,
941            client_id,
942            client_secret,
943            encryption_key=encryption_key,
944            host=host,
945            verify=verify,
946            cert=cert,
947        )

Initialize the client.

Parameters
  • base_url: Base URL of your Fief tenant.
  • client_id: ID of your Fief client.
  • client_secret: Secret of your Fief client. If you're implementing a desktop app, it's not recommended to use it, since it can be easily found by the end-user in the source code. The recommended way is to use a Public client.
  • encryption_key: Encryption key of your Fief client. Necessary only if ID Token encryption is enabled.
  • **verify: Corresponds to the verify parameter of HTTPX. Useful to customize SSL connection handling.
  • **cert: Corresponds to the cert parameter of HTTPX. Useful to customize SSL connection handling.
async def auth_url( self, redirect_uri: str, *, state: Optional[str] = None, scope: Optional[list[str]] = None, code_challenge: Optional[str] = None, code_challenge_method: Optional[str] = None, lang: Optional[str] = None, extras_params: Optional[Mapping[str, str]] = None) -> str:
949    async def auth_url(
950        self,
951        redirect_uri: str,
952        *,
953        state: Optional[str] = None,
954        scope: Optional[list[str]] = None,
955        code_challenge: Optional[str] = None,
956        code_challenge_method: Optional[str] = None,
957        lang: Optional[str] = None,
958        extras_params: Optional[Mapping[str, str]] = None,
959    ) -> str:
960        """
961        Return an authorization URL.
962
963        :param redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
964        :param state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
965        :param scope: Optional list of scopes to ask for.
966        :param code_challenge: Optional code challenge for
967        [PKCE process](https://docs.fief.dev/going-further/pkce/).
968        :param code_challenge_method: Method used to hash the PKCE code challenge.
969        :param lang: Optional parameter to set the user locale.
970        Should be a valid [RFC 3066](https://www.rfc-editor.org/rfc/rfc3066) language identifier, like `fr` or `pt-PT`.
971        If not provided, the user locale is determined by their browser settings.
972        :param extras_params: Optional dictionary containing [specific parameters](https://docs.fief.dev/going-further/authorize-url/).
973
974        **Example:**
975
976        ```py
977        auth_url = await fief.auth_url("http://localhost:8000/callback", scope=["openid"])
978        ```
979        """
980        openid_configuration = await self._get_openid_configuration()
981        return self._auth_url(
982            openid_configuration,
983            redirect_uri,
984            state=state,
985            scope=scope,
986            code_challenge=code_challenge,
987            code_challenge_method=code_challenge_method,
988            lang=lang,
989            extras_params=extras_params,
990        )

Return an authorization URL.

Parameters
  • redirect_uri: Your callback URI where the user will be redirected after Fief authentication.
  • state: Optional string that will be returned back in the callback parameters to allow you to retrieve state information.
  • scope: Optional list of scopes to ask for.
  • code_challenge: Optional code challenge for PKCE process.
  • code_challenge_method: Method used to hash the PKCE code challenge.
  • lang: Optional parameter to set the user locale. Should be a valid RFC 3066 language identifier, like fr or pt-PT. If not provided, the user locale is determined by their browser settings.
  • **extras_params: Optional dictionary containing specific parameters.

Example:

auth_url = await fief.auth_url("http://localhost:8000/callback", scope=["openid"])
async def auth_callback( self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None) -> tuple[FiefTokenResponse, FiefUserInfo]:
 992    async def auth_callback(
 993        self, code: str, redirect_uri: str, *, code_verifier: Optional[str] = None
 994    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
 995        """
 996        Return a `FiefTokenResponse` and `FiefUserInfo` in exchange of an authorization code.
 997
 998        :param code: The authorization code.
 999        :param redirect_uri: The exact same `redirect_uri` you passed to the authorization URL.
1000        :param code_verifier:  The raw
1001        [PKCE](https://docs.fief.dev/going-further/pkce/) code used to generate the code challenge during authorization.
1002
1003        **Example:**
1004
1005        ```py
1006        tokens, userinfo = await fief.auth_callback("CODE", "http://localhost:8000/callback")
1007        ```
1008        """
1009        token_response = await self._auth_exchange_token(
1010            code, redirect_uri, code_verifier=code_verifier
1011        )
1012        jwks = await self._get_jwks()
1013        userinfo = self._decode_id_token(
1014            token_response["id_token"],
1015            jwks,
1016            code=code,
1017            access_token=token_response.get("access_token"),
1018        )
1019        return token_response, userinfo

Return a FiefTokenResponse and FiefUserInfo in exchange of an authorization code.

Parameters
  • code: The authorization code.
  • redirect_uri: The exact same redirect_uri you passed to the authorization URL.
  • code_verifier: The raw PKCE code used to generate the code challenge during authorization.

Example:

tokens, userinfo = await fief.auth_callback("CODE", "http://localhost:8000/callback")
async def auth_refresh_token( self, refresh_token: str, *, scope: Optional[list[str]] = None) -> tuple[FiefTokenResponse, FiefUserInfo]:
1021    async def auth_refresh_token(
1022        self, refresh_token: str, *, scope: Optional[list[str]] = None
1023    ) -> tuple[FiefTokenResponse, FiefUserInfo]:
1024        """
1025        Return fresh `FiefTokenResponse` and `FiefUserInfo` in exchange of a refresh token
1026
1027        :param refresh_token: A valid refresh token.
1028        :param scope: Optional list of scopes to ask for.
1029        If not provided, the access token will share the same list of scopes as requested the first time.
1030        Otherwise, it should be a subset of the original list of scopes.
1031
1032        **Example:**
1033
1034        ```py
1035        tokens, userinfo = await fief.auth_refresh_token("REFRESH_TOKEN")
1036        ```
1037        """
1038        token_endpoint = self._get_endpoint_url(
1039            await self._get_openid_configuration(), "token_endpoint"
1040        )
1041        async with self._get_httpx_client() as client:
1042            request = self._get_auth_refresh_token_request(
1043                client,
1044                endpoint=token_endpoint,
1045                refresh_token=refresh_token,
1046                scope=scope,
1047            )
1048            response = await client.send(request)
1049
1050            self._handle_request_error(response)
1051
1052            token_response = response.json()
1053
1054        jwks = await self._get_jwks()
1055        userinfo = self._decode_id_token(
1056            token_response["id_token"],
1057            jwks,
1058            access_token=token_response.get("access_token"),
1059        )
1060        return token_response, userinfo

Return fresh FiefTokenResponse and FiefUserInfo in exchange of a refresh token

Parameters
  • refresh_token: A valid refresh token.
  • scope: Optional list of scopes to ask for. If not provided, the access token will share the same list of scopes as requested the first time. Otherwise, it should be a subset of the original list of scopes.

Example:

tokens, userinfo = await fief.auth_refresh_token("REFRESH_TOKEN")
async def validate_access_token( self, access_token: str, *, required_scope: Optional[list[str]] = None, required_acr: Optional[FiefACR] = None, required_permissions: Optional[list[str]] = None) -> FiefAccessTokenInfo:
1062    async def validate_access_token(
1063        self,
1064        access_token: str,
1065        *,
1066        required_scope: Optional[list[str]] = None,
1067        required_acr: Optional[FiefACR] = None,
1068        required_permissions: Optional[list[str]] = None,
1069    ) -> FiefAccessTokenInfo:
1070        """
1071        Check if an access token is valid and optionally that it has a required list of scopes,
1072        or a required list of [permissions](https://docs.fief.dev/getting-started/access-control/).
1073        Returns a `FiefAccessTokenInfo`.
1074
1075        :param access_token: The access token to validate.
1076        :param required_scope: Optional list of scopes to check for.
1077        :param required_acr: Optional minimum ACR level required.
1078        Read more: https://docs.fief.dev/going-further/acr/
1079        :param required_permissions: Optional list of permissions to check for.
1080
1081        **Example: Validate access token with required scopes**
1082
1083        ```py
1084        try:
1085            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
1086        except FiefAccessTokenInvalid:
1087            print("Invalid access token")
1088        except FiefAccessTokenExpired:
1089            print("Expired access token")
1090        except FiefAccessTokenMissingScope:
1091            print("Missing required scope")
1092
1093        print(access_token_info)
1094        ```
1095
1096        **Example: Validate access token with minimum ACR level**
1097
1098        ```py
1099        try:
1100            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
1101        except FiefAccessTokenInvalid:
1102            print("Invalid access token")
1103        except FiefAccessTokenExpired:
1104            print("Expired access token")
1105        except FiefAccessTokenACRTooLow:
1106            print("ACR too low")
1107
1108        print(access_token_info)
1109        ```
1110
1111        **Example: Validate access token with required permissions**
1112
1113        ```py
1114        try:
1115            access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
1116        except FiefAccessTokenInvalid:
1117            print("Invalid access token")
1118        except FiefAccessTokenExpired:
1119            print("Expired access token")
1120        except FiefAccessTokenMissingPermission:
1121            print("Missing required permission")
1122
1123        print(access_token_info)
1124        ```
1125        """
1126        jwks = await self._get_jwks()
1127        return self._validate_access_token(
1128            access_token,
1129            jwks,
1130            required_scope=required_scope,
1131            required_acr=required_acr,
1132            required_permissions=required_permissions,
1133        )

Check if an access token is valid and optionally that it has a required list of scopes, or a required list of permissions. Returns a FiefAccessTokenInfo.

Parameters
  • access_token: The access token to validate.
  • required_scope: Optional list of scopes to check for.
  • required_acr: Optional minimum ACR level required. Read more: https://docs.fief.dev/going-further/acr/
  • required_permissions: Optional list of permissions to check for.

Example: Validate access token with required scopes

try:
    access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_scope=["required_scope"])
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenMissingScope:
    print("Missing required scope")

print(access_token_info)

Example: Validate access token with minimum ACR level

try:
    access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_acr=FiefACR.LEVEL_ONE)
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenACRTooLow:
    print("ACR too low")

print(access_token_info)

Example: Validate access token with required permissions

try:
    access_token_info = await fief.validate_access_token("ACCESS_TOKEN", required_permissions=["castles:create", "castles:read"])
except FiefAccessTokenInvalid:
    print("Invalid access token")
except FiefAccessTokenExpired:
    print("Expired access token")
except FiefAccessTokenMissingPermission:
    print("Missing required permission")

print(access_token_info)
async def userinfo(self, access_token: str) -> FiefUserInfo:
1135    async def userinfo(self, access_token: str) -> FiefUserInfo:
1136        """
1137        Return fresh `FiefUserInfo` from the Fief API using a valid access token.
1138
1139        :param access_token: A valid access token.
1140
1141        **Example:**
1142
1143        ```py
1144        userinfo = await fief.userinfo("ACCESS_TOKEN")
1145        ```
1146        """
1147        userinfo_endpoint = self._get_endpoint_url(
1148            await self._get_openid_configuration(), "userinfo_endpoint"
1149        )
1150        async with self._get_httpx_client() as client:
1151            request = self._get_userinfo_request(
1152                client, endpoint=userinfo_endpoint, access_token=access_token
1153            )
1154            response = await client.send(request)
1155
1156            self._handle_request_error(response)
1157
1158            return response.json()

Return fresh FiefUserInfo from the Fief API using a valid access token.

Parameters
  • access_token: A valid access token.

Example:

userinfo = await fief.userinfo("ACCESS_TOKEN")
async def update_profile( self, access_token: str, data: dict[str, typing.Any]) -> FiefUserInfo:
1160    async def update_profile(
1161        self, access_token: str, data: dict[str, Any]
1162    ) -> FiefUserInfo:
1163        """
1164        Update user information with the Fief API using a valid access token.
1165
1166        :param access_token: A valid access token.
1167        :param data: A dictionary containing the data to update.
1168
1169        **Example: Update user field**
1170
1171        To update [user field](https://docs.fief.dev/getting-started/user-fields/) values, you need to nest them into a `fields` dictionary, indexed by their slug.
1172
1173        ```py
1174        userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
1175        ```
1176        """
1177        update_profile_endpoint = f"{self.base_url}/api/profile"
1178
1179        async with self._get_httpx_client() as client:
1180            request = self._get_update_profile_request(
1181                client,
1182                endpoint=update_profile_endpoint,
1183                access_token=access_token,
1184                data=data,
1185            )
1186            response = await client.send(request)
1187
1188            self._handle_request_error(response)
1189
1190            return response.json()

Update user information with the Fief API using a valid access token.

Parameters
  • access_token: A valid access token.
  • data: A dictionary containing the data to update.

Example: Update user field

To update user field values, you need to nest them into a fields dictionary, indexed by their slug.

userinfo = await fief.update_profile("ACCESS_TOKEN", { "fields": { "first_name": "Anne" } })
async def change_password( self, access_token: str, new_password: str) -> FiefUserInfo:
1192    async def change_password(
1193        self, access_token: str, new_password: str
1194    ) -> FiefUserInfo:
1195        """
1196        Change the user password with the Fief API using a valid access token.
1197
1198        **An access token with an ACR of at least level 1 is required.**
1199
1200        :param access_token: A valid access token.
1201        :param new_password: The new password.
1202
1203        **Example**
1204
1205        ```py
1206        userinfo = await fief.change_password("ACCESS_TOKEN", "herminetincture")
1207        ```
1208        """
1209        change_password_profile_endpoint = f"{self.base_url}/api/password"
1210
1211        async with self._get_httpx_client() as client:
1212            request = self._get_change_password_request(
1213                client,
1214                endpoint=change_password_profile_endpoint,
1215                access_token=access_token,
1216                new_password=new_password,
1217            )
1218            response = await client.send(request)
1219
1220            self._handle_request_error(response)
1221
1222            return response.json()

Change the user password with the Fief API using a valid access token.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • new_password: The new password.

Example

userinfo = await fief.change_password("ACCESS_TOKEN", "herminetincture")
async def email_change(self, access_token: str, email: str) -> FiefUserInfo:
1224    async def email_change(self, access_token: str, email: str) -> FiefUserInfo:
1225        """
1226        Request an email change with the Fief API using a valid access token.
1227
1228        The user will receive a verification code on this new email address.
1229        It shall be used with the method `email_verify` to complete the modification.
1230
1231        **An access token with an ACR of at least level 1 is required.**
1232
1233        :param access_token: A valid access token.
1234        :param email: The new email address.
1235
1236        **Example**
1237
1238        ```py
1239        userinfo = await fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
1240        ```
1241        """
1242        email_change_endpoint = f"{self.base_url}/api/email/change"
1243
1244        async with self._get_httpx_client() as client:
1245            request = self._get_email_change_request(
1246                client,
1247                endpoint=email_change_endpoint,
1248                access_token=access_token,
1249                email=email,
1250            )
1251            response = await client.send(request)
1252
1253            self._handle_request_error(response)
1254
1255            return response.json()

Request an email change with the Fief API using a valid access token.

The user will receive a verification code on this new email address. It shall be used with the method email_verify to complete the modification.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • email: The new email address.

Example

userinfo = await fief.email_change("ACCESS_TOKEN", "anne@nantes.city")
async def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
1257    async def email_verify(self, access_token: str, code: str) -> FiefUserInfo:
1258        """
1259        Verify the user email with the Fief API using a valid access token and verification code.
1260
1261        **An access token with an ACR of at least level 1 is required.**
1262
1263        :param access_token: A valid access token.
1264        :param code: The verification code received by email.
1265
1266        **Example**
1267
1268        ```py
1269        userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
1270        ```
1271        """
1272        email_verify_endpoint = f"{self.base_url}/api/email/verify"
1273
1274        async with self._get_httpx_client() as client:
1275            request = self._get_email_verify_request(
1276                client,
1277                endpoint=email_verify_endpoint,
1278                access_token=access_token,
1279                code=code,
1280            )
1281            response = await client.send(request)
1282
1283            self._handle_request_error(response)
1284
1285            return response.json()

Verify the user email with the Fief API using a valid access token and verification code.

An access token with an ACR of at least level 1 is required.

Parameters
  • access_token: A valid access token.
  • code: The verification code received by email.

Example

userinfo = fief.email_verify("ACCESS_TOKEN", "ABCDE")
async def logout_url(self, redirect_uri: str) -> str:
1287    async def logout_url(self, redirect_uri: str) -> str:
1288        """
1289        Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.
1290
1291        **You're still responsible for clearing your own session mechanism if any.**
1292
1293        :param redirect_uri: A valid URL where the user will be redirected after the logout process:
1294
1295        **Example:**
1296
1297        ```py
1298        logout_url = await fief.logout_url("http://localhost:8000")
1299        ```
1300        """
1301        params = {"redirect_uri": redirect_uri}
1302        return f"{self.base_url}/logout?{urlencode(params)}"

Returns a logout URL. If you redirect the user to this page, Fief will clear the session stored on its side.

You're still responsible for clearing your own session mechanism if any.

Parameters
  • redirect_uri: A valid URL where the user will be redirected after the logout process:

Example:

logout_url = await fief.logout_url("http://localhost:8000")
class FiefTokenResponse(typing.TypedDict):
56class FiefTokenResponse(TypedDict):
57    """
58    Typed dictionary containing the tokens and related information returned by Fief after a successful authentication.
59    """
60
61    access_token: str
62    """Access token you can use to call the Fief API."""
63    id_token: str
64    """ID token containing user information."""
65    token_type: str
66    """Type of token, usually `bearer`."""
67    expires_in: int
68    """Number of seconds after which the tokens will expire."""
69    refresh_token: Optional[str]
70    """Token provided only if scope `offline_access` was granted. Allows you to retrieve fresh tokens using the `Fief.auth_refresh_token` method."""

Typed dictionary containing the tokens and related information returned by Fief after a successful authentication.

access_token: str

Access token you can use to call the Fief API.

id_token: str

ID token containing user information.

token_type: str

Type of token, usually bearer.

expires_in: int

Number of seconds after which the tokens will expire.

refresh_token: Optional[str]

Token provided only if scope offline_access was granted. Allows you to retrieve fresh tokens using the Fief.auth_refresh_token method.

class FiefAccessTokenInfo(typing.TypedDict):
73class FiefAccessTokenInfo(TypedDict):
74    """
75    Typed dictionary containing information about the access token.
76
77    **Example:**
78
79    ```json
80    {
81        "id": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
82        "scope": ["openid", "required_scope"],
83        "acr": "1",
84        "permissions": ["castles:read", "castles:create", "castles:update", "castles:delete"],
85        "access_token": "ACCESS_TOKEN",
86    }
87    ```
88    """
89
90    id: uuid.UUID
91    """ID of the user."""
92    scope: list[str]
93    """List of granted scopes for this access token."""
94    acr: FiefACR
95    """Level of Authentication Context class Reference."""
96    permissions: list[str]
97    """List of [granted permissions](https://docs.fief.dev/getting-started/access-control/) for this user."""
98    access_token: str
99    """Access token you can use to call the Fief API."""

Typed dictionary containing information about the access token.

Example:

{
    "id": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
    "scope": ["openid", "required_scope"],
    "acr": "1",
    "permissions": ["castles:read", "castles:create", "castles:update", "castles:delete"],
    "access_token": "ACCESS_TOKEN",
}
id: uuid.UUID

ID of the user.

scope: list[str]

List of granted scopes for this access token.

acr: FiefACR

Level of Authentication Context class Reference.

permissions: list[str]

List of granted permissions for this user.

access_token: str

Access token you can use to call the Fief API.

class FiefUserInfo(typing.TypedDict):
102class FiefUserInfo(TypedDict):
103    """
104    Dictionary containing user information.
105
106    **Example:**
107
108    ```json
109    {
110        "sub": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
111        "email": "anne@bretagne.duchy",
112        "tenant_id": "c91ecb7f-359c-4244-8385-51ecd6c0d06b",
113        "fields": {
114            "first_name": "Anne",
115            "last_name": "De Bretagne"
116        }
117    }
118    ```
119    """
120
121    sub: str
122    """
123    ID of the user.
124    """
125    email: str
126    """
127    Email address of the user.
128    """
129    tenant_id: str
130    """
131    ID of the [tenant](https://docs.fief.dev/getting-started/tenants/) associated to the user.
132    """
133    fields: dict[str, Any]
134    """
135    [User fields](https://docs.fief.dev/getting-started/user-fields/) values for this user, indexed by their slug.
136    """

Dictionary containing user information.

Example:

{
    "sub": "aeeb8bfa-e8f4-4724-9427-c3d5af66190e",
    "email": "anne@bretagne.duchy",
    "tenant_id": "c91ecb7f-359c-4244-8385-51ecd6c0d06b",
    "fields": {
        "first_name": "Anne",
        "last_name": "De Bretagne"
    }
}
sub: str

ID of the user.

email: str

Email address of the user.

tenant_id: str

ID of the tenant associated to the user.

fields: dict[str, typing.Any]

User fields values for this user, indexed by their slug.

class FiefError(builtins.Exception):
139class FiefError(Exception):
140    """Base Fief client error."""

Base Fief client error.

class FiefAccessTokenACRTooLow(fief_client.FiefError):
165class FiefAccessTokenACRTooLow(FiefError):
166    """The access token doesn't meet the minimum ACR level."""

The access token doesn't meet the minimum ACR level.

class FiefAccessTokenExpired(fief_client.FiefError):
157class FiefAccessTokenExpired(FiefError):
158    """The access token is expired."""

The access token is expired.

class FiefAccessTokenMissingPermission(fief_client.FiefError):
169class FiefAccessTokenMissingPermission(FiefError):
170    """The access token is missing a required permission."""

The access token is missing a required permission.

class FiefAccessTokenMissingScope(fief_client.FiefError):
161class FiefAccessTokenMissingScope(FiefError):
162    """The access token is missing a required scope."""

The access token is missing a required scope.

class FiefAccessTokenInvalid(fief_client.FiefError):
153class FiefAccessTokenInvalid(FiefError):
154    """The access token is invalid."""

The access token is invalid.

class FiefIdTokenInvalid(fief_client.FiefError):
173class FiefIdTokenInvalid(FiefError):
174    """The ID token is invalid."""

The ID token is invalid.

class FiefRequestError(fief_client.FiefError):
143class FiefRequestError(FiefError):
144    """The request to Fief server resulted in an error."""
145
146    def __init__(self, status_code: int, detail: str) -> None:
147        self.status_code = status_code
148        self.detail = detail
149        self.message = f"[{status_code}] - {detail}"
150        super().__init__(self.message)

The request to Fief server resulted in an error.

FiefRequestError(status_code: int, detail: str)
146    def __init__(self, status_code: int, detail: str) -> None:
147        self.status_code = status_code
148        self.detail = detail
149        self.message = f"[{status_code}] - {detail}"
150        super().__init__(self.message)
status_code
detail
message