Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authomatic Usecases #1

Closed
peterhudec opened this issue May 7, 2013 · 40 comments
Closed

Authomatic Usecases #1

peterhudec opened this issue May 7, 2013 · 40 comments
Labels

Comments

@peterhudec
Copy link
Member

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.

@peterhudec
Copy link
Member Author

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

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

@SVarier
Copy link

SVarier commented Jun 28, 2014

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

@peterhudec
Copy link
Member Author

@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.

@pbvillaflores
Copy link

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

@webmaven
Copy link

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

@peterhudec
Copy link
Member Author

@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

@peterhudec
Copy link
Member Author

@webmaven, Automatic implements the server-side flow.

@pbvillaflores
Copy link

@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:[email protected]]
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>

@peterhudec
Copy link
Member Author

@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)

@pbvillaflores
Copy link

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:[email protected]]
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>

@peterhudec
Copy link
Member Author

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()

@pbvillaflores
Copy link

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

if result.error:
break # handle error

@peterhudec
Copy link
Member Author

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

@pbvillaflores
Copy link

I get 403 forbidden on OAuth2 with google

@pbvillaflores
Copy link

But I have the same thing working with FB

@peterhudec
Copy link
Member Author

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

@peterhudec
Copy link
Member Author

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

@pbvillaflores
Copy link

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

@pbvillaflores
Copy link

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

result403

@peterhudec
Copy link
Member Author

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'],
    }
}

@pbvillaflores
Copy link

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

@peterhudec
Copy link
Member Author

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

@peterhudec
Copy link
Member Author

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

@pbvillaflores
Copy link

No.


From: Peter Hudec [mailto:[email protected]]
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>

@peterhudec
Copy link
Member Author

could you please provide the code of the whole view?

@pbvillaflores
Copy link

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 = "[email protected]"
    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

@pbvillaflores
Copy link

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.

@peterhudec
Copy link
Member Author

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

@peterhudec
Copy link
Member Author

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 = "[email protected]"
            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

@peterhudec
Copy link
Member Author

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

@pbvillaflores
Copy link

Using the simple view code, I get:

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

@pbvillaflores
Copy link

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.

@pbvillaflores
Copy link

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!

@pbvillaflores
Copy link

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.

@peterhudec
Copy link
Member Author

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.

@pbvillaflores
Copy link

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:[email protected]]
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>

@peterhudec
Copy link
Member Author

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.

@vikalphanda
Copy link

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.

peterhudec pushed a commit that referenced this issue Mar 3, 2015
fix an error i was getting with yahoo openid
@lugearma
Copy link

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!!! :(

@peterhudec
Copy link
Member Author

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

mrichar1 pushed a commit that referenced this issue Feb 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants