I am creating an API to wrap google oauth. I am using google python client library.
Code in api.py
from flask import request, g
from ..helpers import config_helper
from ..adapters.youtube.oauth_adapter import OauthAdapter
from ..api import api_blueprint as api
#api.before_app_request
def before_request():
client_id, client_secret, scope, callback = config_helper.get_config()
g.auth = OauthAdapter(client_id, client_secret, scope, callback)
#api.route('/authorisation_url/')
def authorisation_url():
auth = g.get('auth', None)
return auth.get_authorisation_url()
#api.route('/oauth2callback/')
def callback():
authorisation_code = request.args.get('code')
return authorisation_code
#api.route('/save_oauth2credential/', methods=['POST'])
def oauth2_credentials():
auth = g.get('auth', None)
user = request.form.get('user')
authorisation_code = request.form.get('authorisation_code')
auth.save_credentials(user, authorisation_code)
#api.teardown_app_request
def after_request(response):
g.auth = None
return response
Code in oauth_adapter.py
from oauth2client.client import OAuth2WebServerFlow
from ..repositories import oauth_credentials_repository
class OauthAdapter:
def __init__(self, client_id, client_secret, scope, callback):
self.flow = OAuth2WebServerFlow(client_id=client_id,
client_secret=client_secret,
scope=scope,
redirect_uri=callback)
def get_authorisation_url(self):
return self.flow.step1_get_authorize_url()
def save_credentials(self, user, authorisation_code):
credentials = self.flow.step2_exchange(authorisation_code)
oauth_credentials_repository.save(user, credentials, 'Google')
I need to save the credentials object against the logged in user so I can use it later to make call to other google api's on behalf of this user.
When I call #api.route('/save_oauth2credential/', methods=['POST']) to retrieve the credentials using the authorisation_code retrieved in #api.route('/oauth2callback/') step. I keep getting FlowExchangeError: invalid_grant
Any ideas?
Have a look at the network dialogue in Chrome Devtools and see what is going over the wire. I'm guessing that there is a 403 invalid grant response in there somewhere. So you need to figure out the cause of the invalid grant. I just posted some possible explanations at Getting 'invalid_grant' error with google oauth from second time
In general, there are two things you need to do.
Firstly, check that your code is correct, that your scopes and client id are solid, and that you are correctly saving the refresh token.
Once you've done all of that, you need to deal with invalid grant as an occupational hazard, and reprompt the user for for authorisation in order to get a new refresh token.
Related
In my Django web application i have this function that makes a request to google's youtube api, this function gets excuted each time a user submits a form
Here is the code:
import googleapiclient.discovery
def myFunc():
youtube = googleapiclient.discovery.build(
api_service_name, api_version, developerKey=api_key)
request = youtube.search().list(
part="id",
maxResults=25,
q="surfing",
)
youtube_api_response = request.execute()
for object in youtube_api_response["items"]:
# my logic here
Now building the api client using
googleapiclient.discovery.build(api_service_name, api_version, developerKey=api_key)
So building this way each time a user submits a form will require a lot of time and slow down the website.
Is there a way to store this build somehow or reuse the same one each time without the need of building again and again to improve the performance.
My suggestion would be to have a single instance of your client. You could then pass that client to the function you want to use.
Even better, wrap your client in a class of its own and simply just call functions from that. Such as:
import googleapiclient.discovery
class GoogleClient:
def __init__(self):
self.api_service_name = "name"
self.api_key = "key"
self.api_version = "version"
self.client = googleapiclient.discovery.build(
self.api_service_name, self.api_version, developerKey=self.api_key)
def search(self, q):
request = self.client.search().list(
part="id",
maxResults=25,
q=q,
)
youtube_api_response = request.execute()
for object in youtube_api_response["items"]:
print("do whatever")
client = GoogleClient()
client.search("query")
I'm very beginner with authlib and trying to understand its concepts.
I try to understand, how can I save and reuse fetched tokens with authlib.
I created small FastAPI project:
from fastapi import FastAPI
from starlette.config import Config
from starlette.middleware.sessions import SessionMiddleware
from starlette.requests import Request
from authlib.integrations.starlette_client import OAuth
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-secret")
config = Config(".env")
oauth = OAuth(config)
oauth.register(
name="some_service",
client_id="client_id",
client_secret="client_secret",
authorize_url="https://some-service.com/auth",
access_token_url="https://some-service.com/token",
client_kwargs={
"token_endpoint_auth_method": "client_secret_post",
},
)
#app.get("/login")
async def login(request: Request):
redirect_uri = "https://myservice.com/auth"
return await oauth.some_service.authorize_redirect(request, redirect_uri)
#app.get("/auth")
async def auth(request: Request):
token = await oauth.some_service.authorize_access_token(request)
# I suppose that I should save somehow token here
return token
#app.get("/account")
async def get_account(request: Request):
account_url = "https://some-service.com/account"
resp = await oauth.some_service.get(account_url)
return resp.json()
I want to get account info. So, further steps will be:
GET /login
I'm giving access to use my account and will be redirected back to my service.
GET /auth?oauth_params1=foo&oauth_params2=bar
There will be fetched tokens from token provider. I know that I'm wrongly supposing that token will somehow saved somewhere.
GET /account
And there I'm expecting that with OAuth client I can send previously fetched token. But, I'm getting next error:
authlib.integrations.base_client.errors.MissingTokenError: missing_token:
I also know that I should provide token like that:
oauth.some_service.get(account_url, token=previously_fetched_token)
But, I don't want to ask every time token from some-service I want to reuse token. How to do that?
Am I wrong that this issue is the part of authlib scope? Should I find solution with cache or database mechanisms?
p.s.: I'm really beginner with FastAPI too...
The token is an object with several values-
{
"oauth_token": "TOKEN ID",
"oauth_token_secret": "SECRET TOKEN",
"user_id": "USER ID",
"screen_name": "USER SCREEN NAME"
}
You have several options-
Use a database model that has those values. Use the "user_id" as the primary key, as the "screen_name" can be changed by users.
JSON encode the whole object and stash it somewhere.
Shove it into a cookie object so it's sent back with each request. The nice part of this is you don't have to worry about storing the oauth token at all, but it means you can't do anything with it outside of user requests.
I have a Python app running on Google App Engine. I am trying to perform an Oauth 2 authentication without using oauth libraries.
I need a session to store the information returned from the Google auth code (access token) and send it to the next request. When I try to store the JSON into a flask session key, my app goes to an Internal Server Error with no debug info. My code is practically a copy-paste from Google's HTTP Rest example in their Oauth2 for web server apps documentation (link commented in code).
import logging
import flask
from flask import render_template, session
import json
import urllib
from google.appengine.api import urlfetch
#Code from https://developers.google.com/identity/protocols/OAuth2WebServer
app = flask.Flask(__name__)
CLIENT_ID = '[].apps.googleusercontent.com'
CLIENT_SECRET = '[]'
SCOPE = 'email'
REDIRECT_URI = 'https://[].appspot.com/oauthcallback'
#check for last oauth step, if not go to intermediate steps
#app.route('/')
def index():
if 'credentials' not in flask.session:
return flask.render_template('index.html')
credentials = json.loads(flask.session['credentials'])
if credentials['expires_in'] <= 0:
return flask.redirect(flask.url_for('oauthcallback'))
else:
headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
req_uri = 'https://www.googleapis.com/oauth2/v2/userinfo'
r = requests.get(req_uri, headers=headers)
return r.text
#ask user to sign in, send code to googleapi to get token
#app.route('/oauthcallback')
def oauthcallback():
if 'code' not in flask.request.args:
auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
return flask.redirect(auth_uri)
else:
auth_code = flask.request.args.get('code')
data = {'code': auth_code, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'redirect_uri': REDIRECT_URI, 'grant_type': 'authorization_code'}
r = urlfetch.fetch("https://www.googleapis.com/oauth2/v4/token", payload=data, method="POST")
#return r.content #prints json
flask.session['credentials'] = r.content #breaks here
return flask.redirect(flask.url_for('index'))
if __name__ == '__main__':
import uuid
app.secret_key = str(uuid.uuid4())
app.debug = True
app.run()
Did you enable the redirect URI?
https://developers.google.com/api-client-library/python/auth/web-app
Create authorization credentials
Any application that uses OAuth 2.0 to access Google APIs must have authorization credentials that identify the application to Google's OAuth 2.0 server. The following steps explain how to create credentials for your project. Your applications can then use the credentials to access APIs that you have enabled for that project.
Open the Credentials page in the API Console.
Click Create credentials > OAuth client ID.
Complete the form. Set the application type to Web application. Applications that use languages and frameworks like PHP, Java, Python, Ruby, and .NET must specify authorized redirect URIs. The redirect URIs are the endpoints to which the OAuth 2.0 server can send responses.
For testing, you can specify URIs that refer to the local machine, such as http://localhost:8080. With that in mind, please note that all of the examples in this document use http://localhost:8080 as the redirect URI.
I’m building an application in Python which can retrieve data from Azure AD. This data can require either Application permissions or Delegated permissions. I had a success retrieving data which needs only Application permissions. However, in order to retrieve data which needs delegated permission, I am trying to use OAuth2. Is it possible to get authenticated with Microsoft Graph using OAuth2 but not having the user sign in using the web page, but instead supplying the user credentials through the Python script itself?
Note: I want to use Microsoft Graph API (v1.0 and beta) and not Azure AD Graph API.
Assuming you have registered and configured (api permissions) your azure app and you have copied the apps "client id" and "client secret" you can define a class that holds your session.
The following code works for my app:
import json
import requests
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
class SharepointSession(object):
""" Base Class without credentials, use real credentials in derived Classes
or instances
"""
api_uri = "https://graph.microsoft.com"
api_version = "v1.0"
scope = ["https://graph.microsoft.com/.default"]
directory_id = "" # - tenant id
token_url = "https://login.microsoftonline.com/{}/oauth2/v2.0/token"
sites_url = "{}/{}/sites".format(api_uri, api_version)
site = document_name = app_name = client_id = client_secret = ""
site_id = None
doc_id = None
def __init__(self):
""" """
def getTokenizedSession(self):
"""
OAuth2 to get access token
First set up a backend client, mind to set grant_type
build a OAuth2 Session with the client
get access token
Mind: python 3.x oauthlib requires scope params on more calls than py 2.x
"""
client = BackendApplicationClient(
client_id=self.client_id, scope=self.scope, grant_type="client_credentials")
session = OAuth2Session(client=client, scope=self.scope)
# fill access token
token = session.fetch_token(token_url=self.token_url.format(self.directory_id),
client_id=self.client_id,
scope=self.scope,
client_secret=self.client_secret)
self.session = session
self.token = token
return session, token
def getSiteId(self):
# get the site id
ae = "{}/myonline.sharepoint.com:/sites/{}:".format(
self.sites_url, self.site)
rt = self.session.get(ae)
response = json.loads(rt.text)
self.site_id = response.get("id")
return self.site_id
def someOtherMethod(self):
""" ... """
Now you can instantiate the session class with the credentials copied from your azure app registration i.e. "directory id" (same as tenant id), "client id" and "client secret"
like this:
mysp_session = SharepointSession()
mysp_session.directory_id = "XXXXXXXX-XXXX-YYYY-ZZZZ-XXXXXXXXX"
mysp_session.site = "MySitename"
mysp_session.document_name = "Testlist"
mysp_session.client_id = r"xxxxxxxxxxxxxxxxxxxxxxx"
mysp_session.client_secret = r"xxxxxxxxxxxxxxxxxxxxxxx"
# connect
session, token = mysp_session.getTokenizedSession()
# do your business logic
mysp_session.getSiteId()
....
mysp_session.someOtherMethod()
hope that helps
Yes, this is possible - but keep in mind that there are two Azure AD endpoints for application registration!
Try registering an application on the AAD V2.0 endpoint (apps.dev.microsoft.com), and then use a 'password' grant_type in your request.
Here are the steps you need:
Register your app on the AAD v2.0 endpoint, and generate a password (take
note of this)
Assign your required permissions (in this case, delegated)
As a callback URL I'd suggest using postman's Oauth2 callback URL first so you can debug what you're doing: https://www.getpostman.com/oauth2/callback
Important! If any of those permissions require admin consent, you MUST consent to them first to make the app available. This requires the admin user to sign in once.
Once consent has been given, here's a what your request needs to get a bearer token as a prototype:
POST https://login.microsoftonline.com/common/oauth2/token
Request body (application/x-www-form-urlencoded):
grant_type=[password]
username=[user email address]
password=[user password]
resource=https://graph.microsoft.com
client_id=[your newly registered application ID]
client_secret=[application password you noted during registration]
If successful, you'll get the bearer & refresh token as a response.
Hope this helps,
Ben
You need an Azure AD application to be able to authenticate with Graph API. A native Azure AD app and the flow and considerations described here work for ADAL.net. I use it to provision Microsoft Teams unattended: http://www.cloudidentity.com/blog/2014/07/08/using-adal-net-to-authenticate-users-via-usernamepassword/
I guess for Python you should have a look at ADAL for Python: https://github.com/introp-software/azure-activedirectory-library-for-python-old/blob/master/README.md
I think that the username/password auth is only possible with a native Azure AD app and not the web/web api types.
I am trying to get started with the Box.com SDK and I have a few questions.
from boxsdk import OAuth2
oauth = OAuth2(
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
store_tokens=your_store_tokens_callback_method,
)
auth_url, csrf_token = oauth.get_authorization_url('http://YOUR_REDIRECT_URL')
def store_tokens(access_token, refresh_token):
# store the tokens at secure storage (e.g. Keychain)
1)What is the redirect URL and how do I use it? Do I need to have a server running to use this?
2)What sort of code to I need in the store_tokens method?
The redirect URL is only required if you're runng a Web application that needs to respond to user's requests to authenticate. If you're programtically authenticating, you can simply set this as http://localhost. In a scenario where you require the user to manually authenticate, the redirect URL should invoke some function in your web app to store and process the authentication code returned. Do you need a server running? Well, if you want to do something with the authentication code returned, the URL you specify should be under your control and invoke code to do something useful.
Here's an example of what the store_tokens function should look like. It should accept two parameters, access_token and refresh_token. In the example below, the function will commit these to a local store for use when the API needs to re-authenticate:
From here:
"""An example of Box authentication with external store"""
import keyring
from boxsdk import OAuth2
from boxsdk import Client
CLIENT_ID = 'specify your Box client_id here'
CLIENT_SECRET = 'specify your Box client_secret here'
def read_tokens():
"""Reads authorisation tokens from keyring"""
# Use keyring to read the tokens
auth_token = keyring.get_password('Box_Auth', 'mybox#box.com')
refresh_token = keyring.get_password('Box_Refresh', 'mybox#box.com')
return auth_token, refresh_token
def store_tokens(access_token, refresh_token):
"""Callback function when Box SDK refreshes tokens"""
# Use keyring to store the tokens
keyring.set_password('Box_Auth', 'mybox#box.com', access_token)
keyring.set_password('Box_Refresh', 'mybox#box.com', refresh_token)
def main():
"""Authentication against Box Example"""
# Retrieve tokens from secure store
access_token, refresh_token = read_tokens()
# Set up authorisation using the tokens we've retrieved
oauth = OAuth2(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
access_token=access_token,
refresh_token=refresh_token,
store_tokens=store_tokens,
)
# Create the SDK client
client = Client(oauth)
# Get current user details and display
current_user = client.user(user_id='me').get()
print('Box User:', current_user.name)
if __name__ == '__main__':
main()
I suggest taking a look at the OAuth 2 tutorial. It will help give a better understanding of how OAuth works and what the various parameters are used for.
The redirect URL is set in your Box application's settings:
This is the URL where Box will send an auth code that can be used to obtain an access token. For example, if your redirect URL is set to https://myhost.com, then your server will receive a request with a URL that looks something like https://myhost.com?code=123456abcdef.
Note that your redirect URI doesn't need to be a real server. For example, apps that use a WebView will sometimes enter a fake redirect URL and then extract the auth code directly from the URL in the WebView.
The store_tokens callback is optional, but it can be used to save the access and refresh tokens in case your application needs to shutdown. It will be invoked every time the access token and refresh token changes, giving you an opportunity to save them somewhere (to disk, a DB, etc.).
You can then pass in these tokens to your OAuth2 constructor at a later time so that your users don't need to login again.
If you're just testing, you can also pass in a developer token. This tutorial explains how.
This is the most basic example that worked for me:
from boxsdk import Client, OAuth2
CLIENT_ID = ''
CLIENT_SECRET = ''
ACCESS_TOKEN = '' # this is the developer token
oauth2 = OAuth2(CLIENT_ID, CLIENT_SECRET, access_token=ACCESS_TOKEN)
client = Client(oauth2)
my = client.user(user_id='me').get()
print(my.name)
print(my.login)
print(my.avatar_url)