Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

google app engine oauth2 provider

I want to set up a rest api with an oauth 2.0 provider for authentication.I use python. is there any library for setting up an oauth 2.0 provider coded in python that runs on app engine ? Thanks.

like image 674
neo Avatar asked Oct 18 '11 16:10

neo


3 Answers

OAuth2 support in builtin on both the Python and Java App Engine runtimes.

In Python all you need is:

from google.appengine.api import oauth

# Note, unlike in the Android app below, there's no 'oauth2:' prefix here
SCOPE = 'https://www.googleapis.com/auth/userinfo.email'

# magic happens here
user = oauth.get_current_user(SCOPE)

In Java you would use:

OAuthService oauth = OAuthServiceFactory.getOAuthService();

// Note, unlike in the Android app below, there's no 'oauth2:' prefix here
String SCOPE = "https://www.googleapis.com/auth/userinfo.email";

// magic happens here
User user = oauth.getCurrentUser(SCOPE);

Here's the full Python 2.7 handler which will allow you to verify the user:

from google.appengine.api import oauth
import logging
import traceback
import webapp2


class MainHandler(webapp2.RequestHandler):

  def post(self):
    self.response.headers['Content-Type'] = 'text/plain'
    self.response.write('Hi there!\n')

    # Note, unlike in the Android app below, there's no 'oauth2:' prefix here
    scope = 'https://www.googleapis.com/auth/userinfo.email'
    try:
      self.response.write('\noauth.get_current_user(%s)' % repr(scope))

      # validates audience of the OAuth2 access token
      allowed_clients = ['407408718192.apps.googleusercontent.com'] # list your client ids here
      token_audience = oauth.get_client_id(scope)
      if token_audience not in allowed_clients:
        raise oauth.OAuthRequestError('audience of token \'%s\' is not in allowed list (%s)' % (token_audience, allowed_clients))          

      # gets user object for the user represented by the oauth token
      user = oauth.get_current_user(scope)
      self.response.write(' = %s\n' % user)
      self.response.write('- auth_domain = %s\n' % user.auth_domain())
      self.response.write('- email       = %s\n' % user.email())
      self.response.write('- nickname    = %s\n' % user.nickname())
      self.response.write('- user_id     = %s\n' % user.user_id())
    except oauth.OAuthRequestError, e:
      self.response.set_status(401)
      self.response.write(' -> %s %s\n' % (e.__class__.__name__, e.message))
      logging.warn(traceback.format_exc())


app = webapp2.WSGIApplication([
  ('/.*', MainHandler)
], debug=True)

The app.yaml is trivial

application: your-app-id
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: .*
  script: main.app

Note that client should send the OAuth2 token in an Authorization: Bearer HTTP Request Header, e.g.

Authorization: Bearer ya29XAHES6ZT4w72FecXjZu4ZWskTSX3x3OqYxUSTIrA2IfxDDPpI

If you happen to be building an Android app, you can easily generate these tokens using the AccountManager interface:

AccountManager accountManager = AccountManager.get(this);
Account[] accounts = accountManager.getAccountsByType("com.google");

// TODO: Allow the user to specify which account to authenticate with
for (Account account : accounts) {
  Log.i(TAG, "- account.name = " + account.name);
}

// Note the "oauth2:" prefix here
String authTokenType = "oauth2:https://www.googleapis.com/auth/userinfo.email";

// Note: AccountManager will cache these token, even after they've expired.
// TODO: Invalidate expired tokens, either after auth fails, or preemptively via:
// accountManager.invalidateAuthToken(accounts[0].type, token);

accountManager.getAuthToken(accounts[0], authTokenType, null, this,
    new AccountManagerCallback<Bundle>() {
      @Override
      public void run(AccountManagerFuture<Bundle> future) {
        try {
          String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);
          Log.i(TAG, "Got KEY_AUTHTOKEN: " + token);
          // Don't forget HTTP Header "Authorization: Bearer <token>"
          callAppEngineRestApi(token); // <---- Your code here
        } catch (OperationCanceledException e) {
          Log.i(TAG, "The user has denied you access to the API");
        } catch (Exception e) {
          Log.i(TAG, "Exception: ", e);
        }
      }
    }, null);

If you'd like to see everything put together, feel free to checkout these projects for the full source:

  • https://github.com/fredsa/sauer.motivate-android Android client app
  • https://github.com/fredsa/sauer.motivate-appengine Python 2.7 App Engine app
  • https://github.com/fredsa/sauer.echo-headers Java App Engine app
like image 180
Fred Sauer Avatar answered Sep 28 '22 18:09

Fred Sauer


Have you checked out the OAuth for Python article information? It says that it is for "This reference describes how to use OAuth with Python applications as the service provider. "

like image 41
Mark S. Avatar answered Sep 28 '22 17:09

Mark S.


I can't comment on the above answer so I have added it here for anyone struggling with this snippet:

# magic happens here
user = oauth.get_current_user(SCOPE)

This has been broken on AppEngine for a month if you're using service accounts (and as of today, I think also Google user tokens) as the token length causes issues in the AE library. Google have told me they are unlikely to fix it soon.

This is the only thing that works for me, at the moment:

    token = self.request.headers['Authorization'].split(' ')[1]
    url = 'https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=' + token
    oauth_response = urlfetch.fetch(url)
    if oauth_response.status_code != 200:
        raise Exception('Unable to authorise: {}/{}'.format(oauth_response.status_code, oauth_response.content))
    token_response = json.loads(oauth_response.content)
    email = token_response['email']
like image 40
KevH Avatar answered Sep 28 '22 18:09

KevH