# fars An unofficial Rust client for the [Firebase Auth REST API](https://firebase.google.com/docs/reference/rest/auth). ## Installation Please install this library by adding by CLI: ```shell $ cargo add fars ``` or adding dependency to your `Cargo.toml`: ```toml [dependencies] fars = "0.2.0" ``` ## Features All features in this crate are as follows: - default - [Session-based interfaces](#api-usages) - [Raw API interfaces](#optional-raw-api-interfaces) - (Optional) `verify` - [ID token verification](#optional-id-token-verification) - (Optional) `custom_client` - [HTTP client customization](#http-client-customization) ## Supported APIs Suppoted APIs of the [Firebase Auth REST API](https://firebase.google.com/docs/reference/rest/auth) are as follows: - [x] [Exchange custom token for an ID and refresh token](https://firebase.google.com/docs/reference/rest/auth#section-verify-custom-token) - [x] [Exchange a refresh token for an ID token](https://firebase.google.com/docs/reference/rest/auth#section-refresh-token) - [x] [Sign up with email / password](https://firebase.google.com/docs/reference/rest/auth#section-create-email-password) - [x] [Sign in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password) - [x] [Sign in anonymously](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-anonymously) - [x] [Sign in with OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-with-oauth-credential) - [x] [Fetch providers for email](https://firebase.google.com/docs/reference/rest/auth#section-fetch-providers-for-email) - [x] [Send password reset email](https://firebase.google.com/docs/reference/rest/auth#section-send-password-reset-email) - [ ] (Not tested) [Verify password reset code](https://firebase.google.com/docs/reference/rest/auth#section-verify-password-reset-code) - [ ] (Not tested) [Confirm password reset](https://firebase.google.com/docs/reference/rest/auth#section-confirm-reset-password) - [x] [Change email](https://firebase.google.com/docs/reference/rest/auth#section-change-email) - [x] [Change password](https://firebase.google.com/docs/reference/rest/auth#section-change-password) - [x] [Update profile](https://firebase.google.com/docs/reference/rest/auth#section-update-profile) - [x] [Get user data](https://firebase.google.com/docs/reference/rest/auth#section-get-account-info) - [x] [Link with email/password](https://firebase.google.com/docs/reference/rest/auth#section-link-with-email-password) - [x] [Link with OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-link-with-oauth-credential) - [x] [Unlink provider](https://firebase.google.com/docs/reference/rest/auth#section-unlink-provider) - [x] [Send email verification](https://firebase.google.com/docs/reference/rest/auth#section-send-email-verification) - [ ] (Not tested) [Confirm email verification](https://firebase.google.com/docs/reference/rest/auth#section-confirm-email-verification) - [x] [Delete account](https://firebase.google.com/docs/reference/rest/auth#section-delete-account) > [!NOTE] > Unsupported APIs have already been implemented but not tested. ## Supported OAuth ID providers Supported OAuth ID provides are as follows: - [ ] (Not implemented) Apple (`apple.com`) - [ ] (Not implemented) Apple Game Center (`gc.apple.com`) - [ ] (Not tested) Facebook (`facebook.com`) - [ ] (Not implemented) GitHub (`github.com`) - [x] Google (`google.com`) - [ ] (Not implemented) Google Play Games (`playgames.google.com`) - [ ] (Not implemented) LinkedIn (`linkedin.com`) - [ ] (Not implemented) Microsoft (`microsoft.com`) - [ ] (Not tested) Twitter (`twitter.com`) - [ ] (Not implemented) Yahoo (`yahoo.com`) > [!NOTE] > Unsupported providers have either not been tested or the format of `IdpPostBody` is not documented at the [official API reference](https://firebase.google.com/docs/reference/rest/auth). ## API Usages Provides semantic interfaces based on a session (`fars::Session`) as following steps. > [!IMPORTANT] > - ID token (`fars::Session.id_token`) has expiration date. > - API calling through a session automatically refresh an ID token by the [refresh token API](https://firebase.google.com/docs/reference/rest/auth#section-refresh-token) when the ID token has been expired. > - All APIs through session cosume session and return new session that has same ID token or refreshed one except for the [delete account API](https://firebase.google.com/docs/reference/rest/auth#section-delete-account). > > Therefore you have to **update** session every time you use APIs through a session by returned new session. ### A usage for a siging in user 1. Create a config (`fars::Config`) with your Firebase project API key. 2. Sign in or sign up by supported options (Email & password / OAuth / Anonymous / Stored refresh token) through the config then get the session (`fars::Session`) for the siging in user. 3. Use Auth APIs for the siging in user through the session, or use ID token (`fars::Session.id_token`) for other Firebase APIs. A sample code to [sign up with email / password](https://firebase.google.com/docs/reference/rest/auth#section-create-email-password) and to [get user data](https://firebase.google.com/docs/reference/rest/auth#section-get-account-info) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::Config; use fars::ApiKey; use fars::Email; use fars::Password; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Create a config with your Firebase project API key. let config = Config::new( ApiKey::new("your-firebase-project-api-key"), ); // 2. Sign up with email and password then get a session. let session = config.sign_up_with_email_password( Email::new("user@example.com"), Password::new("password"), ).await?; // 3. Get user data through the session and get a new session. let (new_session, user_data) = session.get_user_data().await?; // 4. Do something with new_session and user_data. Ok(()) } ``` ### A usage for not siging in user 1. Create a config (`fars::Config`) with your Firebase project API key. 2. Use Auth APIs for a not siging in user through the config. A sample code to [send password reset email](https://firebase.google.com/docs/reference/rest/auth#section-send-password-reset-email) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::Config; use fars::ApiKey; use fars::Email; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Create a config with your Firebase project API key. let config = Config::new( ApiKey::new("your-firebase-project-api-key"), ); // 2. Send reset password email to specified email through the config if it has been registered. config.send_reset_password_email( Email::new("user@example"), None, // Option: Locale ).await?; Ok(()) } ``` ## Sign in with OAuth credentials > [!IMPORTANT] > This crate does not provide methods to get OAuth credential for each ID provider. > > When you use signing in with OAuth credential, please implement a method to get target OAuth credential. See also [supported OAuth providers](#supported-oauth-id-providers). ### Google OAuth To sign in with Google OAuth credential, 1. Create a config (`fars::Config`) with your Firebase project API key. 2. Get OpenID token from Google OAuth API. See [reference](https://developers.google.com/identity/protocols/oauth2/web-server#obtainingaccesstokens). 3. Sign in with specifying `request_uri` and `IdpPostBody::Google`. A sample code to [sign in with Google OAuth credential](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-with-oauth-credential) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::Config; use fars::ApiKey; use fars::OAuthRequestUri; use fars::IdpPostBody; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Create a config with your Firebase project API key. let config = Config::new( ApiKey::new("your-firebase-project-api-key"), ); // 2. Get an OpenID token from Google OAuth by any method. let google_id_token = "google-open-id-token".to_string(); // 3. Get a session by signing in with Google OAuth credential. let session = config .sign_in_with_oauth_credential( OAuthRequestUri::new("https://your-app.com/redirect/path/auth/handler"), IdpPostBody::Google { id_token: google_id_token, }, ) .await?; // 4. Do something with the session. Ok(()) } ``` ## Error handling If you handle error in this crate, please handle `fars::Result` and `fars::Error`. > [!NOTE] > `fars::Error::ApiError` has an error code (`fars::error::CommonErrorCode`) according to common error codes in the [API reference](https://firebase.google.com/docs/reference/rest/auth). > You can specify error type of an API error of Firebase Auth by matching an error code (`fars::error::CommonErrorCode`). A sample code to handle error for [signing in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password) with [reqwest](https://github.com/seanmonstar/reqwest), [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::Config; use fars::ApiKey; use fars::Email; use fars::Password; #[tokio::main] async fn main() -> anyhow::Result<()> { // Create a config. let config = Config::new( ApiKey::new("your-firebase-project-api-key"), ); // Create a session by signing in with email and password. match config .sign_in_with_email_password( Email::new("user@example"), Password::new("password"), ) .await { // Success | Ok(session) => { println!( "Succeeded to sign in with email/password: {:?}", session ); // Do something with the session. Ok(()) }, // Failure | Err(error) => { match error { // Handle HTTP request error. | fars::Error::HttpRequestError(error) => { // Do something with HTTP request error, e.g. retry. Err(error.into()) }, // Handle API error. | fars::Error::ApiError { status_code, error_code, response, } => { match error_code { | CommonErrorCode::InvalidLoginCredentials => { // Do something with invalid login credentials, e.g. display error message for user: "Invalid email or/and password.". Err(fars::Error::ApiError { status_code, error_code, response, } .into()) }, | CommonErrorCode::UserDisabled => { // Do something with disabled user, e.g. display error message for user: "This user is disabled by administrator, please use another account.". Err(fars::Error::ApiError { status_code, error_code, response, } .into()) }, | CommonErrorCode::TooManyAttemptsTryLater => { // Do something with too many attempts, e.g. display error message for user: "Too may requests, please try again later.". Err(fars::Error::ApiError { status_code, error_code, response, } .into()) }, | _ => { // Do something with other API errors. Err(fars::Error::ApiError { status_code, error_code, response, } .into()) }, } }, // Handle internal errors | _ => { // Do something with internal errors. Err(error.into()) }, } }, } } ``` ## Raw API interfaces Provides raw [supported APIs](#supported-apis) by `fars::api` module. A sample code to [sign in with email / password](https://firebase.google.com/docs/reference/rest/auth#section-sign-in-email-password) with [reqwest](https://github.com/seanmonstar/reqwest), [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::ApiKey; use fars::Client; use fars::api; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Specify your API key. let api_key = ApiKey::new("your-firebase-project-api-key"); // 2. Create a HTTP client. let client = Client::new(); // 3. Create a request payload for the sign in API. let request_payload = api::SignInWithEmailPasswordRequestBodyPayload::new( "user@example.com".to_string(), "password".to_string(), ); // 4. Send a request and receive a response payload of the sign in API. let response_payload = api::sign_in_with_email_password( &client, &api_key, request_payload, ).await?; // 5. Do something with the response payload. Ok(()) } ``` ## (Optional) ID token verification Provides ID token verification of the Firebase Auth via `fars::verification` module. > [!NOTE] > ID token verification is an optional feature. > > Please activate this feature by CLI: > > ```shell > $ cargo add fars --features verify > ``` > > or adding features to your `Cargo.toml`: > > ```toml > [dependencies] > fars = { version = "0.2.0", features = ["verify"] } > ``` A sample code to [verify ID token](https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library) with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use fars::VerificationConfig; use fars::ProjectId; use fars::IdToken; #[tokio::main] async fn main() -> anyhow::Result<()> { // Create a cofig for verification with your Firebase project ID. let cofing = let config = VerificationConfig::new( ProjectId::new("firebase-project-id"), ); // Get an ID token of the Firebase Auth by any method. let id_token = IdToken::new("id-token"); // Verrify the ID token. match config.verify_id_token(&id_token).await { Ok(claims) => { // Verification succeeded. }, Err(error) => { // Verification failed. }, } Ok(()) } ``` ## HTTP client customization Provides HTTP client customization interface for Firebase Auth APIs. > [!NOTE] > HTTP client customization is an optional feature. > > Please activate this feature by CLI: > > ```shell > $ cargo add fars --features custom_client > ``` > > or adding features to your `Cargo.toml`: > > ```toml > [dependencies] > fars = { version = "0.2.0", features = ["custom_client"] } > ``` An example to customize timeout options of HTTP client with [tokio](https://github.com/tokio-rs/tokio) and [anyhow](https://github.com/dtolnay/anyhow) is as follows: ```rust use std::time::Duration; use fars::Client; use fars::ApiKey; use fars::Config; #[tokio::main] async fn main() -> anyhow::Result<()> { // 1. Create a custom client with re-exported `reqwest` client. let client = fars::reqwest::ClientBuilder::new() .timeout(Duration::from_secs(60)) .connect_timeout(Duration::from_secs(10)) .build()?; // 2. Customize HTTP client. let client = Client::custom(client); // 3. Create a cofig with customized client. let config = Config::custom( ApiKey::new("your-firebase-project-api-key"), client, ); // 4. Do something with a customized config. Ok(()) } ``` ## Other examples Please refer [/examples](./examples/) directory, [a shell script](./examples.sh) and [a study of authentication on Web frontend](https://github.com/mochi-neko/rust-frontend-playground) with [dioxus](https://github.com/DioxusLabs/dioxus). ## Changelog See [CHANGELOG](./CHANGELOG.md). ## License Licensed under either of the [Apache License, Version 2.0](./LICENSE-APACHE) or the [MIT](./LICENSE-MIT) license at your option.