firebase authentication with google python endpoints - google-app-engine

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)

Related

How to I send a authentication token from firebase to my django backend so that it may add that user in the admin of django?

Like this is the code I have used in my backend part of Django..My frontend is in ReactJS
default_app = firebase_admin.initialize_app(cred)
class FirebaseAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.META.get("HTTP_AUTHORIZATION")
if not auth_header:
raise NoAuthToken("No auth token provided")
id_token = auth_header.split(" ").pop()
decoded_token = None
try:
decoded_token = auth.verify_id_token(id_token)
except Exception:
raise InvalidAuthToken("Invalid auth token")
pass
if not id_token or not decoded_token:
return None
try:
uid = decoded_token.get("uid")
except Exception:
raise FirebaseError()
user, created = User.objects.get_or_create(username=uid)
user.profile.last_activity = timezone.localtime()
return (user, None)
I have just written this in backend and now am wondering where and how do I call this and how will be token received here.Thank you so much in advance

Message insert - adding external label

I'm using gmail API to insert an email to the inbox. When the From domain is outside the organization, I expect the external label to be added but it's not.
I'm using python and everything else is working, I am using a service account and able to impersonate on behalf the user's email and I'm using labelIds of ['INBOX', 'UNREAD'] and I need the newly external label as well but couldn't figure a way to add it through the API.
This feature is turned ON for the workspace.
Update - adding my code:
from googleapiclient import discovery, errors
from google.oauth2 import service_account
from email.mime.text import MIMEText
import base64
SERVICE_ACCOUNT_FILE = 'insert-messages-91e77b62878f.json'
SCOPES = ['https://www.googleapis.com/auth/gmail.insert']
def validationService():
# Set the credentials
credentials = service_account.Credentials.\
from_service_account_file(SERVICE_ACCOUNT_FILE, scopes= SCOPES)
# Delegate the credentials to the user you want to impersonate
delegated_credentials = credentials.with_subject('<some_user>')
service = discovery.build('gmail', 'v1', credentials=delegated_credentials)
return service
def SendMessage(service, message):
message = service.users().messages().insert(userId='me', body=message).execute() # me will use <some_user> from above
return message
def CreateMessage(sender, to, subject, message_text):
message = MIMEText(message_text)
message['To'] = to
message['From'] = sender
message['Subject'] = subject
return {'raw': base64.urlsafe_b64encode(message.as_string().encode()).decode(), 'labelIds': ['INBOX', 'UNREAD']}
def main():
try:
service = validationService()
email = CreateMessage('some#external.com', "<some_user>", "Test", "This is a test")
email_sent = SendMessage(service, email)
print('Message Id:', email_sent['id'])
except errors.HttpError as err:
print('\n---------------You have the following error-------------')
print(err)
print('---------------You have the following error-------------\n')
if __name__ == '__main__':
main()

Why is AppEngine User null when passed a valid access token from Auth0

We are using Google AppEngine (Java) with Extensible Service Proxy (ESP) as our backend, and Auth0 as our authenticator.
We can successfully invoke Auth0 from our web app and authenticate using email/password, GoogleAccount and FaceBook and receive an id_token and an access_token. As expected when decoded via https://jwt.io/ we can see that the
id_token contains contains (iss, sub, aud, iat, exp) as well as email address and other user info.
access_token contains all the fields required by EndPoints (iss, sub, aud, iat, exp) but no email address.
When we invoke our AppEngine EndPoint providing the access_token as a bearer token, the endpoint gets invoked but the User is always null. There are no errors in the AppEngine logs.
We had expected that the ESP would validate the access_token and provide our AppEngine method with the validated User containing the credentials from Auth0.
Is our expectation incorrect, or have we misconfigured something?
What do we need to do to receive a User authorised by Auth0 in our AppEngine method?
#Api(
name = "ourInterface",
version = "v1",
namespace = #ApiNamespace(ownerDomain = "our-domain.com", ownerName = "Our Company"),
authenticators = {EspAuthenticator.class},
// Authenticate using Auth0
issuers = {
#ApiIssuer(
name = "auth0",
issuer = "https://our-domain.au.auth0.com/",
jwksUri = "https://our-domain.au.auth0.com/.well-known/jwks.json")
},
issuerAudiences = {
#ApiIssuerAudience(
name = "auth0",
audiences = "https://our-domain.appspot.com/ourInterface" // The interface specified in Applications\APIs in Auth0
)
}
)
public class OurInterface {
#ApiMethod(name = "postSomeStuff", path = "postSomeStuff", httpMethod =HttpMethod.POST)
public SomeResponse postSomeStuff(SomeRequest request, User user) {
If (user == null) {
throw new IllegalStatException(“Expected a User”);
}
// Take user and request params and confirm User has access to those resources
// Then do something useful
}
}
Update 1
If we add
authLevel = AuthLevel.REQUIRED,
to the Api annotation, then our method is not invoked and the ESP fails the request with
com.google.api.server.spi.SystemService invokeServiceMethod: exception occurred while calling backend method
com.google.api.server.spi.response.UnauthorizedException: Valid user credentials are required.
at com.google.api.server.spi.request.ServletRequestParamReader.deserializeParams(ServletRequestParamReader.java:161)
at com.google.api.server.spi.request.RestServletRequestParamReader.read(RestServletRequestParamReader.java:161)
at com.google.api.server.spi.SystemService.invokeServiceMethod(SystemService.java:347)
at com.google.api.server.spi.handlers.EndpointsMethodHandler$RestHandler.handle(EndpointsMethodHandler.java:127)
at com.google.api.server.spi.handlers.EndpointsMethodHandler$RestHandler.handle(EndpointsMethodHandler.java:110)
at com.google.api.server.spi.dispatcher.PathDispatcher.dispatch(PathDispatcher.java:50)
at com.google.api.server.spi.EndpointsServlet.service(EndpointsServlet.java:80)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:848)

upload text file to google drive using python every 5 seconds

how can i modify the following python code to upload the ecg.txt to google drive every 5 seconds where i update this text file. the code now ask for Oauth every time it upload the file , i want it to ask for authentication only in the first time.
#!/usr/bin/python
import httplib2
import pprint
from apiclient.discovery import build
from apiclient.http import MediaFileUpload
from oauth2client.client import OAuth2WebServerFlow
# Copy your credentials from the console
CLIENT_ID = XXXXXX
CLIENT_SECRET = XXXXX
# Check https://developers.google.com/drive/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
# Redirect URI for installed apps
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Path to the file to upload
FILENAME = 'ecg.txt'
# Run through the OAuth flow and retrieve credentials
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, REDIRECT_URI)
authorize_url = flow.step1_get_authorize_url()
print 'Go to the following link in your browser: ' + authorize_url
code = raw_input('Enter verification code: ').strip()
credentials = flow.step2_exchange(code)
# Create an httplib2.Http object and authorize it with our credentials
http = httplib2.Http()
http = credentials.authorize(http)
drive_service = build('drive', 'v2', http=http)
# Insert a file
media_body = MediaFileUpload(FILENAME, mimetype='text/plain', resumable=True)
body = {
'title': 'My document',
'description': 'A test document',
'mimeType': 'text/plain'
}
file = drive_service.files().insert(body=body, media_body=media_body).execute()
pprint.pprint(file)
If you use the decorator as seen in the video bellow it stores your access token in the db for you and handles refreshing when needed.
https://www.youtube.com/watch?v=HoUdWBzUZ-M
https://developers.google.com/api-client-library/python/guide/google_app_engine
from oauth2client.appengine import OAuth2Decorator
decorator = OAuth2Decorator(client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
scope=OAUTH_SCOPE,
callback_path=REDIRECT_URI)
class MainPage(webapp2.RequestHandler):
#decorator.oauth_required #Simply place this above any function that requires login.
def get(self):

Facebook login in Google Cloud Endpoints

Can someone explain steps to implement login process with other OAuth2 providers
This link Google Cloud Endpoints with another oAuth2 provider gives little info about writing custom authentication, but I guess for beginner like me that's not enough, please give detailed steps.
Especially, interested in Facebook.
You need to implement Facebook's client side APIs according to their documentation and the environment you are deploying your client app to (Browser vs iOS vs Android). This includes registering your app with them. Your registered app will direct the user to go through an authentication flow and at the end of it your client app will have access to a short-lived access token. Facebook has multiple types of access tokens, but the one it sounds like you're interested in is called a User Access Token since it identifies an authorized user.
Pass the access token to your Cloud Endpoints API via a field or header. Inside of your API code receive the access token and implement Facebook's API that checks the validity of the access token. The first answer on this SO question makes it look rather easy, but you probably want to reference their documentation again. If that check passes then you would run your API code, otherwise throw an exception.
You will typically also want to implement a caching mechanism to prevent calling the Facebook server side validation API for each Cloud Endpoints request.
Finally, I mentioned that your client app has a short lived token. If you have a client app that is browser-based then you will probably want to upgrade that to a long lived token. Facebook has a flow for that as well which involves your API code requesting a long lived token with the short lived one. You would then need to transfer that long lived token back to the client app to use for future Cloud Endpoints API calls.
If your client app is iOS or Android based then your tokens are managed by Facebook code and you simply request access tokens from the respective APIs when you need them.
So I actually tried to implement that custom authentication flow. It seems working fine although there might be further consideration on security side.
First, user go to my application and authenticate with facebook, the application got his user_id and access_token. Then the application call auth API to the server with these info.
class AuthAPI(remote.Service):
#classmethod
def validate_facebook_user(cls, user_id, user_token):
try:
graph = facebook.GraphAPI(user_token)
profile = graph.get_object("me", fields='email, first_name, last_name, username')
except facebook.GraphAPIError, e:
return (None, None, str(e))
if (profile is not None):
# Check if match user_id
if (profile.get('id', '') == user_id):
# Check if user exists in our own datastore
(user, token) = User.get_by_facebook_id(user_id, 'auth', user_token)
# Create new user if not
if user is None:
#print 'Create new user'
username = profile.get('username', '')
password = security.generate_random_string(length=20)
unique_properties = ['email_address']
if (username != ''):
(is_created, user) = User.create_user(
username,
unique_properties,
email_address = profile.get('email', ''),
name = profile.get('first_name', ''),
last_name = profile.get('last_name', ''),
password_raw = password,
facebook_id = user_id,
facebook_token = user_token,
verified=False,
)
if is_created==False:
return (None, None, 'Cannot create user')
token_str = User.create_auth_token(user.get_id())
#print (user, token_str)
# Return if user exists
if token is not None:
return (user, token.token, 'Successfully logged in')
else:
return (None, None, 'Invalid token')
return (None, None, 'Invalid facebook id and token')
# Return a user_id and token if authenticated successfully
LOGIN_REQ = endpoints.ResourceContainer(MessageCommon,
type=messages.StringField(2, required=True),
user_id=messages.StringField(3, required=False),
token=messages.StringField(4, required=False))
#endpoints.method(LOGIN_REQ, MessageCommon,
path='login', http_method='POST', name='login')
def login(self, request):
type = request.type
result = MessageCommon()
# TODO: Change to enum type if we have multiple auth ways
if (type == "facebook"):
# Facebook user validation
user_id = request.user_id
access_token = request.token
(user_obj, auth_token, msg) = self.validate_facebook_user(user_id, access_token)
# If we can get user data
if (user_obj is not None and auth_token is not None):
print (user_obj, auth_token)
result.success = True
result.message = msg
result.data = json.dumps({
'user_id': user_obj.get_id(),
'user_token': auth_token
})
# If we cannot
else:
result.success = False
result.message = msg
return result
In addition to this, you might want to implement the normal user authentication flow following instruction here: http://blog.abahgat.com/2013/01/07/user-authentication-with-webapp2-on-google-app-engine/ .
This is because the user_id and user_token that I obtain was provided by webapp2_extras.appengine.auth.
Implementation of User.get_by_facebook_id:
class User(webapp2_extras.appengine.auth.models.User):
#classmethod
def get_by_facebook_id(cls, fb_id, subj='auth', fb_token=""):
u = cls.query(cls.facebook_id==fb_id).get()
if u is not None:
user_id = u.key.id()
# TODO: something better here, now just append the facebook_token to a prefix
token_str = "fbtk" + str(fb_token)
# get this token if it exists
token_key = cls.token_model.get(user_id, subj, token_str)
print token_key, fb_token
if token_key is None:
# return a token that created from access_token string
if (fb_token == ""):
return (None, None)
else:
token = cls.token_model.create(user_id, subj, token_str)
else:
token = token_key
return (u, token)
return (None, None)
Server verify if the user is authenticated with facebook once more time. If it passes, user is considered logged in. In this case, server pass back a user_token (generated based on facebook_token) and user_id from our datastore.
Any further API calls should use this user_id and user_token
def get_request_class(messageCls):
return endpoints.ResourceContainer(messageCls,
user_id=messages.IntegerField(2, required=False),
user_token=messages.StringField(3, required=False))
def authenticated_required(endpoint_method):
"""
Decorator that check if API calls are authenticated
"""
def check_login(self, request, *args, **kwargs):
try:
user_id = request.user_id
user_token = request.user_token
if (user_id is not None and user_token is not None):
# Validate user
(user, timestamp) = User.get_by_auth_token(user_id, user_token)
if user is not None:
return endpoint_method(self, request, user, *args, **kwargs )
raise endpoints.UnauthorizedException('Invalid user_id or access_token')
except:
raise endpoints.UnauthorizedException('Invalid access token')
#endpoints.api(name='blah', version='v1', allowed_client_ids = env.CLIENT_IDS, auth=AUTH_CONFIG)
class BlahApi(remote.Service):
# Add user_id/user_token to the request
Blah_Req = get_request_class(message_types.VoidMessage)
#endpoints.method(Blah_Req, BlahMessage, path='list', name='list')
#authenticated_required
def blah_list(self, request, user):
newMessage = BlahMessage(Blah.query().get())
return newMessage
Note:
I am using this library to handle facebook authentication checking on server: https://github.com/pythonforfacebook/facebook-sdk
I implemented this use case by adding a webapp2 handler to exchange the Facebook access token for one generated by my own application, using the SimpleAuth mixin for verification:
class AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):
"""Authenticates a user to the application via a third-party provider.
The return value of this request is an OAuth token response.
Only a subset of the PROVIDERS specified in SimpleAuthHandler are currently supported.
Tested providers: Facebook
"""
def _on_signin(self, data, auth_info, provider):
# Create the auth ID format used by the User model
auth_id = '%s:%s' % (provider, data['id'])
user_model = auth.get_auth().store.user_model
user = user_model.get_by_auth_id(auth_id)
if not user:
ok, user = user_model.create_user(auth_id)
if not ok:
logging.error('Unable to create user for auth_id %s' % auth_id)
self.abort(500, 'Unable to create user')
return user
def post(self):
# Consider adding a check for a valid endpoints client ID here as well.
access_token = self.request.get('x_access_token')
provider = self.request.get('x_provider')
if provider not in self.PROVIDERS or access_token is None:
self.abort(401, 'Unknown provider or access token')
auth_info = {'access_token': access_token}
fetch_user_info = getattr(self, '_get_%s_user_info' % provider)
user_info = fetch_user_info(auth_info)
if 'id' in user_info:
user = self._on_signin(user_info, auth_info, provider)
token = user.create_bearer_token(user.get_id())
self.response.content_type = 'application/json'
self.response.body = json.dumps({
'access_token': token.token,
'token_type': 'Bearer',
'expires_in': token.bearer_token_timedelta.total_seconds(),
'refresh_token': token.refresh_token
})
else:
self.abort(401, 'Access token is invalid')
The exchanged access token can be passed on each endpoints request in the Authorization header, or as part of the RPC message if you prefer. Here's an example of reading it from the header:
def get_current_user():
token = os.getenv('HTTP_AUTHORIZATION')
if token:
try:
token = token.split(' ')[1]
except IndexError:
pass
user, _ = User.get_by_bearer_token(token)
return user
I posted the complete example on Github: https://github.com/loudnate/appengine-endpoints-auth-example
So no body has thrown a light on the android client side stuff. Since, you do not require Google login in this case hence the code for getting api handle will look like:
private Api getEndpointsApiHandle() {
Api.Builder api = new Api.Builder(HTTP_TRANSPORT, JSON_FACTORY, null);
api.setRootUrl(yourRootUrl);
return api.build();
}
If you notice; You will require to pass null as the Credential. This code works like a charm
I too have written my own solution for this problem. You can check out the code here: https://github.com/rggibson/Authtopus
Authtopus is a python library for custom authentication with Google Cloud Endpoints. It supports basic username and password registrations + logins, as well as logins via Facebook and Google (and could probably be extended to support other social providers without too much hassle). I know this doesn't directly answer the original question, but it seems related enough that I thought I'd share.

Resources