I'm just getting started with the auth User model and I'm making a new site. The users probably won't have usernames or email addresses as logins but customer numbers and those customer account numbers can probably be used as the username.
But do I really need to inherit the auth User model from webapp2 and define my own User class, can I do without an custom User class?
I could deploy the basic use cases register a user, log in a user and use the decorated function for logged in user. The problems I'm facing is integrating this with google and facebook accounts and an "own" model named User that I either should not have at all or inherit from the webapp2 user model.
class SyncwithFacebook(NewBaseHandler):
"""
........Only accessible to users that are logged in
...."""
@user_required
def get(self, **kwargs):
#a = self.app.config.get('foo')
auser = self.auth.get_user_by_session()
userid = auser['user_id']
user = auth_models.User.get_by_id(auser['user_id'])
try:
email = user.email
return "Secure zone %s <a href='%s'>Logout</a>" % (userid,
self.auth_config['logout_url'])
except (AttributeError, KeyError), e:
#return 'User did not have email'
return """
<!DOCTYPE hml>
<html>
<head>
<title>add your email</title>
</head>
<body>
<form action="%s" method="post">
<fieldset>
<legend>Add facebook ID</legend>
<label>User ID <input
type="text" name="username" placeholder="%s" readonly /></label>
<label>Email <input
type="text" name="email" placeholder="Your email" /></label>
</fieldset>
<button>Create user</button>
</form>
</html>
""" \
% (self.request.url, self.current_user.id)
"""
else:
try:
return "Secure zone %s <a href='%s'>Logout</a>" \
% (user.name, self.auth_config['logout_url'])
except (AttributeError, KeyError), e:
return 'Secure zone'
"""
@user_required
def post(self, **kwargs):
auser = self.auth.get_user_by_session()
user = auth_models.User.get_by_id(auser['user_id'])
#user.auth_ids.append(self.current_user.id)
#user.put()
#return "Profile updated"
existing_user = auth_models.User.get_by_auth_id(self.current_user.id)
if existing_user is not None:
# You need to handle duplicates.
# Maybe you merge the users? Maybe you return an error?
pass
# Test the uniqueness of the auth_id. We must do this to
# be consistent with User.user_create()
unique =
'{0}.auth_id:{1}'.format(auth_models.__class__.__name__,
self.current_user.id)
logging.info('unique: '+str(unique))
if auth_models.User.unique_model.create(unique):
# Append fbuserid to the auth_ids list
user.auth_ids.append(self.current_user.id)
user.put()
return "Profile updated"
else:
return 'some error'
I kind of wonder whether to use a prefix now so that I could change
the code to maybe getting auth id like 'facebook:327400016' instead of
just the Id, any ideas or recommendations about that? Just concatenate
a providor identifier as prefix?
For completeness, here is the method I use to get the facebook ID of a
logged in user:
@property
def current_user(self):
if not hasattr(self, '_current_user'):
self._current_user = None
cookie = get_user_from_cookie(self.request.cookies,
facebookconf.FACEBOOK_APP_ID,
facebookconf.FACEBOOK_APP_SECRET)
if cookie:
# Store a local instance of the user data so we don't need
# a round-trip to Facebook on every request
user = FBUser.get_by_key_name(cookie['uid'])
if not user:
graph = GraphAPI(cookie['access_token'])
profile = graph.get_object('me')
user = FBUser(key_name=str(profile['id']),
id=str(profile['id']),
name=profile['name'],
profile_url=profile['link'],
access_token=cookie['access_token'])
user.put()
elif user.access_token != cookie['access_token']:
user.access_token = cookie['access_token']
user.put()
self._current_user = user
return self._current_user
Thanks!
Nick Rosencrantz
I would recommend using the prefix to avoid possible naming conflicts. It also allows you to determine the provider by examining the auth_id. In the project that I'm working on, I've standardized the auth_id with this method:
@staticmethod
def generate_auth_id(provider, uid, subprovider=None):
"""Standardized generator for auth_ids
:param provider:
A String representing the provider of the id.
E.g.
- 'google'
- 'facebook'
- 'appengine_openid'
- 'twitter'
:param uid:
A String representing a unique id generated by the Provider.
I.e. a user id.
:param subprovider:
An Optional String representing a more granular subdivision of a provider.
i.e. a appengine_openid has subproviders for Google, Yahoo, AOL etc.
:return:
A concatenated String in the following form:
'{provider}#{subprovider}:{uid}'
E.g.
- 'facebook:1111111111'
- 'twitter:1111111111'
- 'appengine_google#yahoo:1111111111'
- 'appengine_google#google:1111111111'
"""
if subprovider is not None:
provider = '{0}#{1}'.format(provider, subprovider)
return '{0}:{1}'.format(provider, uid)
The three things that I'll be working on in near future are:
1. Api consistance among the strategies.
2. Greater test coverage.
3. Threadsafety. - This is the one that I could really use some help on, it's somewhat of a mystery to me.
if ON_DEV:
# Facebook settings for Development
FACEBOOK_APP_KEY = '3435669983'
FACEBOOK_APP_SECRET = 'fec595112347b5f3b35bd958a'
elif os.environ.get("HTTP_HOST").endswith('.br') or
os.environ["SERVER_NAME"].endswith('.br'):
# Facebook settings for domain.com.br
FACEBOOK_APP_KEY = '18797912347046'
FACEBOOK_APP_SECRET = '512a91d5c9ff9123465ababd42c'
else:
# Facebook settings for domain.com
FACEBOOK_APP_KEY = '1643573607006'
FACEBOOK_APP_SECRET = '6423412344094d5b139cb0'
It would also be interesting to make these variables read from
datastore and cached so that an admin can add and ahcnge which FB apps
are used without changing the code. Maybe it could get memcached or
then memcache flushed when updated since these settings don't change
very often.
I also see that in your user model you don't store a password, where
do you store the password for custom users? Does this model also use
the unique entity and multiple auth_ids like the user model of
webapp2?
Thanks,
Nick Rosencrantz
I also see that in your user model you don't store a password, where
do you store the password for custom users?
Does this model also use
the unique entity and multiple auth_ids like the user model of
webapp2?
Thank you for the answers. It's good that you removed the Unique model since it makes it more difficult to remove a profile and the introduktion of userprofiles is also something we need. Using the Unique model I had duplicates which should not be possible. For the same name I had a duplicate in the user table and that should be impossible. I also should look at how email is stored, I store email as a side variable in the user model since I can't know whether the auth_id is in email or just a name and I can look at whether I need to adjust that.
Great to hear the model should be compatible with webapp2 users since then I should be able to "merge" the webapp2 user entities that are already stored in my data layer. ! I could easily enable facebook and linkedin apps with engine auth, I just have to merge the user entities somehow and it looks doable.
Right now I looked at how to change the return url since it is always / and I deployed engine auth to /routing/ and I'd like it to redirect to /routing/
engineauth = {# Login uri. The user will be returned here if an error occures.'login_uri': '/', # default 'login/'# The user is sent here after successfull authentication.'success_uri': '/routing/','secret_key': 'CHANGE_TO_A_SECRET_KEY',# Change to provide a subclassed model'user_model': 'engineauth.models.User',}
In fact I've been looking for a project like this for afew years and didn't find one until now and I've found using Google accounts and federated login in practice is very limiting since you want to store user info.
In the begin I was sub-classing webapp2's User, but by the end I had overridden every method and I wasn't using all of the properties. Since anyone using EngineAuth's User model would have to load if from engineauth.models.User anyways I couldn't justify the overhead of importing a module I wasn't using.
Additionally, EngineAuth was written with the intention of being independent of webapp2. I need a solution that I could use with ProtoRPC. And, while I could have made webapp2 session module fit. It provided, way more flexibility then I need. EngineAuth is about as opinionated as it get. The session is a NDB model and there's no optional cookie storage. This decision alone removed a ton a code.
> I mean, instead of having profiles, why not just extend User from webapp2,
The reason for separating the UserProfiles from the User was overhead, and cost. I assume that most interaction will be with the User, so there's no reason to pay for writes of properties that are seldom changed.
I also wanted the UserProfiles to be independent of the User model. My long term plan is to add more Authorization aspects to EngineAuth where you do something like this:
profile = UserProfile.get_for_user('userid123', 'facebook')
friends = profile.service.get_url('https://graph.facebook.com/me/friends")
In this example there's no reason for the User model or a Google profile, for that mater, to know anything about this.
This project is in the very early stages, though, so I'm completely open to suggestions. So please let me know you thoughts.
Thanks,
Kyle
I'm still trying to decide the best way to handle email addresses. Initially my thought was to make it required, but not all OAuth Provider will give it, so there would have to be an intermediate step, i.e. after authentication a user would be directed to a form where they would enter it.I would, however, like to use an email address as a secondary lookup if it's available.Example:1. A user creates an account with Facebook.2. EngineAuth Creates a UserProfile for the Facebook profile.3. No user with the Facebook auth_id exist so new User is created.4. EngineAuth extracts any email address that are verified with facebook and saves it to a UserEmail model the user_id = User.key.id() that was just created.5. The User logs out of site.6. User returns to the site but forgot that they they have already created an account with facebook, this time they log in with Google.7. EngineAuth Creates a UserProfile for the Google profile.8. EngineAuth extracts the email address from the Google profile and does a search of UserEmail's. It finds a match.9. Instead of creating a new User model it simple appends the google auth_id to the existing one (created in step #2) and logs the user in.The tricky part about this procedure is verifying the email address. If facebook says that an email address belongs to a user should that be trusted?
My long term goal is add an additional project that provides email verification by sending out a an email with a link that a User must, click. I would love to hear thoughts on this process.
> The matching between different accounts, if say, a user forgets they already logged in with facebook before and decides to log in with google and thus creating effectively a new account, is indeed a tricky part. What I've seen so far on different sites/services, is that they visualize sort of "my account providers" page where you can "attach" or remove additional accounts. For instance, I logged in with facebook but then I can also add my google account. Next time I'm logging in with google the app would fetch the same account because they'd be already "linked" together.
Indeed, EngineAuth functions this way. If a User is logged in and they add a new account the auth_id is append to their User model. An account providers page would definitely help clarify this, I'll add one to the sample app.
> The latter, however, doesn't eliminate the issue of a user unintentionally creating two accounts, e.g. first with facebook, then with google, if they were not linked by the user beforehand that is. I don't however think there's a silver bullet unless all of the supported providers have a common attribute, like email address, which is not.
You're absolutely right. I'll hold off on the email lookup. Without a guarantee of ownership, the potential dangers of connecting accounts, don't out weigh the benefits.
Thanks Alex I appreciate the feedback,
Kyle
It's very interesting if you could integrate to use all providers same way in the template home.html like it seems you do with
user.to_dict()
That way we could get for instance username maybe same way for different providers.
engine auths User model and webapp2:s User model read and write from the same table. So I shouldn't confuse 3 ways now of identifying a facebook user:
1. facebook:facebook_id
2. domain.com:facebook:facebook_id
3. facebook_id
Nr 1 is the current way, nr 2 is the suggested way when my app serves multiple domains and multiple FB and linkedin apps, and nr 3 was my old way for my old entity FBUser when I had users logged in with facebook. It's important that I don't get duplicate registrations and can synchronize user data for instance on freelancer.com you can just submit all your facebook details if they are they same that you want to use and keep your current facebook photo there which saves a lot of time if used and I would both like a better way of handling FB logins and a way to a linkedin logins to my apps and after having deployed engine auth I found it looks like it solves the problem. The current user model I've got is webapp2 and when using engine auths user model in the same app the user entities read and write from the kind User so it's possible to get a duplicate like facebook:3333333 and domain.com:facebook:33333 after changing the code but my user entities are not so many so I could just update their auth_ids
Sorry it's taken me so long to get back to you. I agree the Google Strategy should fallback to the standard Google OAuth2 lookup. I have changed to this behavior in the upcoming release. I still have a few more things to hammer out before I push it to Github, though. I'll post back here once it's live.
Thanks,
Kyle
Thank you
/Nick
I want to switch completely to engineauth in all my projects. Is it reasonable even though it is still so early stage? Do you think I can do it?
For some reason I don't understand I had to change to this code since otherwise I got that the object were None and could not use the session:
(models.py, line 437)
@classmethod
def get_by_value(cls, value):
v = cls.deserialize(value)
sid = None
if v:
sid = v.get('session_id')
return cls.get_by_sid(sid) if sid else None
Probably this is just due to my special configuration where I added engineauth to /routing/ and I want to customize it to be my solution for adding login providers. I already had a solution for twitter and facebook but those were unclean hacks that used different user models and now I want a nice solution with just one user entity and engineauth seems like a very good solution.
If you want to inspect were a deployed it the URI is www.montao.com.br/routing/ and not everything is guaranteed to work at this time. I couldn't switch my basic login system to engine auth yet until I understand how engine auth makes its routing. The transition from webapp2s user model is been made tricky due to understanding the routing and how to get a "handle" to the user object. My unclean unsuccessful attempt at migrating my registration to engineauth was:
My site may want to add Orkut as a provider, could that be an idea or
is it the same as Google? I have many ideas and suggestions but since
there are no known bugs and I will go on instead through the source
the you so well informed about.
Thanks!
Nick Rosencrantz
response.delete_cookie('_eauth')
I've thought about storing the users preferred login strategy. Then, when they attempt to access a protected resource, sending them to that provider, but I think that might confuse people. I think maybe the best way to handle this would be on the login page. Maybe use javascript to highlight their preferred strategy.
- Kyle
I have built a simple application using Webapp2 sessions and their User model. I'd like to use EngineAuth, but I guess this means I have to switch over to using the EngineAuth User model, sessions and @user_required decorator for all users, correct? As this is an important change, I need to know if EngineAuth is stable and ready for me to switch to it.