Google App Engine - Secure Cookies - google-app-engine

I'd been searching for a way to do cookie based authentication/sessions in Google App Engine because I don't like the idea of memcache based sessions, and I also don't like the idea of forcing users to create google accounts just to use a website. I stumbled across someone's posting that mentioned some signed cookie functions from the Tornado framework and it looks like what I need. What I have in mind is storing a user's id in a tamper proof cookie, and maybe using a decorator for the request handlers to test the authentication status of the user, and as a side benefit the user id will be available to the request handler for datastore work and such. The concept would be similar to forms authentication in ASP.NET. This code comes from the web.py module of the Tornado framework.
According to the docstrings, it "Signs and timestamps a cookie so it cannot be forged" and
"Returns the given signed cookie if it validates, or None."
I've tried to use it in an App Engine Project, but I don't understand the nuances of trying to get these methods to work in the context of the request handler. Can someone show me the right way to do this without losing the functionality that the FriendFeed developers put into it? The set_secure_cookie, and get_secure_cookie portions are the most important, but it would be nice to be able to use the other methods as well.
#!/usr/bin/env python
import Cookie
import base64
import time
import hashlib
import hmac
import datetime
import re
import calendar
import email.utils
import logging
def _utf8(s):
if isinstance(s, unicode):
return s.encode("utf-8")
assert isinstance(s, str)
return s
def _unicode(s):
if isinstance(s, str):
try:
return s.decode("utf-8")
except UnicodeDecodeError:
raise HTTPError(400, "Non-utf8 argument")
assert isinstance(s, unicode)
return s
def _time_independent_equals(a, b):
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= ord(x) ^ ord(y)
return result == 0
def cookies(self):
"""A dictionary of Cookie.Morsel objects."""
if not hasattr(self,"_cookies"):
self._cookies = Cookie.BaseCookie()
if "Cookie" in self.request.headers:
try:
self._cookies.load(self.request.headers["Cookie"])
except:
self.clear_all_cookies()
return self._cookies
def _cookie_signature(self,*parts):
self.require_setting("cookie_secret","secure cookies")
hash = hmac.new(self.application.settings["cookie_secret"],
digestmod=hashlib.sha1)
for part in parts:hash.update(part)
return hash.hexdigest()
def get_cookie(self,name,default=None):
"""Gets the value of the cookie with the given name,else default."""
if name in self.cookies:
return self.cookies[name].value
return default
def set_cookie(self,name,value,domain=None,expires=None,path="/",
expires_days=None):
"""Sets the given cookie name/value with the given options."""
name = _utf8(name)
value = _utf8(value)
if re.search(r"[\x00-\x20]",name + value):
# Don't let us accidentally inject bad stuff
raise ValueError("Invalid cookie %r:%r" % (name,value))
if not hasattr(self,"_new_cookies"):
self._new_cookies = []
new_cookie = Cookie.BaseCookie()
self._new_cookies.append(new_cookie)
new_cookie[name] = value
if domain:
new_cookie[name]["domain"] = domain
if expires_days is not None and not expires:
expires = datetime.datetime.utcnow() + datetime.timedelta(
days=expires_days)
if expires:
timestamp = calendar.timegm(expires.utctimetuple())
new_cookie[name]["expires"] = email.utils.formatdate(
timestamp,localtime=False,usegmt=True)
if path:
new_cookie[name]["path"] = path
def clear_cookie(self,name,path="/",domain=None):
"""Deletes the cookie with the given name."""
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
self.set_cookie(name,value="",path=path,expires=expires,
domain=domain)
def clear_all_cookies(self):
"""Deletes all the cookies the user sent with this request."""
for name in self.cookies.iterkeys():
self.clear_cookie(name)
def set_secure_cookie(self,name,value,expires_days=30,**kwargs):
"""Signs and timestamps a cookie so it cannot be forged"""
timestamp = str(int(time.time()))
value = base64.b64encode(value)
signature = self._cookie_signature(name,value,timestamp)
value = "|".join([value,timestamp,signature])
self.set_cookie(name,value,expires_days=expires_days,**kwargs)
def get_secure_cookie(self,name,include_name=True,value=None):
"""Returns the given signed cookie if it validates,or None"""
if value is None:value = self.get_cookie(name)
if not value:return None
parts = value.split("|")
if len(parts) != 3:return None
if include_name:
signature = self._cookie_signature(name,parts[0],parts[1])
else:
signature = self._cookie_signature(parts[0],parts[1])
if not _time_independent_equals(parts[2],signature):
logging.warning("Invalid cookie signature %r",value)
return None
timestamp = int(parts[1])
if timestamp < time.time() - 31 * 86400:
logging.warning("Expired cookie %r",value)
return None
try:
return base64.b64decode(parts[0])
except:
return None
uid=1234|1234567890|d32b9e9c67274fa062e2599fd659cc14
Parts:
1. uid is the name of the key
2. 1234 is your value in clear
3. 1234567890 is the timestamp
4. d32b9e9c67274fa062e2599fd659cc14 is the signature made from the value and the timestamp

Tornado was never meant to work with App Engine (it's "its own server" through and through). Why don't you pick instead some framework that was meant for App Engine from the word "go" and is lightweight and dandy, such as tipfy? It gives you authentication using its own user system or any of App Engine's own users, OpenIn, OAuth, and Facebook; sessions with secure cookies or GAE datastore; and much more besides, all in a superbly lightweight "non-framework" approach based on WSGI and Werkzeug. What's not to like?!

For those who are still looking, we've extracted just the Tornado cookie implementation that you can use with App Engine at ThriveSmart. We're using it successfully on App Engine and will continue to keep it updated.
The cookie library itself is at:
http://github.com/thrivesmart/prayls/blob/master/prayls/lilcookies.py
You can see it in action in our example app that's included. If the structure of our repository ever changes, you can look for lilcookes.py within github.com/thrivesmart/prayls
I hope that's helpful to someone out there!

This works if anyone is interested:
from google.appengine.ext import webapp
import Cookie
import base64
import time
import hashlib
import hmac
import datetime
import re
import calendar
import email.utils
import logging
def _utf8(s):
if isinstance(s, unicode):
return s.encode("utf-8")
assert isinstance(s, str)
return s
def _unicode(s):
if isinstance(s, str):
try:
return s.decode("utf-8")
except UnicodeDecodeError:
raise HTTPError(400, "Non-utf8 argument")
assert isinstance(s, unicode)
return s
def _time_independent_equals(a, b):
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= ord(x) ^ ord(y)
return result == 0
class ExtendedRequestHandler(webapp.RequestHandler):
"""Extends the Google App Engine webapp.RequestHandler."""
def clear_cookie(self,name,path="/",domain=None):
"""Deletes the cookie with the given name."""
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
self.set_cookie(name,value="",path=path,expires=expires,
domain=domain)
def clear_all_cookies(self):
"""Deletes all the cookies the user sent with this request."""
for name in self.cookies.iterkeys():
self.clear_cookie(name)
def cookies(self):
"""A dictionary of Cookie.Morsel objects."""
if not hasattr(self,"_cookies"):
self._cookies = Cookie.BaseCookie()
if "Cookie" in self.request.headers:
try:
self._cookies.load(self.request.headers["Cookie"])
except:
self.clear_all_cookies()
return self._cookies
def _cookie_signature(self,*parts):
"""Hashes a string based on a pass-phrase."""
hash = hmac.new("MySecretPhrase",digestmod=hashlib.sha1)
for part in parts:hash.update(part)
return hash.hexdigest()
def get_cookie(self,name,default=None):
"""Gets the value of the cookie with the given name,else default."""
if name in self.request.cookies:
return self.request.cookies[name]
return default
def set_cookie(self,name,value,domain=None,expires=None,path="/",expires_days=None):
"""Sets the given cookie name/value with the given options."""
name = _utf8(name)
value = _utf8(value)
if re.search(r"[\x00-\x20]",name + value): # Don't let us accidentally inject bad stuff
raise ValueError("Invalid cookie %r:%r" % (name,value))
new_cookie = Cookie.BaseCookie()
new_cookie[name] = value
if domain:
new_cookie[name]["domain"] = domain
if expires_days is not None and not expires:
expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days)
if expires:
timestamp = calendar.timegm(expires.utctimetuple())
new_cookie[name]["expires"] = email.utils.formatdate(timestamp,localtime=False,usegmt=True)
if path:
new_cookie[name]["path"] = path
for morsel in new_cookie.values():
self.response.headers.add_header('Set-Cookie',morsel.OutputString(None))
def set_secure_cookie(self,name,value,expires_days=30,**kwargs):
"""Signs and timestamps a cookie so it cannot be forged"""
timestamp = str(int(time.time()))
value = base64.b64encode(value)
signature = self._cookie_signature(name,value,timestamp)
value = "|".join([value,timestamp,signature])
self.set_cookie(name,value,expires_days=expires_days,**kwargs)
def get_secure_cookie(self,name,include_name=True,value=None):
"""Returns the given signed cookie if it validates,or None"""
if value is None:value = self.get_cookie(name)
if not value:return None
parts = value.split("|")
if len(parts) != 3:return None
if include_name:
signature = self._cookie_signature(name,parts[0],parts[1])
else:
signature = self._cookie_signature(parts[0],parts[1])
if not _time_independent_equals(parts[2],signature):
logging.warning("Invalid cookie signature %r",value)
return None
timestamp = int(parts[1])
if timestamp < time.time() - 31 * 86400:
logging.warning("Expired cookie %r",value)
return None
try:
return base64.b64decode(parts[0])
except:
return None
It can be used like this:
class MyHandler(ExtendedRequestHandler):
def get(self):
self.set_cookie(name="MyCookie",value="NewValue",expires_days=10)
self.set_secure_cookie(name="MySecureCookie",value="SecureValue",expires_days=10)
value1 = self.get_cookie('MyCookie')
value2 = self.get_secure_cookie('MySecureCookie')

If you only want to store the user's user ID in the cookie (presumably so you can look their record up in the datastore), you don't need 'secure' or tamper-proof cookies - you just need a namespace that's big enough to make guessing user IDs impractical - eg, GUIDs, or other random data.
One pre-made option for this, which uses the datastore for session storage, is Beaker. Alternately, you could handle this yourself with set-cookie/cookie headers, if you really just need to store their user ID.

Someone recently extracted the authentication and session code from Tornado and created a new library specifically for GAE.
Perhaps this is more then you need, but since they did it specifically for GAE you shouldn't have to worry about adapting it yourself.
Their library is called gaema. Here is their announcement in the GAE Python group on 4 Mar 2010:
http://groups.google.com/group/google-appengine-python/browse_thread/thread/d2d6c597d66ecad3/06c6dc49cb8eca0c?lnk=gst&q=tornado#06c6dc49cb8eca0c

Related

Get all active user sessions webapp2

I am extending the dispatch method as specified in Webapp2 Sessions documentation:
https://webapp2.readthedocs.io/en/latest/_modules/webapp2_extras/sessions.html#SessionStore
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session()
I want to get all the active sessions of a particular user for some purpose. How do I do that?
Try the UserToken model.
from webapp2_extras.appengine.auth.models import UserToken
def all_sessions_for_user(user, _limit=100):
return UserToken.query(UserToken.user == str(user.key.id()).fetch(limit=_limit)
EDIT - OPTION 2
Then, I believe the only way is to define your own session_store backend, most likely using the datastore. There is a datastore backend that webapp2 provides (backend options are securecookie, datastore, and memcache), but it doesn't have a user field.
So you would need to subclass both Session and DatastoreSessionFactory to add it:
from webapp2_extras import auth
from webapp2_extras.appengine.sessions_ndb import Session, DatastoreSessionFactory
class MySession(Session):
user_id = ndb.KeyProperty(User)
class MyDatastoreSessionFactory(DatastoreSessionFactory):
session_model = MySession
def save_session(self, response):
"""extract user_id from self.session.data and pass it to self.session_model constructor"""
if self.session is None or not self.session.modified:
return
deserialized_session = auth.get_auth().store.deserialize_session(self.session.data)
self.session_model(id=self.sid, data=dict(self.session), user_id=deserialized_session['user_id'])._put()
self.session_store.save_secure_cookie(response, self.name, {'_sid': self.sid}, **self.session_args)
And then when you call get_session(), you just need to specify your new backend factory:
self.session_store.get_session(factory=MyDatastoreSessionFactory')

how does get_current_user work

I'm really confused how Google App Engine's User's get_current_user() works. I've looked around the internet at a bunch of different guides and tutorials about login and authentication, and many of them mention similar methods.
If there are a million users logged in to my application at the same time, how can that method possibly work? Does each user get their own instance of the server? How does the server know which client it is talking to?
It doesn't make sense to me at all.
When logging in (by clicking on the URL generated by create_login_url()) a cookie containing user identifying information is prepared and pushed on the client side, then used in subsequent requests until the user logs out or the cookie expires. Calling get_current_user() simply checks the cookie existance/information and responds accordingly.
On the development server the cookie is named dev_appserver_login. I can no longer check the cookie name on GAE as I switched away from the Users API.
The actual handling of the cookie seems to happen somewhere on the Users service backend, for example, by looking at the google/appengine/api/users.py file in the python SDK:
def create_login_url(dest_url=None, _auth_domain=None,
federated_identity=None):
...
req = user_service_pb.CreateLoginURLRequest()
resp = user_service_pb.CreateLoginURLResponse()
try:
apiproxy_stub_map.MakeSyncCall('user', 'CreateLoginURL', req, resp)
...
The end point (at least for the development server) seems to somehow land somewhere in google/appengine/tools/appengine_rpc.py, for example:
#staticmethod
def _CreateDevAppServerCookieData(email, admin):
"""Creates cookie payload data.
Args:
email: The user's email address.
admin: True if the user is an admin; False otherwise.
Returns:
String containing the cookie payload.
"""
if email:
user_id_digest = hashlib.md5(email.lower()).digest()
user_id = "1" + "".join(["%02d" % ord(x) for x in user_id_digest])[:20]
else:
user_id = ""
return "%s:%s:%s" % (email, bool(admin), user_id)
def _DevAppServerAuthenticate(self):
"""Authenticates the user on the dev_appserver."""
credentials = self.auth_function()
value = self._CreateDevAppServerCookieData(credentials[0], True)
self.extra_headers["Cookie"] = ('dev_appserver_login="%s"; Path=/;' % value)

GAE Python Webapp2 auth token doesn't register user nor delete old tokens in production

I'm using webapp2 for user authentication of Google App Engine, based on this article: http://blog.abahgat.com/2013/01/07/user-authentication-with-webapp2-on-google-app-engine/
Locally through the SDK, everything works exactly as expected. However when I deploy to production, the entities under UserToken only have {} as value for user. Because of this I can't properly use the authentication tokens.
What could influence this? The website is only accessible through HTTPS, but I can't imagine that that is causing it. Any input would be very much appreciated!
EDIT:
Adding requested code.
User model
import webapp2_extras.appengine.auth.models
from google.appengine.ext import ndb
from webapp2_extras import security
class User(webapp2_extras.appengine.auth.models.User):
def set_password(self, raw_password):
"""Sets the password for the current user
:param raw_password:
The raw password which will be hashed and stored
"""
self.password = security.generate_password_hash(raw_password, length=12)
#classmethod
def get_by_auth_token(cls, user_id, token, subject='auth'):
"""Returns a user object based on a user ID and token.
:param user_id:
The user_id of the requesting user.
:param token:
The token string to be verified.
:returns:
A tuple ``(User, timestamp)``, with a user object and
the token timestamp, or ``(None, None)`` if both were not found.
"""
token_key = cls.token_model.get_key(user_id, subject, token)
user_key = ndb.Key(cls, user_id)
# Use get_multi() to save a RPC call.
valid_token, user = ndb.get_multi([token_key, user_key])
if valid_token and user:
timestamp = int(time.mktime(valid_token.created.timetuple()))
return user, timestamp
return None, None
When creating the user, I use following code:
from webapp2_extras import sessions, auth
import webapp2_extras.appengine.auth.models
unique_properties = ['email_address']
user_data = self.user_model.create_user(email, unique_properties,
email_address=email, first_name=first_name, password_raw=password, verified=False)
The code that is then used to create a token for 'user' is:
user_id = user.get_id()
token = self.user_model.create_signup_token(user_id)
The problem I have exists in the signup process as well as e.g. the forgot password process. As far as I can see, this is where something goes wrong. Not sure what though.
I've checked to make sure that an id is actually passed, and it is. Funny enough, the keyname of the token does include the user id, however on production the user field is left empty. Working locally it does include the user field.

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.

Detecting first time login of user into application (Google Appengine)

My app requires users to login using their google account.
I have this set in my App.yamp file:
url: /user/.*
script: user.py
login: required
Now when any user tries to access files under /user/secret.py he will need to authenticate via google, which will redirect the user back to /user/secret.py after successful authentication. Now the problem I am facing is when the user is redirected back to the app, I cannot be sure if this is the first time the user has logged in or is it a regular user to my site who has come back again from just the user object which google passes using users.get_current_user() .
I thus need to maintain state in the datastore to check if the user already exists or not everytime. If he does not exist i need to create a new entry with other application specific settings.
My question is: Is there some easier way to handle this? without having to query the datastore to figure if this is a first time user or a regular one?
No, Google doesn't keep track of if a user has logged in to your app before. Since you presumably need to store some sort of state against the user, the simplest way is to try and retrieve the user's record from the datastore. If they don't have one, you can send them to the registration screen to gather this information. You can use memcache to cache a user's information and avoid extra datastore round-trips.
I tend to use my own user and session manangement
For my web handlers I will attach a decorator called session and one called authorize. The session decorator will attach a session to every request, and the authorize decorator will make sure that the user is authorised
(A word of caution, the authorize decorator is specific to how I develop my applications - the username being the first parameter in most requests)
So for example a web handler may look like:
class UserProfile(webapp.RequestHandler):
#session
#authorize
def get(self, user):
# Do some funky stuff
# The session is attached to the self object.
someObjectAttachedToSession = self.SessionObj.SomeStuff
self.response.out.write("hello %s" % user)
In the above code, the session decorator attaches some session stuff that I need based on the cookies that are present on the request. The authorize header will make sure that the user can only access the page if the session is the correct one.
The decorators code are below:
import functools
from model import Session
import logging
def authorize(redirectTo = "/"):
def factory(method):
'Ensures that when an auth cookie is presented to the request that is is valid'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
#Get the session parameters
auth_id = self.request.cookies.get('auth_id', '')
session_id = self.request.cookies.get('session_id', '')
#Check the db for the session
session = Session.GetSession(session_id, auth_id)
if session is None:
self.redirect(redirectTo)
return
else:
if session.settings is None:
self.redirect(redirectTo)
return
username = session.settings.key().name()
if len(args) > 0:
if username != args[0]:
# The user is allowed to view this page.
self.redirect(redirectTo)
return
result = method(self, *args, **kwargs)
return result
return wrapper
return factory
def session(method):
'Ensures that the sessions object (if it exists) is attached to the request.'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
#Get the session parameters
auth_id = self.request.cookies.get('auth_id', '')
session_id = self.request.cookies.get('session_id', '')
#Check the db for the session
session = Session.GetSession(session_id, auth_id)
if session is None:
session = Session()
session.session_id = Session.MakeId()
session.auth_token = Session.MakeId()
session.put()
# Attach the session to the method
self.SessionObj = session
#Call the handler.
result = method(self, *args, **kwargs)
self.response.headers.add_header('Set-Cookie', 'auth_id=%s; path=/; HttpOnly' % str(session.auth_token))
self.response.headers.add_header('Set-Cookie', 'session_id=%s; path=/; HttpOnly' % str(session.session_id))
return result
return wrapper
def redirect(method, redirect = "/user/"):
'When a known user is logged in redirect them to their home page'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
try:
if self.SessionObj is not None:
if self.SessionObj.settings is not None:
# Check that the session is correct
username = self.SessionObj.settings.key().name()
self.redirect(redirect + username)
return
except:
pass
return method(self, *args, **kwargs)
return wrapper
Can you not just set a Cookie the first time the user logs in and check for this? If they're a new user it won't be there and but if they're an old user it will be. It's not 100% accurate since some users might clear their cookies but it might do depending on what it is you want to achieve.
If you're using Django in your application managing Cookies is pretty straightforward.
I agree that managing your own authenticated users is the best way to approach this problem. Depending on your application scope obviously but at the very least an AuthUser(Model) class that contains the UserProperty for the users that have logged in with your account.
...
class AuthUser(db.Model):
user = UserProperty(required=True)
...
Then when a user logs in just
...
user = users.get_current_user()
user_exists = AuthUser.gql('where user = :1', user) # or easy check db.GqlQuery("select __key__ from AuthUser where user = :1", user)
if user_exists:
# do user has been before stuff
else:
# do first time user stuff
...
Alternately a super easy way to do this is have a Model for your site that has a ListProperty(users.User) and then you can easily check the list to see if the user has been into your app before.
...
class SiteStuff(db.Model):
auth_users = ListProperty(users.User)
...
and when they log in: check if they are in the list; if not, you add them to the list, put() it and do whatever you need to do for first time users. If you find them in there then do the other stuff.

Resources