requests / requests-oauthlib

OAuthlib support for Python-Requests!
https://requests-oauthlib.readthedocs.org/
ISC License
1.72k stars 424 forks source link

Mobile Application Flow Example #104

Open Miserlou opened 10 years ago

Miserlou commented 10 years ago

This part is still missing documentation https://requests-oauthlib.readthedocs.org/en/latest/oauth2_workflow.html#mobile-application-flow

Does anybody have an example on hand? I'll write the docs, but would be nice to have some working code to start from. Is this tested?

Lukasa commented 10 years ago

I think @ib-lundgren is in the best place to answer this.

ib-lundgren commented 10 years ago

@Miserlou Happy to hear you want to write docs :)

I've scribbled together an untested example based on the Google OAuth 2 example but for the UserAgent flow. Let me know what errors you run into and I'll investigate...

# Credentials you get from registering a new application
client_id = '<the id you get from google>.apps.googleusercontent.com'
redirect_uri = 'https://your.registered/callback'

# OAuth endpoints given in the Google API documentation
authorization_base_url = "https://accounts.google.com/o/oauth2/auth"
scope = [
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile"
]

from oauthlib.oauth2 import MobileClient
client = MobileClient(client_id)

from requests_oauthlib import OAuth2Session
google = OAuth2Session(client_id, client=client, scope=scope, redirect_uri=redirect_uri)

# Redirect user to Google for authorization
authorization_url, state = google.authorization_url(authorization_base_url,
    # offline for refresh token
    # force to always make user click authorize
    access_type="offline", approval_prompt="force")
print 'Please go here and authorize,', authorization_url

# Get the authorization verifier code from the callback url
redirect_response = raw_input('Paste the full redirect URL here:')

# Fetch the access token
google.token_from_fragment(redirect_response)

# Fetch a protected resource, i.e. user profile
r = google.get('https://www.googleapis.com/oauth2/v1/userinfo')
print r.content

Oh, and in the docs it is worth noting that python web apps want should not use this OAuth flow but it is perfect for desktop apps controlling a browser.

Miserlou commented 10 years ago

Okay cool! I actually like writing docs.. what's the point of writing a library if you don't tell people how to use it?

So we're using django-oauth-toolkit to associate clients with accounts on our web application. Because it's for mobile clients, there is no need for any callback or redirect APIs - there is no user interaction at all. The user has the client_id and a secret, it just needs to get an access token.

My purpose here is to write a python client with requests-oauthlib which can test an API configured in such a way.

So far, the only way I've found that is to do this:

response = requests.post(BASE_URL + ACCESS_TOKEN_PATH, {'grant_type': 'client_credentials', 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET})
token = response.json() # {u'access_token': u'QMa1rtXXXXXXXXXXXXXXd', u'token_type': u'Bearer', u'expires_in': 36000, u'scope': u'read write'}
client = MobileApplicationClient(CLIENT_ID)
session = OAuth2Session(CLIENT_ID, client=client, token=token)
print session.get(API_URL + 'hello/') # <Response [200]>

Is there any way for requests-oauthlib to do the first part so I don't have to make the call directly with requests.post?

ib-lundgren commented 10 years ago

Yes. Try this code

client = oauthlib.oauth2.BackendApplicationClient(CLIENT_ID)
session = requests_oauthlib.OAuth2Session(CLIENT_ID, client=client)
token = session.fetch_token("https://your.token/endpoint", client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
# store token if you fancy
session.get(API_URL + 'hello/')

It might be worth adding an alias for this way/flow along the lines of "SingleUserClient" (or "NonInteractiveSingleUserClient") as that is the second use case for this flow and possibly even more common.

Note that CLIENT_ID is supplied three times but only the last one matters as the first two are for consistency with other flows but unused. Whereas the last one forces client_id and client_secret to be included in the request (since this is not mandated in the oauth 2 spec its not always done). By using BackendApplicationClient the grant_type will be set for you.

Also note that the type of client only matters when obtaining the token. Once you have the token you can just use default.

# sometime later
token = load_token_from_db(CLIENT_ID)
session = OAuth2Session(CLIENT_ID, token=token)
print session.get(API_URL + 'hello/') 
whitechris969 commented 10 years ago

I've tried the code with the Github API but I must be doing something wrong:

import requests_oauthlib
from oauthlib.oauth2 import BackendApplicationClient

client_id = "my_client_id"
client_secret = "my_client_secret"
token_url = 'https://github.com/login/oauth/access_token'

client = BackendApplicationClient(client_id)
session = requests_oauthlib.OAuth2Session(client_id, client=client)
token = session.fetch_token(token_url, client_id=client_id, client_secret=client_secret)
# store token if you fancy
session.get('https://api.github.com/user')

I get a response from github: "error=bad_verification_code&error_description=The+code+passed+is+incorrect+or+expired.&error_uri=http%3A%2F%2Fdeveloper.github.com%2Fv3%2Foauth%2F%23bad-verification-code"

Then, I get an error: "oauthlib.oauth2.rfc6749.errors.MissingTokenError"

I've double and triple-checked my client_id and client_secret keys

whitechris969 commented 10 years ago

I tried the above code (changing the client_id, secret, token url, etc) against my flask-oauthlib server and get an error: {"error": "invalid_client"}. The client_id and secret is valid and are in my database (and work when I do a web authorization flow).

EDIT: @lepture solved this issue with the flask-oauthlib server but still can't access github api above

ib-lundgren commented 10 years ago

Sorry for the late reply. Great that it works with flask-oauthlib now.

For GitHub, I am not certain they actually support the client credentials grant that you are trying to use. I think they only support the authorization code one. However, you can use their API with your token using basic auth which is quite similar https://developer.github.com/v3/oauth/#non-web-application-flow .