Package: dj-rest-firebase
Firebase for Django Rest Framework🎉 This package is for those who want to avoid vendor lock-in with Firebase, but still use it for authentication in their DRF project.  🎉Some features:Firebase Authentication APIs.Sign in with OAuth Providers.Multi-factor authentication (MFA) for Email/Password provider.No vendor lock-in, more control to the Developer.Currently, OAuth is working only with Google, Facebook and GitHub 😢InstallationYou can install thepackage from PyPIby running the following command:pipinstalldj-rest-firebaseInitial SetupFirebase SetupCreate a Firebase project and then a web app inside the Firebase project. Make a note of theWeb API Keyavailable underProject Settings ➡ Generaltab. You'll need this inDjango Setup.There are many articles on the web that explain how to create a Firebase project and an app inside it.Navigate toBuild ➡ Authentication ➡ Sign-in methodand add/enable your preferred providers.There are many articles on the web that explain how to generate Client ID and Client Secret for each OAuth provider.NOTE: In case of Google provider, the Client ID and Client Secret are available under "Web SDK Configuration".NOTE: Without this setup, you'll not be able to use Firebase authentication. You'll need to have at-least one provider added.Navigate toBuild ➡ Authentication ➡ Settingsand customizeUser Account Managementsettings to your preference.Also, under the sameSettingstab, modifyAuthorized Domains.OPTIONAL: Change Email templates underBuild ➡ Authentication ➡ Templatestab. This tab defines which Email ID to use for sending emails such as verification links etc. and how the Email template looks like.NOTE: The Email ID can be configured underSMTP Settingsoption.Django SetupAdd this package toINSTALLED_APPSlist insettings.pyfile.INSTALLED_APPS=[...,'dj_rest_firebase']Also set the following attributes insettings.pyfile.NOTE:DRF_MFA_ENABLEDis OPTIONAL and add it only if you need multi-factor authentication (MFA).NOTE:DRF_OAUTH_CALLBACK_URLwill be used only if you don't pass a callback URL explicitly in certain functions (see Functions usage below). This setting is useful when your callback URL is same for all OAuth providers.DRF_MFA_ENABLED=TrueDRF_OAUTH_CALLBACK_URL="https://<YOUR CALLBACK URL HERE>/"Create an environment variable namedFIREBASE_API_KEYand set it to theWeb API Keyvalue taken fromFirebase Setupabove.Run Django migration commands:This should create a Django model calledFirebaseMFASecret.This model will be unused if MFA is not enabled.pythonmanage.pymakemigrations
pythonmanage.pymigrateFinal SetupThis setup is OPTIONAL if you don't have multi-factor authentication enabled.Create two 16-character (16 or more) strings and store them in files safely.Use these strings as replacements forencryption_keyandencryption_ivrespectively in the functions.NOTE: These keys are used for encrypting the MFA Secrets before saving them in the Django model, as an added security.NOTE: The same keys are used to de-crypt the encrypted secrets from Django model.Caution: If you lose these keys, the email/password authentication will not work for any user.Release NotesVersionRelease DateDetails0.6.6 (Current)August 2022Firebase Auth APIs0.7.5XYZ 202XSupport for Twitter, Yahoo, Microsoft and Apple OAuthFutureXYZ 202XFirebase Storage & Firestore APIsFunctions UsageBefore you use these functions, it's very important that you review theThings to Considersection below to keep your application secure.Usage Example**All functions are available fromdj_rest_firebase.auth**fromdj_rest_firebase.authimportfetch_providers_for_emailprint(str(fetch_providers_for_email("user@example.com")))get_mfa_secret()Purpose: To generate an MFA Secret codeInputs:No Input RequiredResponse:'[MFA_SECRET]'fetch_providers_for_email()Purpose: To fetch the auth providers info for an Email IDInputs:email: User's Email ID.(Optional)continue_uri: The URI to which the IDP redirects the user back. For this use case, this is just the current URL.Response:{'kind':'identitytoolkit#CreateAuthUriResponse','allProviders':['password'],'registered':True,'sessionId':'[SESSION_ID]','signinMethods':['password']}signup_with_email_password()Purpose: To sign up a new user using Email and PasswordInputs:email: User's Email ID.password: User's Password.(Optional)mfa_secret: MFA Secret code. This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_key: Encryption Key created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_iv: Encryption IV created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.Response:{"idToken":"[ID_TOKEN]","email":"[USER_EMAIL]","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600","localId":"[USER_ID]"}signin_with_email_password()Purpose: To sign in a user using Email and PasswordInputs:email: User's Email ID.password: User's Password.(Optional)otp: One Time Passcode (String). This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_key: Encryption Key created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_iv: Encryption IV created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","displayName":"","idToken":"[ID_TOKEN]","registered":True,"refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600"}signin_anonymously()Purpose: To sign in anonymouslyInputs:No Input RequiredResponse:{"idToken":"[ID_TOKEN]","email":"","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600","localId":"[USER_ID]"}signin_with_oauth()Purpose: To sign in a user using OAuth ProviderInputs:auth_provider: OAuth provider name.auth_code: Authorization code returned to the Callback URL by thelinks from this table below.client_id: OAuth Provider Client ID.client_secret: OAuth Provider Client Secret.(Optional)callback_url: OAuth callback URL. This is REQUIRED ifDRF_OAUTH_CALLBACK_URLis not set.(Optional)challenge_txt: This is required only when OAuth Provider istwitter.com. This value should match<CHALLENGE_TEXT>fromthis table below.Response:{"federatedId":"https://accounts.google.com/1234567890","providerId":"google.com","localId":"[USER_ID]","emailVerified":True,"email":"[USER_EMAIL]","oauthIdToken":"[GOOGLE_ID_TOKEN]","firstName":"John","lastName":"Doe","fullName":"John Doe","displayName":"John Doe","idToken":"[ID_TOKEN]","photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600","rawUserInfo":"{\"updated_time\":\"2017-02-22T01:10:57+0000\",\"gender\":\"male\", ...}"}send_email_verification()Purpose: To send an email with verification linkInputs:id_token: Firebase Auth ID token.Response:{"success":"VERIFICATION_EMAIL_SENT"}confirm_email_verification()Purpose: To confirm email verificationInputs:oob_code: Code from Verification Email.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","passwordHash":"...","providerUserInfo":[{"providerId":"password","federatedId":"[USER_EMAIL]"}]}get_user_data()Purpose: To get user data from FirebaseInputs:id_token: Firebase Auth ID token.Response:{"users":[{"localId":"[USER_ID]","email":"[USER_EMAIL]","emailVerified":False,"displayName":"John Doe","providerUserInfo":[{"providerId":"password","displayName":"John Doe","photoUrl":"http://localhost:8080/img1234567890/photo.png","email":"[USER_EMAIL]",}],"photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg","passwordHash":"...","passwordUpdatedAt":1.484124177E12,"validSince":"1484124177","disabled":False,"lastLoginAt":"1484628946000","createdAt":"1484124142000","customAuth":False}]}delete_account()Purpose: To delete a user accountInputs:id_token: Firebase Auth ID token.Response:{"success":"ACCOUNT_DELETED"}update_profile()Purpose: To delete a user accountInputs:id_token: Firebase Auth ID token.display_name: User's Display Name (First Name and Last Name combined).photo_url: User's Photo URL.(Optional)delete_attributes: (bool) Flag to indicate whether to delete any attributes.(Optional)delete_attributes_list: (list) List of attributes to delete. Acceptable list values: 'DISPLAY_NAME', 'PHOTO_URL'.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","displayName":"John Doe","photoUrl":"[http://localhost:8080/img1234567890/photo.png]","passwordHash":"...","providerUserInfo":[{"providerId":"password","federatedId":"[USER_EMAIL]","displayName":"John Doe","photoUrl":"http://localhost:8080/img1234567890/photo.png"}],"idToken":"[NEW_ID_TOKEN]","refreshToken":"[NEW_REFRESH_TOKEN]","expiresIn":"3600"}send_password_reset_email()Purpose: To Email password reset linkInputs:email: User Email ID.Response:{"success":"PASSWORD_RESET_EMAIL_SENT"}verify_password_reset_code()Purpose: To verify code from password reset EmailInputs:oob_code: Code from Password Reset Email.Response:{"success":"PASSWORD_RESET_CODE_VALID"}confirm_password_reset()Purpose: To confirm password resetInputs:oob_code: Code from Password Reset Email.new_password: User's new password.Response:{"success":"PASSWORD_RESET_DONE"}change_email()Purpose: To change User's EmailInputs:id_token: Firebase Auth ID token.new_email: User's new Email ID.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","passwordHash":"...","providerUserInfo":[{"providerId":"password","federatedId":"[USER_EMAIL]"}],"idToken":"[NEW_ID_TOKEN]","refreshToken":"[NEW_REFRESH_TOKEN]","expiresIn":"3600"}change_password()Purpose: To change User's PasswordInputs:id_token: Firebase Auth ID token.new_password: User's new password.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","passwordHash":"...","providerUserInfo":[{"providerId":"password","federatedId":"[USER_EMAIL]"}],"idToken":"[NEW_ID_TOKEN]","refreshToken":"[NEW_REFRESH_TOKEN]","expiresIn":"3600"}change_mfa_secret()Purpose: To change User's MFA SecretInputs:id_token: Firebase Auth ID token.mfa_secret: MFA Secret code.encryption_key: Encryption Key created inFinal Setupabove.encryption_iv: Encryption IV created inFinal Setupabove.Response:{"success":"MFA_UPDATED"}link_with_email_password()Purpose: To link user account with Email/PasswordInputs:id_token: Firebase Auth ID token.email: User's Email ID.new_password: User's new password.(Optional)mfa_secret: MFA Secret code. This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_key: Encryption Key created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.(Optional)encryption_iv: Encryption IV created inFinal Setupabove. This is required ifDRF_MFA_ENABLEDis set toTrue.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","displayName":"John Doe","photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg","passwordHash":"...","providerUserInfo":[{"providerId":"password","federatedId":"[USER_EMAIL]"}],"idToken":"[ID_TOKEN]","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600","emailVerified":False}link_with_oauth()Purpose: To link user account with an OAuth providerInputs:id_token: Firebase Auth ID token.auth_provider: OAuth provider name.auth_code: Authorization code returned to the Callback URL by thelinks from this table below.client_id: OAuth Provider Client ID.client_secret: OAuth Provider Client Secret.(Optional)callback_url: OAuth callback URL. This is REQUIRED ifDRF_OAUTH_CALLBACK_URLis not set.(Optional)challenge_txt: This is required only when OAuth Provider istwitter.com. This value should match<CHALLENGE_TEXT>fromthis table below.Response:{"federatedId":"https://accounts.google.com/1234567890","providerId":"google.com","localId":"[USER_ID]","emailVerified":True,"email":"[USER_EMAIL]","oauthIdToken":"[GOOGLE_ID_TOKEN]","firstName":"John","lastName":"Doe","fullName":"John Doe","displayName":"John Doe","idToken":"[ID_TOKEN]","photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600","rawUserInfo":"{\"updated_time\":\"2017-02-22T01:10:57+0000\",\"gender\":\"male\", ...}"}unlink_provider()Purpose: To unlink User Account from an Auth providerInputs:id_token: Firebase Auth ID token.auth_provider: OAuth provider name.Response:{"localId":"[USER_ID]","email":"[USER_EMAIL]","displayName":"John Doe","photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg","passwordHash":"...","providerUserInfo":[{"providerId":"google.com","federatedId":"1234567890","displayName":"John Doe","photoUrl":"https://lh5.googleusercontent.com/.../photo.jpg"},{"providerId":"password","federatedId":"[USER_EMAIL]"}],"emailVerified":"true"}refresh_id_token()Purpose: To generate a new ID token using Refresh tokenInputs:refresh_token: Firebase Auth Refresh token.Response:{"expires_in":"3600","token_type":"Bearer","refresh_token":"[REFRESH_TOKEN]","id_token":"[ID_TOKEN]","user_id":"[USER_ID]","project_id":"1234567890"}exchange_custom_token_for_id_token()Purpose: To exchange custom token for ID and Refresh tokensInputs:custom_token: Firebase Auth Custom token.Response:{"idToken":"[ID_TOKEN]","refreshToken":"[REFRESH_TOKEN]","expiresIn":"3600"}Things to ConsiderResponse CodesAll functions return a dictionary with one of the following keys (and a code as their value), in case of un-successful operations:errorIndicates failed operationwarningThis is not really an error but something to be taken care of by the application. The only possible code for this is AUTH_PROVIDER_NOT_FOUND. See below on what this means.AUTH_PROVIDER_NOT_FOUNDIf you're getting this response, it means that the user is trying to sign in using a provider that they didn't previously link.For example, if the user signed up using Email/Password initially, but later trying to sign in using Google provider (with same Email ID).
Another example, if the user signed up using Facebook provider, but later trying to sign in using GitHub provider.In order to allow the user to perform above actions, they need to first link their providers while being signed in. That is, in the first example above, they should first sign in using Email/Password and then link Google provider, in order to use it for signing-in in the future.ADMIN_ONLY_OPERATIONThiserrorcode indicates that some firebase configuration is not done or incomplete.For example, you may receive this error when you try to use the functionsignin_anonymously(). Here, it indicates that the Anonymous provider is not enabled in Firebase.emailVerified BooleanWhen you callsignup_with_email_passwordfunction orsignin_with_oauthfunction, it returns anid_tokenin the success response. However, theemailVerifiedattribute may be returned asFalse.It's very important that the email gets verified, before letting the user access any sensitive information. Not doing so can make your application insecure.signin_with_oauthalways returnsemailVerifiedasTruewhen the auth provider is 'google.com'QR Code Generator (for MFA Secret)The following link can be used to generate QR Code image, using the MFA Secret. Replace<MFA_SECRET>and<ACCOUNT_NAME>.https://chart.apis.google.com/chart?cht=qr&chs=250x250&chld=L|2&chl=otpauth://totp/<ACCOUNT_NAME>?secret=<MFA_SECRET>OAuth Authorize (Sign In) LinksYou can use following links in your frontend application to sign in with OAuth provider. Replace<CLIENT_ID>,<CALLBACK_URL>and<YOUR_STATE>.<CLIENT_ID>is your OAuth provider's client id.<CALLBACK_URL>is the callback URL set in your OAuth app settings.<YOUR_STATE>is some state that needs to be persisted in the callback URL.<CHALLENGE_TEXT>is required only for Twitter.OAuth ProviderAuthorize LinkGooglehttps://accounts.google.com/o/oauth2/auth/oauthchooseaccount?prompt=select_account&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=profile%20email&response_type=code&flowName=GeneralOAuthFlow&state=<YOUR_STATE>Facebookhttps://www.facebook.com/v14.0/dialog/oauth?client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=email&auth_type=rerequest&state=<YOUR_STATE>GitHubhttps://github.com/login/oauth/authorize?login&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=user&state=<YOUR_STATE>Microsofthttps://login.microsoftonline.com/common/adminconsent?client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&state=<YOUR_STATE>Applehttps://appleid.apple.com/auth/authorize?response_type=code id_token&response_mode=form_post&client_id=<CLIENT_ID>&scope=name email&redirect_uri=<CALLBACK_URL>&state=<YOUR_STATE>Yahoohttps://api.login.yahoo.com/oauth2/request_auth?client_id=<CLIENT_ID>&response_type=code&redirect_uri=<CALLBACK_URL>&scope=openid&state=<YOUR_STATE>Twitterhttps://twitter.com/i/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=offline.access&state=<YOUR_STATE>&code_challenge=<CHALLENGE_TEXT>&code_challenge_method=plainAmazonhttps://www.amazon.com/ap/oa?client_id=<CLIENT_ID>&scope=profile&response_type=code&redirect_uri=<CALLBACK_URL>&state=<YOUR_STATE>LinkedInhttps://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=r_liteprofile%20r_emailaddress&state=<YOUR_STATE>Reddithttps://www.reddit.com/api/v1/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<CALLBACK_URL>&scope=identity&state=<YOUR_STATE>HelpIf you run into any issues while setting up or while using the package, feel free to open an issue in this GitHub repository.This is my first PyPI package and I built it from scratch. If you like my work, please consider giving it a GitHub ⭐️️. Thanks!