authomatic/authomatic

Authomatic Usecases

peterhudec opened this issue · 40 comments

I would love to hear from you in comments below whether you use Authomatic, how you use it, why you use it or why you don't.

The first usecase of Authomatic is apparently the live demo running at:

http://authomatic-example.appspot.com/

I wanted to use this, after seeing a demo. Unfortunately, the demo application you have hosted on GAE is not working.

@SVarier, what exactly is not working on the on-line demo? There was a problem with Google when user profile response contained unicode characters which could not be decoded to ASCII, but that was a bug of the demo app, not Authomatic. The bug is now fixed.

Hi Peter, is authomatic for google OAuth2 authentication working - particularly using the werkzeug adapter? I keep getting None result.

Which of the Google sign in flows is being implemented by Authomatic?: https://developers.google.com/+/web/signin/

@pbvillaflores, Yes, it is working. If the login() method returns None, it means that the login procedure is not over yet and Authomatic is about to redirect the user to the provider. You should wrap your code in an if result: statement. When the login procedure is over there will always be a result even if something goes wrong. The result either has a result.user if everything went OK, or a result.error if something went wrong. I will update the documentation of the login() method.

@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
def login(provider_name):
    response = make_response()
    result = authomatic.login(WerkzeugAdapter(request, response), provider_name)
    if result:
        # Login procedure is over
        if result.user:
            # Success
            response.data += 'Hi {0}'.format(result.user.name)
        elif result.error:
            # Error
            response.data += 'Something went wrong: {0}'.format(result.error.message)

    # If there is no result, the login procedure is not over yet
    return response

@webmaven, Automatic implements the server-side flow.

@peter,

The example code at
http://peterhudec.github.io/authomatic/examples/flask-simple.html says
something else:

if result:

    if result.user:

        # We need to update the user to get more info.

        result.user.update()

From: Peter Hudec [mailto:notifications@github.com]
Sent: Monday, 14 July 2014 7:57 PM
To: peterhudec/authomatic
Cc: pbvillaflores
Subject: Re: [authomatic] Authomatic Usecases (#1)

@pbvillaflores https://github.com/pbvillaflores , Yes, it is working. If
the login() method returns None, it means that the login procedure is not
over yet and Authomatic is about to redirect the user to the provider. You
should wrap your code in an if result: statement. When the login procedure
is over there will always be a result even if something goes wrong. The
result either has a result.user if everything went OK, or a result.error if
something went wrong. I will update the documentation of the login() method.

@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
def login(provider_name):
response = make_response()
result = authomatic.login(WerkzeugAdapter(request, response),
provider_name)
if result:
# Login procedure is over
if result.user:
# Success
response.data += 'Hi {0}'.format(result.user.name)
elif result.error:
# Error
response.data += 'Something went wrong:
{0}'.format(result.error.message)

# If there is no result, the login procedure is not over yet
return response

Reply to this email directly or view
#1 (comment)
it on GitHub.
<https://github.com/notifications/beacon/6745211__eyJzY29wZSI6Ik5ld3NpZXM6Qm
VhY29uIiwiZXhwaXJlcyI6MTcyMDk1MTAyNywiZGF0YSI6eyJpZCI6MTA2NTM1NTJ9fQ==--d7cd
3095f32deafecfdc872939971475ffdbc13c.gif>

@pbvillaflores, Yes, you're right. the result of the login procedure is a response with an access token. Most of the providers don't provide the user profile information in this response, although some do. Therefore to get the user profile info you need to make a protected resource request with the access token to get the user name. This is intentional because not everybody needs the user profile info but rather just the access token. The best way is to call the method only if there is not the user info you need:

...
if result.user:
    # Success
    if not result.user.name:
        result.user.update() # Makes a provider API call internally

        # The above statement internally calls
        response = authomatic.access(result.user.credentials, 'https://graph.facebook.com/me')
        user_info = response.data # Parsed JSON

    response.data += 'Hi {0}'.format(result.user.name)

So, I wonder what is the correct way to structure that portion of the login
handler code?

if result.user:
# Success

OR:

if result.user:
# Maybe Success?
while result.user.name is None:
# repeat result.user.update() again...


From: Peter Hudec [mailto:notifications@github.com]
Sent: Monday, 14 July 2014 9:02 PM
To: peterhudec/authomatic
Cc: pbvillaflores
Subject: Re: [authomatic] Authomatic Usecases (#1)

@pbvillaflores https://github.com/pbvillaflores , Yes, you're right. the
result of the login procedure is a response with an access token. Most of
the providers don't provide the user profile information in this response,
although some do. Therefore to get the user profile info you need to make a
protected resource request with the access token to get the user name. This
is intentional because not everybody needs the user profile info but rather
just the access token. The best way is to call the method only if there is
not the user info you need:

...
if result.user:
# Success

response.data += 'Hi {0}'.format(result.user.name)

Reply to this email directly or view
#1 (comment)
it on GitHub.
<https://github.com/notifications/beacon/6745211__eyJzY29wZSI6Ik5ld3NpZXM6Qm
VhY29uIiwiZXhwaXJlcyI6MTcyMDk1NDkzOCwiZGF0YSI6eyJpZCI6MTA2NTM1NTJ9fQ==--8d8f
4059bfc32070a8c2cb76d74efd7107ef8733.gif>

If there is user, it is always success. But most of the time you only have an access token of the user. Some providers do not provide user profile info at all. A better solution would be to check the response status of the result.user.update() request.

if result.user:
    if not result.user.name:
        response = result.user.update()
        while response.status != 200:
             response = result.user.update()

should we add after each call to user.update::

if result.error:
break # handle error

The result.error is not related to result.user.update() but to authomatic.login(). There is either result.user or result.error but not both. They are mutually exclusive. If you want to check the success of result.user.update() you should check the response.status:

update_success = True if result.user.update().response.status == 200 else False

I get 403 forbidden on OAuth2 with google

But I have the same thing working with FB

In which phase are you getting the error? Could you provide the logs?

Google should provide information why it is a forbidden 403 request. Try error.message.

I am trying to still figure out how to get the logging to work correctly. Maybe you can help also with this. I've set it at the instance authomatic creation line added logging_level, but no joy. Anyway, the run has already reached these bits of OAuth2 code:

# exchange authorization code for access token by the provider self._log(logging.INFO, 'Fetching access token from {0}.'.format(self.access_token_url))
        self.credentials.token = authorization_code
        ...
       self._log(logging.INFO, 'Got access token.')
        ...
        # create user
        self._update_or_create_user(response.data, self.credentials)

        #===================================================================
        # We're done!
        #===================================================================

The google response result is:

result

Here is the result after result.user.update pass with 403 status response.

result403

This looks like you have an access token but not for reading the user profile. Did you set the user info scope in the config?

CONFIG = {
    'google': {
        'class_': oauth2.Google,
        'consumer_key': '##########',
        'consumer_secret': '##########',
        'scope': ['profile'],
    }
}

that didn't change the outcome. isn't profile and email already included by default in scope?

No, you need to set it explicitly. There is however the Google.user_info_scope which equals to ['profile', 'email'].

If you comment out the result.user.update() does the user have the token attribute result.user.token?

No.


From: Peter Hudec [mailto:notifications@github.com]
Sent: Monday, 14 July 2014 10:26 PM
To: peterhudec/authomatic
Cc: pbvillaflores
Subject: Re: [authomatic] Authomatic Usecases (#1)

If you comment out the result.user.update() does the user have the token
attribute result.user.token?

Reply to this email directly or view
#1 (comment)
it on GitHub.
<https://github.com/notifications/beacon/6745211__eyJzY29wZSI6Ik5ld3NpZXM6Qm
VhY29uIiwiZXhwaXJlcyI6MTcyMDk1OTkzNCwiZGF0YSI6eyJpZCI6MTA2NTM1NTJ9fQ==--296c
b62f14762470590860665ed9103269a520fc.gif>

could you please provide the code of the whole view?

its messy, but what i have currently is:

@app.route('/login/<provider_name>', methods = ['GET', 'POST'])

def login2(provider_name):

print ">= request.base_url: ", request.base_url
if g.user is not None and g.user.is_authenticated():
    return redirect(url_for('index'))

# We need response object for the WerkzeugAdapter.
response = make_response()
# Log the user in, pass it the adapter and the provider name.
result = authomatic.login(WerkzeugAdapter(request, response), provider_name)

# If there is no LoginResult object, the login procedure is still pending.
if result:
    if not result.user.name:
        response = result.user.update()
        while response.status/100 not in [4,5]:
            response = result.user.update()

        if response.status/100 in [4,5]:
            # this line gives an error. response.data is a dict
            response.data += 'Response status: {}'.format(response.status)
            return response

    if result.error:
        # Error
        # this line gives an error. response.data is a dict
        response.data += 'Something went wrong: {0}'.format(result.error.message)

    #got results of login
    if result.user.name is None:
        result.user.name = "Got None"
        result.user.email = "NoEmail@None.com"
    else:
        flash("Logged in successfully id=" + result.user.id)

    if result.user:
        user = User.query.filter_by(email = result.user.email).first()
        if user is None:
            nickname = result.user.name
            if nickname is None or nickname == "":
                nickname = result.user.email.split('@')[0]
            nickname = User.make_unique_nickname(nickname)
            user = User(nickname = nickname, email = result.user.email, role = ROLE_USER)
            db.session.add(user)
            db.session.commit()
    g.user = user

    login_user(g.user, remember = True)

    return redirect(request.args.get('next') or url_for('index'))


# Don't forget to return the response.
return response

I managed to get the logs...

INFO:authomatic.core:authomatic: Google: Starting OAuth 2.0 authorization procedure. INFO:authomatic.core:authomatic: Google: Redirecting user to https://accounts.google.com/o/oauth2/auth?scope=profile+email+profile&state=6a30d07f08b2ff116664183a6c&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000%2Flogin%2Fgoogle&response_type=code&client_id=600898592185-ui9jov18q0083hjce3rtr45ges2f735t.apps.googleusercontent.com. INFO:werkzeug:127.0.0.1 - - [14/Jul/2014 22:41:10] "GET /login/google HTTP/1.1" 302 - INFO:authomatic.core:authomatic: Google: Continuing OAuth 2.0 authorization procedure after redirect. INFO:authomatic.core:authomatic: Google: Validating request by comparing request state with stored state. INFO:authomatic.core:authomatic: Google: Request is valid. INFO:authomatic.core:authomatic: Google: Fetching access token from https://accounts.google.com/o/oauth2/token. DEBUG:authomatic.core:authomatic: Google: ├─ host: accounts.google.com DEBUG:authomatic.core:authomatic: Google: ├─ path: /o/oauth2/token DEBUG:authomatic.core:authomatic: Google: ├─ method: POST DEBUG:authomatic.core:authomatic: Google: ├─ body: client_secret=Mx234234&code=4%2Fb62342340cxpjgI&grant_type=authorization_code&client_id=600898592185-ui9jov18q0083hjce3rtr45ges2f735t.apps.googleusercontent.com&redirect_uri=http%3A%2F%2F127.0.0.1%3A5000%2Flogin%2Fgoogle DEBUG:authomatic.core:authomatic: Google: ├─ params: {'client_secret': '234234234sdfwrfNwVKIMjt', 'code': u'4/b6l227IHJAiJOzZHG6GmHb_cnV14.IqjksOW8cPQfBrG_bnfDxpIu0cxpjgI', 'grant_type': 'authorization_code', 'client_id': '60089859218123q0083hjce3rtr45ges2f735t.apps.googleusercontent.com', 'redirect_uri': u'http://127.0.0.1:5000/login/google'} DEBUG:authomatic.core:authomatic: Google: └─ headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic NjAwODk4NTkyMTg1LXVpOWQuY29tOk1ZX0NIYTZNTFd4Y1BYUUlOd1ZLSU1qdA=='} DEBUG:authomatic.core:authomatic: Google: Got response: DEBUG:authomatic.core:authomatic: Google: ├─ url: https://accounts.google.com/o/oauth2/token DEBUG:authomatic.core:authomatic: Google: ├─ status: 200 DEBUG:authomatic.core:authomatic: Google: └─ headers: [('alternate-protocol', '443:quic'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('content-disposition', 'attachment; filename="json.txt"; filename*=UTF-8\'\'json.txt'), ('transfer-encoding', 'chunked'), ('expires', 'Fri, 01 Jan 1990 00:00:00 GMT'), ('server', 'GSE'), ('pragma', 'no-cache'), ('cache-control', 'no-cache, no-store, max-age=0, must-revalidate'), ('date', 'Mon, 14 Jul 2014 12:41:42 GMT'), ('x-frame-options', 'SAMEORIGIN'), ('content-type', 'application/json; charset=utf-8')] INFO:authomatic.core:authomatic: Google: Got access token. INFO:authomatic.core:authomatic: Google: Procedure finished. INFO:authomatic.core:authomatic: Google: Accessing protected resource https://www.googleapis.com/plus/v1/people/me. DEBUG:authomatic.core:authomatic: Google: ├─ host: www.googleapis.com DEBUG:authomatic.core:authomatic: Google: ├─ path: /plus/v1/people/me DEBUG:authomatic.core:authomatic: Google: ├─ method: GET DEBUG:authomatic.core:authomatic: Google: ├─ body: DEBUG:authomatic.core:authomatic: Google: ├─ params: {} DEBUG:authomatic.core:authomatic: Google: └─ headers: {'Authorization': 'Bearer ya29.QgD29DpKW7rdoEcAAADZeyO23424234234234243234LwZ5vHmA'} DEBUG:authomatic.core:authomatic: Google: Got response: DEBUG:authomatic.core:authomatic: Google: ├─ url: https://www.googleapis.com/plus/v1/people/me DEBUG:authomatic.core:authomatic: Google: ├─ status: 403 DEBUG:authomatic.core:authomatic: Google: └─ headers: [('alternate-protocol', '443:quic'), ('x-xss-protection', '1; mode=block'), ('x-content-type-options', 'nosniff'), ('transfer-encoding', 'chunked'), ('expires', 'Mon, 14 Jul 2014 12:41:43 GMT'), ('server', 'GSE'), ('cache-control', 'private, max-age=0'), ('date', 'Mon, 14 Jul 2014 12:41:43 GMT'), ('x-frame-options', 'SAMEORIGIN'), ('content-type', 'application/json; charset=UTF-8')] INFO:authomatic.core:authomatic: Google: Got response. HTTP status = 403.

According to the logs everything went fine. Try this simple handler:

@app.route('/login/<provider_name>/', methods=['GET', 'POST'])
def login2(provider_name):
    response = make_response()
    result = authomatic.login(WerkzeugAdapter(request, response), provider_name)
    if result:
        if result.user:
            result.user.update()
            return 'Hi: {0}'.format(result.user.name)
        elif result.error:
            return 'Error: {0}'.format(result.error.message)

    return response

I have reorganized your code a little bit:

@app.route('/login/', methods = ['GET', 'POST'])
def login2(provider_name):
    print ">= request.base_url: ", request.base_url
    if g.user is not None and g.user.is_authenticated():
        return redirect(url_for('index'))

    response = make_response()
    result = authomatic.login(WerkzeugAdapter(request, response), provider_name)

    if result:
        if result.error:
            return 'Something went wrong: {0}'.format(result.error.message)

        if result.user:
            if not result.user.name:
                update_response = result.user.update()
                while update_response.status/100 not in [4,5]:
                    update_response = result.user.update()

                if update_response.status/100 in [4,5]:
                    return 'Response status: {}'.format(response.status)

            if result.user.name is None:
                result.user.name = "Got None"
                result.user.email = "NoEmail@None.com"
            else:
                flash("Logged in successfully id=" + result.user.id)

            user = User.query.filter_by(email = result.user.email).first()

            if user is None:
                nickname = result.user.name
                if nickname is None or nickname == "":
                    nickname = result.user.email.split('@')[0]
                nickname = User.make_unique_nickname(nickname)
                user = User(nickname=nickname, email=result.user.email, role=ROLE_USER)
                db.session.add(user)
                db.session.commit()

            g.user = user
            login_user(g.user, remember=True)
            return redirect(request.args.get('next') or url_for('index'))

    return response

⚠️ Don't forget to reset your client_secret by Google. You revealed it with the logs.

Using the simple view code, I get:

Error: The returned state "68909ff8c79796ed0732a447b8" doesn't match with the stored state!

I've resolved that last issue by clearing cookies. But the result using the simple handler code is still the 403 forbidden error and I get user is None.

yahoo! Solved it. So, the error message is available in:

update_response.user.data[u'error']['message']

And the solution was to enable google+ API in the APIs menu of the google dev console.

Thanks Peter!

The other problem now is that when I use FB OAuth2 with the app running on heroku, I get:

in login2() handler: Something went wrong: Failed to obtain OAuth 2.0 access token from https://graph.facebook.com/oauth/access_token! HTTP status: 400, message: {"error":{"message":"This IP can't make requests for that application.","type":"OAuthException","code":5}}.

And it seems no matter how I update the IP address whitelist on the FB dev console, I can't get the app to successfully authenticate through. It did work before already, but now it isn't.

Great! I will add a note about enabling the Google+ API to the docs. The other issue seems to be a Facebook bug: http://stackoverflow.com/questions/12072720/oauth-error-this-ip-cant-make-requests-for-that-application.

Thanks again. BTW, I assume the FB authentication cannot be made open to the
public until the app is submitted for review?


From: Peter Hudec [mailto:notifications@github.com]
Sent: Tuesday, 15 July 2014 7:27 PM
To: peterhudec/authomatic
Cc: pbvillaflores
Subject: Re: [authomatic] Authomatic Usecases (#1)

Great! I will add a note about enabling the Google+ API to the docs. The
other issue seems to be a Facebook bug:
http://stackoverflow.com/questions/12072720/oauth-error-this-ip-cant-make-re
quests-for-that-application.

Reply to this email directly or view
#1 (comment)
it on GitHub.
<https://github.com/notifications/beacon/6745211__eyJzY29wZSI6Ik5ld3NpZXM6Qm
VhY29uIiwiZXhwaXJlcyI6MTcyMTAzNTYzMSwiZGF0YSI6eyJpZCI6MTA2NTM1NTJ9fQ==--7fbd
f81618bd2f6118ad6a63f52eebd58617db11.gif>

You are very welcome.
I can not recall whether I had to submit the live demo for review, probably yes, but it must have been a painless process when I don't remember it.

Hi, Thanks for writing this wonderful library. While running your credentials example, I'm not able to post tweet. I have given Read and Write permission to my app. When I try to post, I get Internal Server Error and in GAE logs I get
logger = cls._logger or authomatic.core._logger

AttributeError: type object 'Twitter' has no attribute '_logger'
Please help me how can I resolve this error.

I have this error message:

Failed to obtain request token from 
https://api.twitter.com/oauth/request_token! HTTP 
status code: 401    
content: Desktop applications only support the oauth_callback value 'oob' /oauth/request_token?oauth_nonce=4c7b99721f46a774edc558c6df&oauth_timestamp=1427743083&oauth_consumer_key=5ROubSCwMYsCLjHd4rzlYpMjt&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_signature=k7Un015dyBljIfh4oTdI1%2BF132o%3D&oauth_callback=http%3A%2F%2F127.0.0.1%3A8000%2Fauth%2Ftwitter

help!!! :(

Please use stackoverflow to ask questions.
Use issues only for bug reports and feature requests.