How to add jwt token integration with fastapi authlib oauth? - python-3.x

I have this main.py file. I am creating a jwt token here at /auth endpoint. After the token is generated, now I am unable to redirect it to base path("/"). How can I achieve that. If I try to access the / path, i get redirected to auth endpoint with the bearer token displayed. Any help or pointers on how this can be done.
main.py
from authlib.integrations.starlette_client import OAuth
oauth = OAuth()
CONF_URL = "https://localhost:8080/.well-known/openid-configuration"
oauth.register(
name="cad",
server_metadata_url=CONF_URL,
client_id=settings.CLIENT_ID,
client_secret=settings.CLIENT_SECRET,
client_kwargs={"scope": "openid email profile authorization_group"},
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/auth')
def create_access_token(*, data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.datetime.utcnow() + expires_delta
else:
expire = datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
to_encode.update({'exp': expire})
encoded_jwt = jwt.encode(to_encode, "abcd", algorithm="HS256")
return encoded_jwt
def create_token(id):
access_token_expires = datetime.timedelta(minutes=120)
access_token = create_access_token(data={'sub': id}, expires_delta=access_token_expires)
return access_token
#app.middleware("http")
async def authorize(request: Request, call_next):
if not (request.scope["path"].startswith("/login") or request.scope["path"].startswith("/auth")):
if not is_session_okay(request.session):
return RedirectResponse(url="/login")
return await call_next(request)
#app.get("/login")
async def login(request: Request):
redirect_uri = request.url_for("auth")
return await oauth.cad.authorize_redirect(request, redirect_uri)
#app.get("/auth")
async def auth(request: Request):
try:
token = await oauth.cad.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
user = await oauth.cad.parse_id_token(request, token)
access_token = create_token(user['sub'])
return {"access_token": access_token, "token_type": "bearer"}
#app.get("/", tags=["Web-UI"])
def index():
frontend_root = "./ui"
return FileResponse(str(frontend_root) + "/index.html", media_type="text/html")

Since you are using fastapi you should use it's jwt implementation.
The framework provide classes especialy for that => OAuth2PasswordBearer and OAuth2PasswordRequestForm
the doc on this is available in the advanced documentation:
(from the doc)
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
class Token(BaseModel):
access_token: str
token_type: str
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
#app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}

Related

It cannot auth with custom token auth class

I tried to make custom auth class (DEFAULT_AUTHENTICATION_CLASSES), I want to auth with Bearer token. I could register an user, but I cannot login as an auth user.
I'm stuck. I used example and now I cannot get some info about auth token
authentication module
class BearerTokenAuthentication(BaseAuthentication):
keyword = 'Bearer'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
try:
access_token = auth[-1]
payload = jwt.decode(
access_token, settings.SECRET_KEY, algorithms=["HS256"]
)
except jwt.ExpiredSignatureError:
raise exceptions.AuthenticationFailed("The access token expired")
except IndexError:
raise exceptions.AuthenticationFailed("Bearer prefix missing")
return self.authenticate_credentials(payload["id"])
def authenticate_credentials(self, user_id: str):
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed("Invalid token.")
return user, None
I guess I missed some settings because the auth variable is None. I wrote in the settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'custom_authentication.authentication.BearerTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}
AUTH_USER_MODEL = 'users.User'
serializer module
class LoginSerializer(serializers.ModelSerializer):
email = serializers.EmailField(max_length=255)
password = serializers.CharField(max_length=68, min_length=6, write_only=True)
confirm_password = serializers.CharField(write_only=True)
access = serializers.CharField(max_length=555, min_length=1, read_only=True)
refresh = serializers.CharField(max_length=555, min_length=1, read_only=True)
class Meta:
model = User
fields = ["id", "email", "password", "confirm_password", "access", "refresh"]
def validate(self, attrs):
email = attrs.get("email", "")
password = attrs.get("password", "")
confirm_password = attrs.get("confirm_password", "")
if password != confirm_password:
raise exceptions.ValidationError("Those password don't match")
validate_password(password)
user = auth.authenticate(email=email, password=password)
if not user:
raise AuthenticationFailed("Invalid credentials, try it again")
return {
"id": user.id,
"email": user.email,
"access": user.get_access().get("access"),
"refresh": user.get_refresh().get("refresh"),
}
Now I'm getting this "Invalid credentials, try it again". The user variable is None, too

Can I use urlencode in python to insert a string of characters into this Spotify API request?

I am working with Spotify API to request song data through a URL. I imported urlencode to run the song's ID as a function parameter into the url. Essentially, I need the ID portion of the url request to be the ID by itself, not "id=<id string>"
I tried assigning the id string to a parameter of my 'search' function. The search function takes a user input song ID from spotify, inserts it into the proper position in the URL request, and sends it to the spotify database to retrieve that songs data analysis. The program successfully sends out the request, but the id portion I am filling in puts "id=<song ID>" instead of the song ID by itself.
import requests
import datetime
from urllib.parse import urlencode
# In[3]:
import base64
# In[4]:
client_id = 'fb5af83351d4402fa82904fc04f7fc9e'
client_secret = 'b5057eb39b024180b61b02eb45fb97a6'
# In[5]:
class SpotifyAPI(object):
access_token = None
access_token_expires = datetime.datetime.now()
access_token_did_expire = True
client_id = None
client_secret = None
token_url = "https://accounts.spotify.com/api/token"
def __init__(self, client_id, client_secret, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client_id = client_id
self.client_secret = client_secret
def get_client_credentials(self):
client_id = self.client_id
client_secret = self.client_secret
if client_secret == None or client_id == None:
raise Exception("You must set client_id and client_secret")
client_creds = f"{client_id}:{client_secret}"
client_creds_b64 = base64.b64encode(client_creds.encode())
return client_creds_b64.decode()
def get_token_headers(self):
client_creds_b64 = self.get_client_credentials()
return {
"Authorization": f"Basic {client_creds_b64}"
}
def get_token_data(self):
return {
"grant_type": "client_credentials"
}
def perform_authorization(self):
token_url = self.token_url
token_data = self.get_token_data()
token_headers = self.get_token_headers()
r = requests.post(token_url, data=token_data, headers=token_headers)
print(r.json())
if r.status_code not in range(200,299):
raise Exception("Could not authenticate client")
#return False
data = r.json()
now = datetime.datetime.now()
access_token = data['access_token']
expires_in = data['expires_in'] #seconds
expires = now + datetime.timedelta(seconds=expires_in)
self.access_token = access_token
self.access_token_expires = expires
self.access_token_did_expire = expires < now
return True
def get_access_token(self):
token = self.access_token
expires = self.access_token_expires
now = datetime.datetime.now()
if expires < now:
self.perform_authorization()
return self.get_access_token()
elif token == None:
self.perform_authorization()
return self.get_access_token()
return token
def search(self, id):
access_token = self.get_access_token()
headers = {
"Authorization": f"Bearer {access_token}"
}
end_point = "https://api.spotify.com/v1/audio-analysis/"
data = urlencode({"id":id})
print(data)
lookup_url = f"{end_point}{data}"
print(lookup_url)
r = requests.get(lookup_url, headers = headers)
if r.status_code not in range(200, 299):
return r
return r.json()
it returns
{'access_token': 'BQCLoKT_b2PF7KPSbscosa1dCpE5rzd_RBkswOvwklVdlAL4AeEGCDn0iYuqac5o86BTqCIz0m95u3olLp4', 'token_type': 'Bearer', 'expires_in': 3600}
id=1UGD3lW3tDmgZfAVDh6w7r
https://api.spotify.com/v1/audio-analysis/id=1UGD3lW3tDmgZfAVDh6w7r

Unable to use accessToken instead of idToken - msal python

I am validating my Bearer token through JWT in Python and it was earlier written in a way to handle idTokens only. We just moved to the new auth code flow pattern on the UI and they advice me to use accessToken instead of idToken for token validation. The app works great E2E if I use idToken, however when I use accessToken in the Bearer auth, the validation fails. I get a 401 unauthorized.
Please advice.
Here is my python code:
import os
import sys
import requests
import time
import calendar
from functools import wraps
from jose import jwk, jwt, JWTError
from flask import abort, current_app, request
from src.database import db
from src.secrets import derive_base64_secret
from src.models.user import User, UserRoleEnum
CLIENT_ID = os.environ.get("AZURE_AD_CLIENT_ID")
TENANT_ID = os.environ.get("AZURE_AD_TENANT_ID")
AUTHORITY_MSAL = f"https://login.microsoftonline.com/{TENANT_ID}/v2.0"
OIDC = requests.get(f"{AUTHORITY_MSAL}/.well-known/openid-configuration").json()
MSAL_JWKS = requests.get(OIDC["jwks_uri"]).json()
S2S_JWK = {"alg": "HS256", "kty": "oct", "k": derive_base64_secret("S2S JWT", 32)}
def validate_msal_token(token):
try:
return jwt.decode(token, MSAL_JWKS, audience=CLIENT_ID, issuer=[AUTHORITY_MSAL])
except JWTError:
abort(401)
def validate_s2s_token(token):
try:
return jwt.decode(token, S2S_JWK, audience=CLIENT_ID, issuer=CLIENT_ID)
except JWTError:
abort(401)
def create_s2s_token(ttl, additional_claims):
now = calendar.timegm(time.gmtime())
claims = {
"iss": CLIENT_ID,
"aud": CLIENT_ID,
"iat": now,
"exp": now + ttl,
**additional_claims,
}
return jwt.encode(claims, S2S_JWK)
def get_access_token():
authorization = request.headers.get("Authorization")
if isinstance(authorization, str) and authorization.startswith("Bearer "):
return authorization[7:]
return request.args.get("access_token")
def load_user():
access_token = get_access_token()
if not access_token:
abort(401)
token = validate_msal_token(access_token)
if not token:
abort(401)
oid = token.get("oid", None)
if not oid:
abort(403)
user = User.query.filter_by(OID=oid).first()
if not user:
# Auto add the first user to access the API as an Admin IN DEVELOPMENT ONLY
if current_app.env == 'development' and User.query.count() == 0:
user = User(OID=oid, Display_Name="Unknown", User_Name="unknown#example.com", Role=0)
db.session.add(user)
else:
abort(403)
display_name = token.get("name", None)
if display_name and user.Display_Name != display_name:
user.Display_Name = display_name
# `preferred_username` is supplied on a v2 id token as provided by the FM
# `unique_name` is supplied on a v1 id token as provided by the MDA
user_name = token.get("preferred_username", token.get("unique_name", None))
if user_name and user.User_Name != user_name:
user.User_Name = user_name
db.session.commit()
request.token = token
request.user = user
return user
def jwt_required(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
access_token = get_access_token()
if not access_token:
abort(401)
token = validate_s2s_token(access_token)
if not token:
abort(401)
request.token = token
return fn(*args, **kwargs)
return wrapper
def user_required(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
load_user()
return fn(*args, **kwargs)
return wrapper
Thank you for sharing your code. You should receive a response from the bearer auth and be granted access. It appears that your credentials are insufficient for successful authentication resulting in a 401 unauthorized. Based on the situation, perform a secondary check on the cause of failure. Token validation is not required for all apps. Apps should only validate a token in the following circumstances listed here. APIs and web apps can only validate tokens with an aud claim that matches their app; other resources may have their own token validation requirements. Learn more about managing access tokens here.

How can I add Authorization header to a request so that a flask route with #jwt_required can be access if access token is present on the headers

I'm trying to implement flask_jwt_extended to my flask app.
My Use Case is, I want to set Authorization headers to every request. So that when a #jwt_required decorator is decorated to a flask route it can be access if an access token is present on the Authorization headers.
I've tried some solutions like the one below:
I use #app.after_request to attach headers to every request but still it gives me this response
{
"msg": "Missing Authorization Header"
}
Here is my code:
#app.after_request
def add_headers(resp):
access_token = session.get("access_token", None)
if access_token is not None:
resp.headers["Authorization"] = f"Bearer {access_token}"
return resp
return resp
my login route:
#app.route('/', methods=["GET", "POST"])
def login():
if request.method == "POST":
form = request.form
_username = form["username"]
_password = form["password"]
for username in Users:
if username.get(_username):
if safe_str_cmp(username[_username]["password"],_password):
access_token = create_access_token(identity=username[_username]["user_id"], fresh = True)
session["access_token"] = access_token
res = make_response(redirect(url_for("home")))
res.headers["Authorization"] = f"Bearer {access_token}"
return res
else:
return "Incorrect password"
return f"Hello{_username} doesn't exist"
return render_template("login.html")
Here is my protected route:
#app.route('/home')
#jwt_required
def home():
res = Response(render_template("base.html"))
return res
I've also tried adding my headers in the route,But it still the headers I specify are not recognize and still give me the same response message. Here's how do it
#app.route('/home')
#jwt_required
def home():
access_token = session.get("access_token", None)
print(access_token)
if access_token is not None:
res = Response(render_template("base.html"), headers={"Authorization" : f"Bearer {access_token}"})
return res
res = Response(render_template("base.html"))
return res
Writing this for those who will come across this and are trying to implement an OAuth flow
Don't use decorators use middleware instead
I believe you should handle this in a middleware. In the middleware, you can set the authorization property of the 2nd parameter of the call function which contains the current wsgi app environment variables you have passed in your init function. Look at code below:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
cookie = Request(environ).cookies.get('access_token')
if cookie is not None:
environ['HTTP_AUTHORIZATION']='Bearer '+cookie
return self.app(environ, start_response)

Basic Authentication not working in python

I am implementing basic authentication to validate username and password with the following code:
def auth_required(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
cur = get_db().cursor().execute("SELECT * FROM users where user_name='parag'")
result = cur.fetchall()
if auth and auth.username == result[0][user_name] and auth.password== result[0][password]:
return True
return make_response('could not verify', 401, {'WWW-authenticate' : 'Basic realm = "Login Required"'})
return decorated
#app.route("/")
#auth_required
def index():
data = request.get_json()
cur = get_db().cursor())
return '''<h1> hi paarg </h1>'''
but auth.username and auth.password is none.
auth = request.authorization is coming as a NoneType.
Please help.

Resources