├── .gitignore ├── CHANGES.txt ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── README ├── README.md ├── firebase_token_generator.py ├── firebase_token_generator_test.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/ 3 | *.egg-info/* 4 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v2.0.1, 2014/12/14 -- Add support for Python 3 2 | v2.0.0, 2014/09/15 -- Update token generator for updated token format, and require 'uid' field. 3 | v1.3.2, 2013/09/16 -- Support native datetime objects for 'expires' and 'notBefore' options. 4 | v1.0.0, 2013/01/24 -- Initial Release 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2014 Firebase 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | LICENSE 3 | MANIFEST.in 4 | README 5 | README.md 6 | firebase_token_generator.py 7 | setup.py 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include MANIFEST.in 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Status: Archived 2 | This repository has been archived and is no longer maintained. 3 | 4 | ![status: inactive](https://img.shields.io/badge/status-inactive-red.svg) 5 | 6 | # Firebase Token Generator - Python 7 | 8 | **This library is deprecated. Developers who are looking for a library to mint custom JWT tokens for Firebase are advised to use the [Firebase Admin SDK for Python](https://github.com/firebase/firebase-admin-python).** 9 | 10 | **WARNING: This token generator is compatible with versions 1.x.x and 2.x.x of the Firebase SDK. If you are using the 3.x.x SDK, please refer to the documentation [here](https://firebase.google.com/docs/auth/admin/create-custom-tokens#create_custom_tokens_using_a_third-party_jwt_library).** 11 | 12 | [Firebase Custom Login](https://www.firebase.com/docs/web/guide/simple-login/custom.html) 13 | gives you complete control over user authentication by allowing you to authenticate users 14 | with secure JSON Web Tokens (JWTs). The auth payload stored in those tokens is available 15 | for use in your Firebase [security rules](https://www.firebase.com/docs/security/api/rule/). 16 | This is a token generator library for Python which allows you to easily create those JWTs. 17 | 18 | 19 | ## Installation 20 | 21 | The Firebase Python token generator library is available via pip: 22 | 23 | ```bash 24 | $ pip install firebase-token-generator 25 | ``` 26 | 27 | ## A Note About Security 28 | 29 | **IMPORTANT:** Because token generation requires your Firebase Secret, you should only generate 30 | tokens on *trusted servers*. Never embed your Firebase Secret directly into your application and 31 | never share your Firebase Secret with a connected client. 32 | 33 | 34 | ## Generating Tokens 35 | 36 | To generate tokens, you'll need your Firebase Secret which you can find by entering your Firebase 37 | URL into a browser and clicking the "Secrets" tab on the left-hand navigation menu. 38 | 39 | Once you've downloaded the library and grabbed your Firebase Secret, you can generate a token with 40 | this snippet of Python code: 41 | 42 | ```python 43 | from firebase_token_generator import create_token 44 | 45 | auth_payload = {"uid": "1", "auth_data": "foo", "other_auth_data": "bar"} 46 | token = create_token("", auth_payload) 47 | ``` 48 | 49 | The payload passed to `create_token()` is made available for use within your 50 | security rules via the [`auth` variable](https://www.firebase.com/docs/security/api/rule/auth.html). 51 | This is how you pass trusted authentication details (e.g. the client's user ID) 52 | to your Firebase security rules. The payload can contain any data of your 53 | choosing, however it must contain a "uid" key, which must be a string of less 54 | than 256 characters. The generated token must be less than 1024 characters in 55 | total. 56 | 57 | 58 | ## Token Options 59 | 60 | A second `options` argument can be passed to `create_token()` to modify how Firebase treats the 61 | token. Available options are: 62 | 63 | * **expires** (int or datetime) - A timestamp (as number of seconds since the epoch) or `datetime` 64 | denoting the time after which this token should no longer be valid. 65 | 66 | * **notBefore** (int or datetime) - A timestamp (as number of seconds since the epoch) or `datetime` 67 | denoting the time before which this token should be rejected by the server. 68 | 69 | * **admin** (bool) - Set to `True` if you want to disable all security rules for this client. This 70 | will provide the client with read and write access to your entire Firebase. 71 | 72 | * **debug** (bool) - Set to `True` to enable debug output from your security rules. You should 73 | generally *not* leave this set to `True` in production (as it slows down the rules implementation 74 | and gives your users visibility into your rules), but it can be helpful for debugging. 75 | 76 | * **simulate** (bool) - If `True`, Firebase will run security rules but not actually make any 77 | data changes. Note that this is internal-only for now. 78 | 79 | Here is an example of how to use the second `options` argument: 80 | 81 | ```python 82 | from firebase_token_generator import create_token 83 | 84 | auth_payload = {"uid": "1", "auth_data": "foo", "other_auth_data": "bar"} 85 | options = {"admin": True} 86 | token = create_token("", auth_payload, options) 87 | ``` 88 | -------------------------------------------------------------------------------- /firebase_token_generator.py: -------------------------------------------------------------------------------- 1 | try: 2 | basestring 3 | except NameError: # Python 3 4 | basestring = str 5 | from array import array 6 | from base64 import urlsafe_b64encode 7 | import hashlib 8 | import hmac 9 | import sys 10 | try: 11 | import json 12 | except ImportError: 13 | import simplejson as json 14 | import calendar 15 | import time 16 | import datetime 17 | 18 | __all__ = ['create_token'] 19 | 20 | TOKEN_VERSION = 0 21 | TOKEN_SEP = '.' 22 | 23 | CLAIMS_MAP = { 24 | 'expires': 'exp', 25 | 'notBefore': 'nbf', 26 | 'admin': 'admin', 27 | 'debug': 'debug', 28 | 'simulate': 'simulate' 29 | } 30 | 31 | def create_token(secret, data, options=None): 32 | """ 33 | Generates a secure authentication token. 34 | 35 | Our token format follows the JSON Web Token (JWT) standard: 36 | header.claims.signature 37 | 38 | Where: 39 | 1) "header" is a stringified, base64-encoded JSON object containing version and algorithm information. 40 | 2) "claims" is a stringified, base64-encoded JSON object containing a set of claims: 41 | Library-generated claims: 42 | "iat" -> The issued at time in seconds since the epoch as a number 43 | "d" -> The arbitrary JSON object supplied by the user. 44 | User-supplied claims (these are all optional): 45 | "exp" (optional) -> The expiration time of this token, as a number of seconds since the epoch. 46 | "nbf" (optional) -> The "not before" time before which the token should be rejected (seconds since the epoch) 47 | "admin" (optional) -> If set to true, this client will bypass all security rules (use this to authenticate servers) 48 | "debug" (optional) -> "set to true to make this client receive debug information about security rule execution. 49 | "simulate" (optional, internal-only for now) -> Set to true to neuter all API operations (listens / puts 50 | will run security rules but not actually write or return data). 51 | 3) A signature that proves the validity of this token (see: http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-07) 52 | 53 | For base64-encoding we use URL-safe base64 encoding. This ensures that the entire token is URL-safe 54 | and could, for instance, be placed as a query argument without any encoding (and this is what the JWT spec requires). 55 | 56 | Args: 57 | secret - the Firebase Application secret 58 | data - a json serializable object of data to be included in the token 59 | options - An optional dictionary of additional claims for the token. Possible keys include: 60 | a) "expires" -- A datetime or timestamp (as a number of seconds since the epoch) denoting a time after 61 | which this token should no longer be valid. 62 | b) "notBefore" -- A datetime or timestamp (as a number of seconds since the epoch) denoting a time before 63 | which this token should be rejected by the server. 64 | c) "admin" -- Set to true to bypass all security rules (use this for your trusted servers). 65 | d) "debug" -- Set to true to enable debug mode (so you can see the results of Rules API operations) 66 | e) "simulate" -- (internal-only for now) Set to true to neuter all API operations (listens / puts 67 | will run security rules but not actually write or return data) 68 | Returns: 69 | A signed Firebase Authentication Token 70 | Raises: 71 | ValueError: if an invalid key is specified in options 72 | 73 | """ 74 | if not isinstance(secret, basestring): 75 | raise ValueError("firebase_token_generator.create_token: secret must be a string.") 76 | if not options and not data: 77 | raise ValueError("firebase_token_generator.create_token: data is empty and no options are set. This token will have no effect on Firebase."); 78 | if not options: 79 | options = {} 80 | is_admin_token = ('admin' in options and options['admin'] == True) 81 | _validate_data(data, is_admin_token) 82 | claims = _create_options_claims(options) 83 | claims['v'] = TOKEN_VERSION 84 | claims['iat'] = int(time.time()) 85 | claims['d'] = data 86 | 87 | token = _encode_token(secret, claims) 88 | if len(token) > 1024: 89 | raise RuntimeError("firebase_token_generator.create_token: generated token is too long.") 90 | return token 91 | 92 | def _validate_data(data, is_admin_token): 93 | if data is not None and not isinstance(data, dict): 94 | raise ValueError("firebase_token_generator.create_token: data must be a dictionary") 95 | contains_uid = (data is not None and 'uid' in data) 96 | if ((not contains_uid and not is_admin_token) or (contains_uid and not isinstance(data['uid'], basestring))): 97 | raise ValueError("firebase_token_generator.create_token: data must contain a \"uid\" key that must be a string.") 98 | if (contains_uid and (len(data['uid']) > 256)): 99 | raise ValueError("firebase_token_generator.create_token: data must contain a \"uid\" key that must not be longer than 256 bytes.") 100 | 101 | def _create_options_claims(opts): 102 | claims = {} 103 | for k in opts: 104 | if (isinstance(opts[k], datetime.datetime)): 105 | opts[k] = int(calendar.timegm(opts[k].utctimetuple())) 106 | if k in CLAIMS_MAP: 107 | claims[CLAIMS_MAP[k]] = opts[k] 108 | else: 109 | raise ValueError('Unrecognized Option: %s' % k) 110 | return claims 111 | 112 | if sys.version_info < (2, 7): 113 | def _encode(bytes_data): 114 | # Python 2.6 has problems with bytearrays in b64 115 | encoded = urlsafe_b64encode(bytes(bytes_data)) 116 | return encoded.decode('utf-8').replace('=', '') 117 | else: 118 | def _encode(bytes): 119 | encoded = urlsafe_b64encode(bytes) 120 | return encoded.decode('utf-8').replace('=', '') 121 | 122 | 123 | def _encode_json(obj): 124 | return _encode(bytearray(json.dumps(obj, separators=(',',':')), 'utf-8')) 125 | 126 | def _sign(secret, to_sign): 127 | def portable_bytes(s): 128 | try: 129 | return bytes(s, 'utf-8') 130 | except TypeError: 131 | return bytes(s) 132 | return _encode(hmac.new(portable_bytes(secret), portable_bytes(to_sign), hashlib.sha256).digest()) 133 | 134 | def _encode_token(secret, claims): 135 | encoded_header = _encode_json({'typ': 'JWT', 'alg': 'HS256'}) 136 | encoded_claims = _encode_json(claims) 137 | secure_bits = '%s%s%s' % (encoded_header, TOKEN_SEP, encoded_claims) 138 | sig = _sign(secret, secure_bits) 139 | return '%s%s%s' % (secure_bits, TOKEN_SEP, sig) 140 | -------------------------------------------------------------------------------- /firebase_token_generator_test.py: -------------------------------------------------------------------------------- 1 | try: 2 | basestring 3 | except NameError: # Python 3 4 | basestring = str 5 | from firebase_token_generator import create_token 6 | import unittest 7 | 8 | class TestTokenGenerator(unittest.TestCase): 9 | 10 | def test_smoke_test(self): 11 | token = create_token("barfoo", {"uid": "foo"}) 12 | self.assertIsInstance(token, basestring) 13 | 14 | def test_malformed_key(self): 15 | with self.assertRaises(ValueError): 16 | token = create_token(1234567890, {"uid": "foo"}) 17 | 18 | def test_no_uid(self): 19 | with self.assertRaises(ValueError): 20 | token = create_token("barfoo", {"blah": 5}) 21 | 22 | def test_invalid_uid(self): 23 | with self.assertRaises(ValueError): 24 | token = create_token("barfoo", {"uid": 5, "blah": 5}) 25 | 26 | def test_uid_max_length(self): 27 | #length: 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 256 28 | token = create_token("barfoo", {"uid": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456"}) 29 | self.assertIsInstance(token, basestring) 30 | 31 | def test_uid_too_long(self): 32 | with self.assertRaises(ValueError): 33 | #length: 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 257 34 | token = create_token("barfoo", {"uid": "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"}) 35 | 36 | def test_uid_min_length(self): 37 | token = create_token("barfoo", {"uid": ""}) 38 | self.assertIsInstance(token, basestring) 39 | 40 | def test_token_too_long(self): 41 | with self.assertRaises(RuntimeError): 42 | create_token("barfoo", {"uid": "blah", "long_var": "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345612345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234561234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456"}) 43 | 44 | def test_no_uid_with_admin(self): 45 | token = create_token("barfoo", None, {"admin": True}) 46 | self.assertIsInstance(token, basestring) 47 | token = create_token("barfoo", {}, {"admin": True}) 48 | self.assertIsInstance(token, basestring) 49 | token = create_token("barfoo", {"foo": "bar"}, {"admin": True}) 50 | self.assertIsInstance(token, basestring) 51 | 52 | def test_invalid_uid_with_admin(self): 53 | with self.assertRaises(ValueError): 54 | token = create_token("barfoo", {"uid": 1}, {"admin": True}) 55 | with self.assertRaises(ValueError): 56 | token = create_token("barfoo", {"uid": None}, {"admin": True}) 57 | with self.assertRaises(ValueError): 58 | token = create_token("barfoo", "foo", {"admin": True}) 59 | 60 | if __name__ == '__main__': 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | import os 3 | 4 | def read(fname): 5 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 6 | 7 | setup( 8 | name='firebase-token-generator', 9 | version='2.0.1', 10 | author='Firebase', 11 | zip_safe=False, 12 | py_modules=['firebase_token_generator'], 13 | license='LICENSE', 14 | url='https://github.com/firebase/firebase-token-generator-python', 15 | description='A utility to generate signed Firebase Authentication Tokens', 16 | classifiers=[ 17 | 'Programming Language :: Python', 18 | 'Programming Language :: Python :: 2', 19 | 'Programming Language :: Python :: 3', 20 | ], 21 | long_description=read('README') 22 | ) 23 | --------------------------------------------------------------------------------