I am integrating federated login using firebase authentication and I have REST API's on google appengine endpoints and other servers that am accessing.
Conceptually, the user logs in via firebase google signin, the angular web client gets the id token using the firebase client libraries and sends it to the google endpoint. The google endpoint verifies the id token to grant access.
After reading and working through various articles I have finally got something working, however I have very little experience in this area of authorization and was wondering if there was a better way to do this.
Code to get the id token on the client side is
firebase.auth().currentUser.getToken(true).then(function(idToken) {
// Send token to your backend via HTTPS
}
This code works and I get the correct id token.
For now I am using postman to send this id token to the app engine endpoint in the authorization header.
Google appengine endpoint code is
# only showing imports specifically required for cryptography
import jwt
import jwt.algorithms
from jwt.contrib.algorithms.pycrypto import RSAAlgorithm
jwt.register_algorithm('RS256', RSAAlgorithm(RSAAlgorithm.SHA256))
logging.debug("algorithms = ", jwt.algorithms.get_default_algorithms())
#had to install pyjwt as a library in appengine https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27
import json
import base64
from Crypto.Util.asn1 import DerSequence
from Crypto.PublicKey import RSA
from binascii import a2b_base64
import httplib
""" Message to accept a JWT for access """
class UserJWTMsg(messages.Message):
token = messages.StringField(1)
class UserMsg(messages.Message):
name = messages.StringField(1)
# [START user_api]
@endpoints.api(name='user', version='v1')
class UserApi(remote.Service):
""" Access with id Token """
@endpoints.method(
message_types.VoidMessage,
UserMsg,
path='access-with-idToken',
http_method='GET',
name='access-with-idToken')
def access_with_id_token(self, request):
id_token = self.request_state.headers.get('authorization')
id_token_parts = id_token.split('.')
#get key id from the token header
#check if padding needs to be added to the id token header
padding_count = 4 - len(id_token_parts[0])%4
if padding_count:
id_token_header = id_token_parts[0] + b'='*padding_count
#decode base64 header
decoded_id_token_header_json = base64.b64decode(id_token_header)
decoded_id_token_header = json.loads(decoded_id_token_header_json)
#get key id from the header
key_id = decoded_id_token_header['kid']
#get certificate for the given key id, #cert = CERTS.get(key_id)
cert = getCertificate(key_id)
#get the public key from certificate
public_key = pubKeyFrmCert(cert)
#decode / verify id token
decoded_payload = jwt.decode(id_token, public_key, audience='audience')
return UserMsg(name=json.dumps(decoded_payload));
""" get Certificate for key id """
def getCertificate(key_id):
c = httplib.HTTPSConnection("www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com")
c.request("GET", "/")
response = c.getresponse()
cert_str = response.read()
cert_obj = json.loads(cert_str)
cert = cert_obj.get(key_id)
return(cert)
""" return a public key from a X509 certificate """
""" http://stackoverflow.com/questions/12911373/how-do-i-use-a-x509-certificate-with-pycrypto """
def pubKeyFrmCert(cert):
lines = cert.replace(" ",'').split()
der = a2b_base64(''.join(lines[1:-1]))
# Extract subjectPublicKeyInfo field from X.509 certificate (see RFC3280)
cert = DerSequence()
cert.decode(der)
tbsCertificate = DerSequence()
tbsCertificate.decode(cert[0])
subjectPublicKeyInfo = tbsCertificate[6]
# Initialize RSA key
rsa_key = RSA.importKey(subjectPublicKeyInfo)
return(rsa_key)