I have followed the docs about Oauth2 but it does not describe the proccess to add client id and secret
https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/
and what this does
class UserInDB(User):
hashed_password: str
from the original example
In documentation it uses OAuth2PasswordRequestForm to authenticate the user this class has basically 6 different Fields,
grant_type: str = Form(None, regex="password"),
username: str = Form(...),
password: str = Form(...),
scope: str = Form(""),
client_id: Optional[str] = Form(None),
client_secret: Optional[str] = Form(None),
So you can add client_id and client_secret,if you are interested Repository here.
But i usally prefer authlib, it saves so much time makes it easier. Here is a complete example of how you can create a OAuth with authlib
First create a OAuth Client
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
config = Config('.env') # read config from .env file
oauth = OAuth(config)
oauth.register(
name='google',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid email profile'
}
)
We don't need to add client_id and client_secret here, because they are in .env file. You are not supposed to hard code them in the code in real products.Google has an OpenID discovery endpoint, we can use this URL for server_metadata_url. Authlib will automatically fetch this server_metadata_url to configure the OAuth client for you.
Now we will create a FastAPI application to define a login route.
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="secret-string")
We need this SessionMiddleware, because Authlib will use request.session to store temporary codes and states. The below code which is /login endpoint, will redirect you to Google account website.
#app.route('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
return await oauth.google.authorize_redirect(request, redirect_uri
When you grant access from Google website, Google will redirect back to your given redirect_uri, which is request.url_for('auth'):
#app.route('/auth')
async def auth(request: Request):
token = await oauth.google.authorize_access_token(request)
user = await oauth.google.parse_id_token(request, token)
return user
The above code will obtain a token which contains access_token and id_token. An id_token contains user info, we just need to parse it to get the login user's information.
Sources: Authlib-FastAPI-Google-Login
Also if you still wanna use Pure FastAPI check this link FastAPI OAuth2PasswordRequestForm
Related
I have a Flask backend server that integrates Reddit's OAuth integration via PRAW, and a VueJS Frontend.
The backend APIs for handling the OAuth cycle look like this:
#api_user.route('/login', methods=['GET'])
def login() -> Response:
reddit: Reddit = Reddit()
auth_url: str = reddit.auth.url(scopes=SCOPES, state=STATE)
auth_url = urllib.parse.unquote(auth_url) # remove url encodings
return redirect(auth_url)
#api_user.route('/auth_callback', methods=['GET'])
def callback() -> Tuple[Response, int]:
code: str = request.args.get('code', '')
reddit = Reddit()
refresh_token: str = reddit.auth.authorize(code)
me: Redditor = reddit.user.me()
token = jwt.encode({
'refresh_token': refresh_token,
'user': {
'name': me.name,
'id': me.id
}
}, str(SECRET_KEY), algorithm=ALGORITHM)
response: Response = make_response(jsonify({
'token': token
}))
response.set_cookie('access_token', token, max_age=COOKIE_MAX_AGE)
return response, 200
#api_user.route('/me', methods=['GET'])
#jwt_required
def about(reddit: Reddit) -> Tuple[Response, int]:
user: Redditor = reddit.user.me()
return jsonify({
'name': user.name,
'id': user.id,
'karma': user.link_karma + user.comment_karma
}), 201
/api/login redirects to Reddit's authorization URL where user can provide their username/password, which then redirects the callback uri /api/auth_callback (set in the Reddit app configuration), which then generates a refresh token and sends it as a JWT Token to be stored as a cookie.
What I would like to know is how would I connect this flow with a separate VueJS frontend. Even if the VueJS login page /login calls the API /api/login, after the OAuth is finished it will end up at the server-side route /api/auth_callback?code=xyz.
Integrating VueJS in the Jinja2 Template and making the whole application contained within Flask can work, but it becomes messy quickly.
Another way is to make the redirect_uri be a VueJS route, not a backend route. But in that case, I would like to know how can I parse the URL and extract the code parameter (like I did in the /auth_callback Flask route handler). In that case, I can get rid of /api/auth_callback and add /api/jwt which takes the code parameter extracted by the Vue frontend and returns the JWT token after validating using PRAW.
Any kind of help would be appreciated. Thanks.
I've been trying to use Flask-dance's OAuth2ConsumerBlueprint to make requests to the Fitbit API. So far, I've managed to get the authorization page to come up on my Flask app but I haven't been able to route to a page where I can view the data yet.
In browser, when I try to make a request I get the output of the CURL response {"errors":[{"errorType":"system","fieldName":"n/a","message":"Authorization Error: Invalid authorization token type"}],"success":false}
My goal right now is just to be able to view the user's fitbit dashboard in my app through the API by making a request through the app to "https://api.fitbit.com/1/user/-/profile.json"
This is my code so far. If anyone could offer some guidance as to where I'm going wrong with the Oauth2.0 authorization code flow, flask, or the fitbit api I would greatly appreciate it.
from flask import Flask, redirect, url_for, render_template
from flask_dance import OAuth2ConsumerBlueprint
import os
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
CLIENT_ID = 'CLIENT_ID' # OAuth 2.0 Client ID
CLIENT_SECRET = CLIENT_SECRET'
scope = ["activity",
"nutrition",
"heartrate",
"location",
"nutrition",
"profile",
"settings",
"sleep",
"social",
"weight",
]
# Flask OAuth2 Custom Blueprint for Fitbit API
app = Flask(__name__)
fitbit_blueprint = OAuth2ConsumerBlueprint(
"fitbit-api", __name__,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
base_url="https://www.fitbit.com",
token_url="https://api.fitbit.com/oauth2/token",
authorization_url="https://www.fitbit.com/oauth2/authorize",
scope=scope
)
app.register_blueprint(fitbit_blueprint, url_prefix="/login")
app.secret_key = "supersecret" # Replace this
app.token = fitbit_blueprint.token
print(app.token)
#app.route("/")
def index():
#return redirect(url_for("fitbit-api.login"))
return render_template('index.html')
#app.route("/success")
def access():
return "Success"
#app.route('/login')
def login():
return redirect(url_for("fitbit-api.login"))
# Redirect URI = http://127.0.0.1:
if __name__ == '__main__':
app.run(host="localhost", port=5000, debug=True)
You can not access the (user profile) resource before you get authorized from fitbit. Once you got authorized, you will need to exchange your authorization code with a Token Pair and save the tokens - namely Access Token and Refresh Token - somewhere in your code.
Solely being able to reach and pass the authorization page does not mean that you've been authorized. You can check if your authorization is completed within your fitbit profile:
Open your Fitbit profile -> My Dashboard -> Settings. From the left panel choose Applications. There you should be able to see a list of authorized apps: If you don't, you haven't changed your authorization code with a token pair yet!
You can only then be able to make requests to the user-profile endpoint in which case you have to build up an http post with your valid saved Access Token in its header as in https://dev.fitbit.com/build/reference/web-api/oauth2/#making-requests.
I am having trouble implementing authentication verification in an GraphQL Server built with FastAPI. Before, we were using REST but now we are switching to GraphQL and I am wondering how I can implement this. Before, we had different routers and with FastAPI it is easy to check authentication based on routes using dependencies as in here. We are sending a Token in the Authorization Header which we are decoding in the backend and getting back the user_id which we can then use in our different endpoints.
I am wondering how this might work using GraphQL here. We use the Graphene and I had a look at Starlettes Authentication Examples as well as there intro into setting up GraphQl
import binascii
from fastapi import FastAPI
from starlette.authentication import (
AuthenticationBackend, AuthenticationError, SimpleUser, AuthCredentials
)
from starlette.graphql import GraphQLApp
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from schemas.root import my_schema
class BasicAuthBackend(AuthenticationBackend):
async def authenticate(self, request):
if "Authorization" not in request.headers:
raise AuthenticationError('No auth credentials')
auth = request.headers["Authorization"]
try:
id_token = auth.split('Bearer ')[1]
decoded_token = auth.verify_id_token(id_token)
except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
raise AuthenticationError('Invalid basic auth credentials')
user_id = decoded_token['uid']
return AuthCredentials(["authenticated"]), user_id
middleware = [
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]
my_schema = Schema(
query=RootQuery,
mutation=RootMutation,
)
api = FastAPI(title=f"MyGraphQLServer", middleware=middleware)
api.add_route("/graphql", GraphQLApp(schema=my_schema))
For example, imagine that I now would only like to authenticate mutation Requests but not query requests. Furthermore I want to access the user_id in each of my resolvers. What would be the best way to do this?
In FastAPI documentation or starlette documentation they use add_route, that is the way to add a route in Starlette without declaring the specific operation (as would be with .get(), .post(), etc). But it has some disadvantages, we can't add dependencies like we do in FastAPI, example below
app.add_route(
"/graphql",
GraphQLApp(schema=graphene.Schema(query=Query),
executor_class=AsyncioExecutor),
dependencies=(Depends(SomeAuthorizationStuffHere)),
)
So we need to do in FastAPI, i created a simple app with HTTPBasicAuth, you can expand this with other Method's you just need to include the router(s)
from fastapi import Query, Depends, Request, FastAPI, APIRouter
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import graphene
from graphene import Field, Schema, String, ObjectType
from starlette.graphql import GraphQLApp
router = APIRouter()
app = FastAPI()
security = HTTPBasic()
class Query(ObjectType):
hello = Field(String, name=String())
def resolve_hello(root, info, name):
coverage = info.context["request"].state.some_dep
return f"Hello {some_dep.some_method(name)}"
graphql_app = GraphQLApp(schema=Schema(query=Query))
#router.api_route("/gql", methods=["GET", "POST"])
async def graphql(request: Request):
return await graphql_app.handle_graphql(request=request)
app.include_router(router, dependencies=[Depends(security)])
I am working with google cloud functions in python and I am facing the following difficulties:
I don't know how to add a token to the automatically generated endpoint for my HTTP cloud function :
Below is the code I deployed :
from flask import Flask, request
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
PROJECT = "projectname"
app = Flask(__name__)
#app.route('/', methods=['PUT', 'POST'])
#auth.login_required
def httpfunction(request):
data = request.stream.read()
return data
The generated URL is :
https://europe-west1-projectname.cloudfunctions.net/httpfunction
I want to add a token to it in order to avoid inappropriate persons to make use it , so basically here I want to avoid POST and PUT requests from unauthorized users.
Any ideas how to do this please ?
HTTPBasicAuth is not using tokens but username and password. To query the endpoint with username and password you should first modify your code to something like this:
from flask import Flask, request
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
users = {
"Obama": "WhiteHouse1",
"Trump": "WhiteHouse2"
}
#auth.get_password
def get_pw(username):
if username in users:
return users.get(username)
return None
app = Flask(__name__)
#app.route('/', methods=['PUT', 'POST'])
#auth.login_required
def httpfunction(request):
data = request.stream.read()
return data
Than you can query the endpoint with providing the username and password. For example a query with cURL would look like this:
curl -u Trump:WhiteHouse2 -X POST https://europe-west1-projectname.cloudfunctions.net/httpfunction
For queries using tokens, take a look at HTTPTokenAuth.
Look into the Stack Overflow thread Secure Google Cloud Functions http trigger with auth for other ways of authentication when using Cloud Functions. Implementation is described in Node.js but the idea can be implemented with python as well.
When I make a redirect from main.py, it works, but when I try to redirect from within a method that it calls, nothing happens. There is no error, the program simply does nothing.
main.py
from githubauth import GetAuthTokenHandler
class AuthUser(webapp2.RequestHandler):
"""
If no environment variable exists with the access token in it,
auth the user as an admin. If it does exist, auth them as a regular
user.
"""
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = GetAuthTokenHandler()
get_auth_token.get()
githubauth.py
import webapp2
class GetAuthTokenHandler(webapp2.RequestHandler):
"""Redirect users to github to get an access request token."""
def get(self):
self.redirect('http://api.github.com/authorize')
It depends on what kind of authorization you're doing with Github, there are two ways to do that, OAuth token authorization and Web Application Flow.
OAuth Token Authorization
If you're doing OAuth authorization, you don't have to create a request handler to fetch Github auth token, request handler is for serving specific url on your server, for this kind of task, you should use urlfetch().
So the whole flow should be like the following code:
import webapp2
from google.appengine.api import urlfetch
def getAuthToken():
github_auth_url = "http://api.github.com/authorizations"
result = urlfetch.fetch(github_auth_url)
return result
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = getAuthToken()
# do something with your token...
Redirect Authorization (Web Application Flow)
This is the case if you have applied a client id, and want to be authorized by users as a standalone web application, the steps of this kind authorization is more complicated than former one:
Redirect users to request GitHub access
GitHub redirects back to your site
If you don't know about this flow, take a look at Github OAuth - Web Application Flow
Let's see how could we do within Google App Engine
Redirect users to request Github access
This is the part which involved in your sample, simply redirect user to the authorize url with specified parameters
from urllib import urlencode
class AuthUser(webapp2.RequestHandler):
def get(self):
# ... do something ...
# Github configuration
github_client_id = "Your github client id..."
github_redirect_url = "Your url for github redirecting users back to your GAE"
github_scope = "Gtihub scopes...."
github_authorize_url = "http://github.com/login/oauth/authorize"
github_authorize_parameters = {
'client_id': github_client_id,
'redirect_url': github_redirect_url,
'scope': github_scop
}
if not ACCESS_TOKEN:
# if no access_token found on your site, redirect users to Github for authorization
url_to_redirect = "%s?%s" % (github_authorize_url, urlencode(github_authorize_parameters))
self.redirect(url_to_redirect)
Github redirects users back to your site
Github will redirect users back to your site based on the previous parameter redirect_url, so you will have to prepare another request handler for receiving redirection from Github.
(You can do this is the same request handler, but it will mess your code)
The redirection back from the step 1 will contains one parameter, code, you will need it to exchange for an access token.
from urllib import urlencode
class GithubRequestHandler(webapp2.RequestHandler):
def get(self):
# this handler need to be bind to redirect_url
# get authentication code
github_code = self.request.get('code')
# prepare data to exchange access token
github_token_url = "https://github.com/login/oauth/access_token"
github_token_parameters = {
'client_id': 'Your Github client id',
'client_secret': 'Your Github client secret',
'code': github_code}
# exchange access token
data = urlfetch.fetch(github_token_url, payload=urlencode(github_token_parameter), method='POST')
# data will perform in the following form:
# access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer
# extract access_token from the string
# save the access_token to user's model
P.S.
the code is kinda of simulation of your application flow, it needs some tuning to be able to run on production :)
You try to create a webapp2 request handler, but it cannot be done this way. get_auth_token is not a WSGI webapp2 handler instance.
If you do not change githubauth.py you have to change your main.py.
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
self.redirect(to your GetAuthTokenHandler)
This will result in two redirects if you do not have an access token.
RequestHandler needs to be instantiated with a request and a response for things to work properly.
That said, instantiating one and calling methods on it from inside the handler-method of another is pretty weird.