Skip to content

Add web sample for using google-auth for end-user auth #1127

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

Merged
merged 5 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions auth/end-user/web/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""An example web application that obtains authorization and credentials from
an end user.

This sample is used on
https://developers.google.com/identity/protocols/OAuth2WebServer. Please
refer to that page for instructions on using this sample.

Notably, you'll need to obtain a OAuth2.0 client secrets file and set the
``GOOGLE_CLIENT_SECRETS`` environment variable to point to that file.
"""

import os

import flask
import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery

# The path to the client-secrets.json file obtained from the Google API
# Console. You must set this before running this application.
CLIENT_SECRETS_FILENAME = os.environ['GOOGLE_CLIENT_SECRETS']
# The OAuth 2.0 scopes that this application will ask the user for. In this
# case the application will ask for basic profile information.
SCOPES = ['email', 'profile']

app = flask.Flask(__name__)
# TODO: A secret key is included in the sample so that it works but if you
# use this code in your application please replace this with a truly secret
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ", please"

# key. See http://flask.pocoo.org/docs/0.12/quickstart/#sessions.
app.secret_key = 'TODO: replace with a secret value'


@app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.redirect('authorize')

# Load the credentials from the session.
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to check credential validity, either here or after issuing the request if it failed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credentials.valid but the apiclient will refresh if needed and raise a useful error if it's unrefreshable/otherwise invalid.


# Get the basic user info from the Google OAuth2.0 API.
client = googleapiclient.discovery.build(
'oauth2', 'v2', credentials=credentials)

response = client.userinfo().v2().me().get().execute()

return str(response)


@app.route('/authorize')
def authorize():
# Create a flow instance to manage the OAuth 2.0 Authorization Grant Flow
# steps.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILENAME, scopes=SCOPES)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)
authorization_url, state = flow.authorization_url(
# This parameter enables offline access which gives your application
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ", which"

# an access token and a refresh token for the user's credentials.
access_type='offline',
# This parameter enables incremental auth.
include_granted_scopes='true')

# Store the state in the session so that the callback can verify the
# authorization server response.
flask.session['state'] = state

return flask.redirect(authorization_url)


@app.route('/oauth2callback')
def oauth2callback():
# Specify the state when creating the flow in the callback so that it can
# verify the authorization server response.
state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILENAME, scopes=SCOPES, state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does flow.redirect_uri do here? I'm surprised this handler is setting it to itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has to be set for oauthlib to verify the response - oauthlib verifies state, http(s), and makes sure the url base is correct.


# Use the authorization server's response to fetch the OAuth 2.0 tokens.
authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

# Store the credentials in the session.
credentials = flow.credentials
flask.session['credentials'] = {
'token': credentials.token,
'refresh_token': credentials.refresh_token,
'token_uri': credentials.token_uri,
'client_id': credentials.client_id,
'client_secret': credentials.client_secret,
'scopes': credentials.scopes
}

return flask.redirect(flask.url_for('index'))


if __name__ == '__main__':
# When running locally with Flask's development server this disables
# OAuthlib's HTTPs verification. When running in production with a WSGI
# server such as gunicorn this option will not be set and your application
# *must* use HTTPS.
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
app.run('localhost', 8080, debug=True)
38 changes: 38 additions & 0 deletions auth/end-user/web/main_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

import main

# Note: samples that do end-user auth are difficult to test in an automated
# way. These tests are basic sanity checks.


@pytest.fixture
def client():
main.app.testing = True
return main.app.test_client()


def test_index_wo_credentials(client):
r = client.get('/')
assert r.status_code == 302
assert r.headers['location'].endswith('/authorize')


def test_authorize(client):
r = client.get('/authorize')
assert r.status_code == 302
assert r.headers['location'].startswith('https://accounts.google.com')
6 changes: 6 additions & 0 deletions auth/end-user/web/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
google-auth==1.1.0
google-auth-oauthlib==0.1.1
google-auth-httplib2==0.0.2
google-api-python-client==1.6.3
flask==0.12.2
requests==2.18.4