Making a new auth system with webapp2 and the auth model

2,202 views
Skip to first unread message

Niklas Rosencrantz

unread,
Dec 19, 2011, 4:09:00 PM12/19/11
to web...@googlegroups.com
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.

Please tell me something about this. The code I'm using is basically exactly the one posted i nthe issue with 2 corrections for bugs I found - the self-redirect should use the get_url and be changed like this:

#self.redirect('/secure')
self.redirect(webapp2.uri_for('secure'))

And I also found I had to change the routing to make it work.

Thank you

Kyle Finley

unread,
Dec 19, 2011, 9:51:16 PM12/19/11
to web...@googlegroups.com
Niklas,

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?

No, you don't need to inherit from the auth User model. The model is actually an expando, so you can pass custom fields when you create your user:
from webapp2_extras.appengine.auth.models import User
new_user = User.create_user('cn:customernumber123', password_raw='password1', cats_name='wiskers', location='CA')
 
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.

As far as integration with google and facebook, this is the reason that the auth_ids property is repeatable. This will allow you to add multiple auth_ids for a User. All you need to do is establish a unique property such as facebooks userid. You then create an auth_id like this: 'facebook:fbuserid12121212', and create or add to a User. Geting the unique property for facebook and google, is a little bit more complicated, however. I'm actually working on mutiauth solution, myself to help solve this problem. It's a WSGI middleware that's similar to Ruby's OmniAuth. If your interested, I'll let you know when it's completed, probably in the next week or so.

- Kyle

Niklas Rosencrantz

unread,
Dec 30, 2011, 2:10:41 PM12/30/11
to web...@googlegroups.com
I looked a bit on this details:

"All you need to do is establish a unique property such as facebooks userid. You then create an auth_id like this: 'facebook:fbuserid12121212', and create or add to a User."
I can get the facebook user id for my user but I don't know exactly how to store it. Say I get the facebook user ID from HTTP POST since my user is logged in with facebook. Then how should I do?

    @user_required
    def post(self, **kwargs):
        fbuserid = self.request.POST.get('fbuserid')
        auser = self.auth.get_user_by_session()
        user = auth_models.User.get_by_id(auser['user_id'])
        # ... ?  
        user.put()
        return "Profile updated"

Could you please help me how to proceed? I'm reading up on http://webapp-improved.appspot.com/api/webapp2_extras/auth.html and wanting to know the exact details how to enable the multi-auth solution.

Thanks

alex

unread,
Jan 1, 2012, 7:29:57 AM1/1/12
to web...@googlegroups.com
Hey Kyle,

love ruby's omniauth gem, and really curious to the middleware you mentioned. are you planning on publishing it somewhere? I'd love to check it out.

thanks,
alex.

Kyle Finley

unread,
Jan 1, 2012, 12:32:49 PM1/1/12
to web...@googlegroups.com
 Something like this should work:

    @user_required
    def post(self, **kwargs):
        fbuserid = self.request.POST.get('fbuserid')
        auser = self.auth.get_user_by_session()
        user = auth_models.User.get_by_id(auser['user_id'])
        # Check if there is already an account using the fbuserid
        existing_user = auth_models.User.get_by_auth_id(fbuserid)
        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__, fbuserid)
        if auth_models.User.unique_model.create(unique):
            # Append fbuserid to the auth_ids list
            user.auth_ids.append(fbuserid)
            user.put()
            return "Profile updated"
        else:
            return 'some error'

I submitted a patch, but I don't believe it's been applied:


- Kyle

Niklas Rosencrantz

unread,
Jan 1, 2012, 2:00:05 PM1/1/12
to web...@googlegroups.com
That's awesome and excellent to know and really got things working,
thanks a lot! We could update our code with this and it worked right
away and generated very interesting result in the data layer. The
complete code I'm using to to make my function syncwithfacebook is
(interesting part is in the post method)

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

multi-auth is working.png
syncwithfacebook.png
add email.png
login.png
create.png

Kyle Finley

unread,
Jan 1, 2012, 4:34:54 PM1/1/12
to web...@googlegroups.com

> 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?

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)

Niklas Rosencrantz

unread,
Jan 1, 2012, 6:39:46 PM1/1/12
to web...@googlegroups.com, alex.h...@gmail.com, shan...@gmail.com, rober...@gmail.com, staffan....@gmail.com, jorge...@gmail.com, hol...@gmail.com, g...@eddaconsult.se, kim...@gmail.com
Thanks Kyle for the answer that we can use a form such as 'facebook:1111111111'

I wonder also whether it is preferable to add email as an id or as a variable? It seems natural to add it to id like this:

auth_ids (list)
[u'mytest', u'nikl...@gmail.com']

Can you tell me aything about this "solution" if there are any drawbacks and disadvantages in setting the email address as an id like I did?

I'm considering adding email addresses like this:

    @user_required
    def post(self, **kwargs):
        email = self.request.POST.get('email')

        auser = self.auth.get_user_by_session()
        userid = auser['user_id']
        user = auth_models.User.get_by_id(auser['user_id'])
        existing_user = auth_models.User.get_by_auth_id(email)


        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__, email)

        if auth_models.User.unique_model.create(unique):
            # Append email to the auth_ids list
            user.auth_ids.append(email)
            user.put()
            return "Email updated"
        else:
            return 'some error'

Thanks

entity.png

Niklas Rosencrantz

unread,
Jan 2, 2012, 7:40:22 AM1/2/12
to web...@googlegroups.com, alex.h...@gmail.com, shan...@gmail.com, rober...@gmail.com, staffan....@gmail.com, jorge...@gmail.com, hol...@gmail.com, g...@eddaconsult.se, kim...@gmail.com
I want to use the above code for my GAE website that has Login with facebook but I don't know where to put this code. Should it be in a custom User class or somewhere else?

Thank you

Kyle Finley

unread,
Jan 10, 2012, 10:35:07 AM1/10/12
to web...@googlegroups.com
Hey Alex,

Sorry for the late response.

Here's the project that I mentioned, I'm calling it EngineAuth:


It's simular to OminAuth, but It takes things a step further.

Basically, you direct a user to a url e.g. example.com/auth/google and EngineAuth saves a User and UserProfile entities to the datastore, and return a logged in user to the url that you provide.

It's still very early on, so the api might change, but if you're interested in this functionality I would love to get feedback.

Thanks,

Kyle 

Kyle Finley

unread,
Jan 10, 2012, 10:46:22 AM1/10/12
to web...@googlegroups.com
oops, the docs url should be:


Jakob Holmelund

unread,
Jan 10, 2012, 10:52:26 AM1/10/12
to web...@googlegroups.com
Looks very good Kyle ! I think i will give it a spin, ill be happy to
contribute if i find that it lags something or something could be
done smarter.

Kyle Finley

unread,
Jan 10, 2012, 11:09:45 AM1/10/12
to web...@googlegroups.com
Excellent. Any suggestions or patches would be great!

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.

Niklas Rosencrantz

unread,
Jan 11, 2012, 1:06:15 AM1/11/12
to web...@googlegroups.com, Anton Danilchenko
I think something like engine auth should be included with Google App
Engine for the reasons Kyle mentions. I've been looking for something
like engine auth to easily add an OAuth provider and it seems your
projects answers my question "How to add a new OAuth provider?" that
we have been discussing for some time. It's interesting you put the
settings in appengine_config.py and I could make the code work with
many FB applications so that my app engine app can authenticate the
right FB app depending on namespace or domain:

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

Kyle Finley

unread,
Jan 11, 2012, 12:44:02 PM1/11/12
to web...@googlegroups.com, Anton Danilchenko
Thanks Nick, Optionally, storing the provider settings in the datastore is a great idea. I've create a feature request:


I also see that in your user model you don't store a password, where
do you store the password for custom users?

The password is stored in a UserProfile. Every user has 1 User model and multiple UserProfile models (one for each strategy i.e. Facebook, Google, Password ect.)
Since not all developers will use the password strategy I thought that it didn't really belong in the User model.

To test this out go to engineauth.scotchmedia.com 
In the form under Or create a new one type in an email address ( or just a string - this strategy would work for username as well ) and a password hit login. Once your returned to that page under the Profiles tab you can see what the UserProfile looks like in the datastore. You will notice there's a password property with the encrypted password.
 
Does this model also use
the unique entity and multiple auth_ids like the user model of
webapp2?

The user model does use auth_ids, therefore it should be compatible with webapp2 models. The only issue being that the password is no longer stored in the User model, in which case some type of migration will be necessary. If you're running into this let me know and I'll see if I can come up with something.

For the time being I removed the Unique model. With the implementation that I'm using it has not been necessary. The reason for this is that EngineAuth stores a UserProfile with the auth_id as a key. I might add it back in the future, but then again maybe not. Were you using the unique entity for anything other then auth_id?


Niklas Rosencrantz

unread,
Jan 11, 2012, 1:27:34 PM1/11/12
to web...@googlegroups.com, Anton Danilchenko
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.

For engine auth you propose an admin section for adding app IDs, secrets and app rules such as facebook credentials, where you set the app ID and secret for each provider and possibly even the rules how to use multiple facebook/twitter/linkedin apps like I I could use engine auth to enable multiple facebook apps since one of my apps serves multiple domains and one app for the Brazilian domain and another app for the .com so it really helped solve that problem and also how to enable login with linkedin in a cleaner way than I added facebook which was taking bits here and there and writing my own OAuth 2.0 authentication not using the Javascript SDK.

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/ while I do tests since I already did FB integration but with hacks and engine auth is just the way I want it with a good way to add providers and with some work I should be able to add its features even to an estblished system like mine that has been running on appengine since 2008 but I waited with a custom user model since I didn't want to take on another migration when there comes a new one and when these models are compatible since derived from webapp2 there are good reasons to use it.

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.

Thank you /Nick
Data Viewer - bnano_1326302956573.png

Kyle Finley

unread,
Jan 11, 2012, 2:58:50 PM1/11/12
to web...@googlegroups.com

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.

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.

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.

Good, I haven't test this, however, so let me know if you run into any issues.

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/

I need to add this to the documentation:

change 'success_uri' in appengine_config.py 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',
}

this set the default redirect url after a successful login. 

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.

I agree federated login is limited, and the main thing for me was the lack of facebook and twitter, support. I do love how easy they have made it to use, though, and that's one of my primary goals for EngineAuth.

Jakob Holmelund

unread,
Jan 11, 2012, 3:07:57 PM1/11/12
to web...@googlegroups.com
Hmm. I have some questions. Kyle, could you explain why you have moved
away from using the User model from webapp2 ? wouldnt it be better to
extend that, and add functionality to a subclass of User ?

Jakob Holmelund

unread,
Jan 11, 2012, 3:10:40 PM1/11/12
to web...@googlegroups.com
I mean, instead of having profiles, why not just extend User from webapp2,

Kyle Finley

unread,
Jan 11, 2012, 4:18:57 PM1/11/12
to web...@googlegroups.com

> Hmm. I have some questions. Kyle, could you explain why you have moved
> away from using the User model from webapp2 ? wouldnt it be better to
> extend that, and add functionality to a subclass of User ?

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

Niklas Rosencrantz

unread,
Jan 12, 2012, 2:26:22 AM1/12/12
to web...@googlegroups.com
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

Thanks! /Nick R

alex

unread,
Jan 12, 2012, 3:36:26 AM1/12/12
to web...@googlegroups.com
Kyle,

first off, awesome job so far!

My two cents:


On Wednesday, January 11, 2012 8:58:50 PM UTC+1, Kyle Finley wrote:

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? 

You're touching a more general, initially openid-related issue here, like "should I trust provider A?". and that's been going on for years :)

 
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.


That would sort of kill the seemless login for users using their google, fb, etc. providers. I don't think you should trust email confirmation more than you'd trust any provider out there. If someone wants to create a fake account they'll do it with either simple email or facebook, twitter, google, etc. I guess, email confirmation is good only in cases where users have to type it in and might just do a simple typo, given that having email is crucial for the app.

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. 

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.


later,
alex.

Kyle Finley

unread,
Jan 12, 2012, 4:27:43 AM1/12/12
to web...@googlegroups.com
Alex,

> 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

Kyle Finley

unread,
Jan 12, 2012, 5:10:48 AM1/12/12
to web...@googlegroups.com

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.

That is the goal of the UserProfile.user_info (If I'm understating correctly) The UserProfile's user_info property conform to the portable contacts spec:

I need to do a better job on consistency, but once done all username should be in a constant place. for example:

user_profile.user_info['info']["accounts"]: [
  {
    "domain": "plaxo.com",
    "userid": "2706"
  }
]


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

Would it help if you could change the name of the model in the datastore? Instead of User if you could change it do EAUser?

Roberto Previtera

unread,
Jan 13, 2012, 3:37:48 AM1/13/12
to web...@googlegroups.com
Hi Kyle,

Thank you for the great work with Engineauth, i would like use the framework with an existing GAE app that currently uses the Google Accounts authentication system.
Currently registered users have google accounts but not necessarily a Google Plus account and I cannot ask them to create one, so I would like to know if it is possible to use google as a provider but only to fetch basic google profile data, I found a way to do this via using google as an openid provider (https://www.google.com/accounts/o8/id) but I would like to know if it is possible to use Oauth2 to access the profile.

Thanks
Roberto

Kyle Finley

unread,
Jan 15, 2012, 11:23:14 AM1/15/12
to web...@googlegroups.com
Hi Roberto,

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

Roberto Previtera

unread,
Jan 15, 2012, 6:21:00 PM1/15/12
to web...@googlegroups.com
Thanks for your reply.

Roberto

Niklas Rosencrantz

unread,
Jan 17, 2012, 12:10:17 AM1/17/12
to web...@googlegroups.com
We're looking forward to the next release. If I understand correctly
with the new release we can also use the builtin google auth with
engine auth and not just google + for the google strategy.

Thank you
/Nick

Niklas Rosencrantz

unread,
Jan 20, 2012, 9:08:39 PM1/20/12
to web...@googlegroups.com, alex.h...@gmail.com, kim...@gmail.com, ot...@fetaste.com, rober...@gmail.com, shan...@gmail.com, kyriakos.ko...@gmail.com, g...@eddaconsult.se, hol...@gmail.com, staffan....@gmail.com, in...@nanosilver.nu, ped...@gmail.com, con...@abcourses.com, matti.ko...@gmail.com, jorge...@gmail.com, marcus.b...@billstrommucher.se
I especially liked how easy it was to add linkedin app and now I want to switch my FB website login system to the one provided with engineauth so I've been experimenting with engineauth and I think it is more powerful than webapp2s user model and 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:

class Signup(NewBaseHandler):

    def get(self):

        # Returns a simple HTML form for create a new user


        return """
                        <!DOCTYPE hml>
                        <html>
                                <head>
                                        <title>Register new user</title>

                                </head>
                                <body>
                                <form action="%s" method="post">
                                        <fieldset>
        <!--                                    <legend>Register user form</legend>
  <label>Country
<select>
  <option value="se">Sweden</option>
  <option value="uk">UK</option>
  <option value="cr">Croatia</option>
</select> </label>-->

                                                <label>Email <input type="text" name="email" placeholder="Your email" /></label>
                                                <label>Password <input type="password" name="password" placeholder="Your password" /></label>

                                        </fieldset>
                                        <button>Create user</button>
                                </form>
                        </html>
                """ \
            % self.request.url

    def post(self):
        #email = self.request.POST.get('email')
        #password = self.request.POST.get('password')
        password = self.request.POST['password']
        email = self.request.POST['email']
    """
        if not password or not email:
            return self.raise_error('Please provide a valid email '
                                    'and a password.')
        user_info = self.user_info(req)
        profile = self.get_or_create_profile(
            auth_id=user_info['auth_id'],
            user_info=user_info,
            password=password)
        #req.load_user_by_profile(profile)
        return 'user gotten or created'
    """
        # Passing password_raw=password so password will be hashed
        # Returns a tuple, where first value is BOOL. If True ok, If False no new user is created

       
    user = self.auth.store.user_model.create_user(email,
                password_raw=password)
        if not user[0]:  # user is a tuple
            return user[1]  # Error message
        else:

            # User is created, let's try redirecting to login page

            try:
                message = mail.EmailMessage(sender='nikl...@gmail.com '
                        , subject='test')
                message.body = 'new user was created '
                message.to = email
                message.bcc = 'nikl...@gmail.com, in...@nanosilver.nu'
                message.send()
                self.redirect(self.auth_config['login_url'], abort=True)
            except (AttributeError, KeyError), e:

                # self.abort(403)

                self.response.out.write(str(e))

I'll keep trying though and create seperate issues since Iäm asking many questions at the same time.

Thanks,
Nick Rosencrantz

Kyle Finley

unread,
Jan 21, 2012, 11:19:42 AM1/21/12
to web...@googlegroups.com
Hey 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?

Not quite, yet. While I believe that code is reasonably stable, the api is not. This is the reason I've been slow to push the next release. I've changed the package name from engineauth to engine_auth breaking current installs. Things like this will be common until I've put it into production myself, which isn't true yet. My timeline is one to two weeks. If you can hold out until then, things will be a lot easier for you.

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

Thanks for the report, and the code. I'll get that changed.

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:


I need to to a better job documenting this but here's how engine auth is designed to work.

You would set your config settings.

# appengine_config.py
# caution these variable names might change in the next release.
engineauth = {
  'base_uri': '/routing/auth', # this the base url for strategies. For example the login for facebook would now be '/routing/auth/facebook' and the callback would now be '/routing/auth/facebook/callback'. This isn't very well tested, so you might run into some problems. 
  'login_uri': '/routing', # where the user is redirected to on error
  'success_uri': '/routing/successful_login', # where the user is redirected to on successful login 
}

Then, in your form you would POST to '/routing/auth/password/'. The only requirement are that 'email' and 'password'are included in the POST. engine_auth checks for an existing account, if one is found it compares the password. If one is not found it will create one and encrypt the password. On success it will redirect to '/routing/successful_login' (you can test this in the example app, the only difference is the success_uri and login_uri are the same).

Once a user is login you will have access to there user model through self.request.user

So engine_auth should remove the majority of your code. The only thing you would be left with is a form that POSTs to '/routing/auth/password' and the email handling.

Your example brings up a couple of issues that I need to handle, though.

1) The password strategy should be able to accept additional information about the user. and save that to the profile. In your example the country should be saved.
2) There's needs to be a way to add additional functionality when a new user is creation. For example send out an email.

This is a common case, I'll add an example of how to accomplish this.

Thanks for the feedback Nick, you always give me a lot think about.

- Kyle Finley



Niklas Rosencrantz

unread,
Jan 21, 2012, 3:49:43 PM1/21/12
to web...@googlegroups.com
Many thanks Kyle for the informative replies, of course I even prefer
wait for a new release since I've got other work to do and I know I'm
an early adopter positive about engine_auth already succeeded adding
linkedin as provider, could demo it to my boss who liked it and for
multilingual and international commerce and hobby sites I develop I
want to solve migration problems at an early stage since I only have a
few dozen user entities now that I've been gathering via FB, google
and custom webapp2 accounts and I think they all can be compatible and
if they are not compatible then I want to start over with this user
model since I think it's the best user model that solves all problems
especially the one "adding a provider" which we have been discussing
on and off lists in different groups, on forums and off lists.

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

Roberto Previtera

unread,
Feb 1, 2012, 7:27:46 AM2/1/12
to web...@googlegroups.com
Hello Kyle, I am integrating engineauth in my application, just a trivial question: is there something like a "logout" uri or the application must remove the user from the session to "logout" the authenticated user ?

Thanks
Roberto

Kyle Finley

unread,
Feb 1, 2012, 11:05:48 AM2/1/12
to web...@googlegroups.com
Reberto,

There's currently isn't a logout uri. If your using webapp2 you can manually logout the user by deleting the cookie:

response.delete_cookie('_eauth')



I think it's best to delete the cookie instead of modifying the session. That way when the user logs in again the session will be waiting for them. Additionally, a user may want to logout of one browser, but say logged into another. The session uses the user's id as their key for this purpose.

If you are instead wanting to forcefully logout a user. Deleting the session would accomplish that:

from engineauth.models import Session

Session.get_by_id(str(user_id)).delete()


I hope that helps,

Kyle

Roberto Previtera

unread,
Feb 1, 2012, 11:54:54 AM2/1/12
to web...@googlegroups.com
it seems to work perfectly, thanks !

New question: I would like to write a decorator like @login_required to check for an existing user session and fallback to the login_uri.
I think the only way to access the login_uri is directly via engineauth["logon_uri"] config, is it correct ?

Thanks
Roberto

Kyle Finley

unread,
Feb 1, 2012, 12:38:27 PM2/1/12
to web...@googlegroups.com
If there's an error authenticating a user, the user is redirected to engineauth["login_uri"] . So, yes, that would be the ideal redirect location for your @login_required decorator. engine_auth doesn't have an opinion about what that page looks like or does. For instance in the example app the login_uri page has the form with provider links.

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

Roberto Previtera

unread,
Feb 1, 2012, 12:55:04 PM2/1/12
to web...@googlegroups.com
Would you suggest using a client side cookie for storing the preferred strategy ?

I agree, it would improve the user experience but it could also be confusing if it is not consistent among different clients.

Roberto

Kyle Finley

unread,
Feb 1, 2012, 2:41:40 PM2/1/12
to web...@googlegroups.com
I don't know? What do you think? 

When a user logs in engine_auth could create a second cookie with just the providers name maybe called '_eauth_provider'. 

- Kyle

Roberto Previtera

unread,
Feb 2, 2012, 4:46:04 AM2/2/12
to web...@googlegroups.com
This would improve usability, but it should always be possible for the user to revert to the complete list of providers and choose a different one (use case: shared computer, one user "A" logon via provider "1", user "B" via provider "2") so I would not redirect the user to the provider without asking to confirm.

Roberto

akindo

unread,
Mar 11, 2012, 12:16:49 PM3/11/12
to web...@googlegroups.com
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.

Thanks. 

Kyle Finley

unread,
Mar 11, 2012, 12:36:45 PM3/11/12
to web...@googlegroups.com
Hey akindo,

Sorry I've neglected EngineAuth, recently. 

The current version of EngineAuth is not stable enough to switch to. But, I'll be announcing a project tomorrow that is. I'll post here once it's live.


- Kyle 

Jannik Sundø

unread,
May 3, 2012, 5:25:40 PM5/3/12
to web...@googlegroups.com
Hi Kyle,

Sorry for the late reply.

No worries, in the end I used SimpleAuth.

Thanks.

David Thomas

unread,
Apr 10, 2013, 1:37:16 PM4/10/13
to web...@googlegroups.com
I know this is an old topic, but what's the state of engineauth or whatever new project you were referencing?

Kyle Finley

unread,
Apr 10, 2013, 2:08:44 PM4/10/13
to web...@googlegroups.com
Hey Dave,

I'm no longer actively maintaining EngineAuth. That said, I believe the code is relatively stable.

The primary goal for EngineAuth was to create an easy to use authentication method that did not relay upon a particular App Engine Framework E.g. webapp2. What I found, is that to achieve this goal I had to sacrifice clarity in customization.

The project that I was referencing was my answer to the customization hurl. A collection of package that were designed to work specifically with webapp2:

aeauth[0]
aecore[1]
aeaccount[2]
aeadmin[3]
aerest[4]

All harmoniously joined in a buildout project -- aestarter[5].

For beter or worse the project moved to a different programming language, therefore, ae* is still incomplete.


- Kyle

[0]: https://github.com/scotch/aeauth
[1]: https://github.com/scotch/aecore
[2]: https://github.com/scotch/aeaccount
[3]: https://github.com/scotch/aeadmin
[4]: https://github.com/scotch/aerest
[5]: https://github.com/scotch/aestarter
> --
> You received this message because you are subscribed to a topic in the Google Groups "webapp2" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/webapp2/Ui5FEFVnyIY/unsubscribe?hl=en.
> To unsubscribe from this group and all its topics, send an email to webapp2+u...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Reply all
Reply to author
Forward
0 new messages