Why do I get a 200 from a authentication-required request using expired access token with django rest framework simple jwt? - django-rest-framework-simplejwt

I am building a REST API with DRF using djangorestframework-simplejwt(5.2.1)
When I try to access a protected endpoint which requires an authentication with an expired token, expecting a 401. Instead, I get a positive answer from the API every single time.
Here is the test view:
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def test(request:HttpRequest) -> Response:
return JsonResponse('OK')
Here is the urls.py:
from django.contrib import admin
from django.urls import path,include
from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView
from account.views import AccountTokenObtainPairView
from base.views import test
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('api/test/', test),
path('api/token/', AccountTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
And here, the postman request with the result I got:
As last my settings.py :
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':('rest_framework_simplejwt.authentication.JWTAuthentication',),
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
#'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
by calling /api/test/ I was expected django to send back an answer telling me that the request could not be fullfilled since the access token was expired.
I did check that the token is indeed expired:
Thank you for your help!

Related

FastAPI auth check before granting access to sub-applications

I am mounting a Flask app as a sub-application in my root FastAPI app, as explained in the documentation
Now I want to add an authentication layer using HTTPAuthorizationCredentials dependency, as nicely explained in this tutorial
tutorial code
How can I do that?
Preferably, I would like that any type of access attempt to my Flask sub-application goes first through a valid token authentication process implemented in my FastAPI root app. Is that possible?
You can use a custom WSGIMiddleware and authorize the call to flask app inside that like this:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.wsgi import WSGIMiddleware
from flask import Flask, escape, request
from starlette.routing import Mount
from starlette.types import Scope, Receive, Send
flask_app = Flask(__name__)
def authenticate(authorization: str = Header()):
# Add logic to authorize user
if authorization == "VALID_TOKEN":
return
else:
raise HTTPException(status_code=401, detail="Not Authorized")
class AuthWSGIMiddleware(WSGIMiddleware):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
_, authorization = next((header for header in scope['headers'] if header[0] == b'authorization'), (b'authorization', "" ))
authenticate(authorization.decode('utf-8'))
await super().__call__(scope, receive, send)
routes = [
Mount("/v1", AuthWSGIMiddleware(flask_app)),
]
# OR Optionally use this as you were doing
# The above one is preferred as per starlette docs
# app.mount("/v1", WSGIMiddleware(flask_app))
#flask_app.route("/")
def flask_main():
name = request.args.get("name", "World")
return f"Hello, {escape(name)} from Flask!"
app = FastAPI(routes=routes, dependencies=[Depends(authenticate)])
#app.get("/v2")
def read_main():
return {"message": "Hello World"}
For the tutorial you are looking at, you can replace the invoking authenticate function in my example inside AuthWSGIMiddleware->__call__() with
AuthHandler().decode_toke(authorization)

Flask OIDC with Keycloak returning None when trying to get 'openid_id' and 'role' fields

I'm trying to build some test applications using Flask-ODBC + Keycloak.
I successfully started Keycloak, created a Realm, a role and an user to who this role is assigned. Now I'm trying to get the 'role' and 'openid_id' fields from the OpenIDConnect object but these two fields are returning None.
My application code is the following
import json
import flask
from flask import Flask, render_template, g
import flask_login
from flask_login import login_required
from flask_oidc import OpenIDConnect
from model.user import User
app = Flask(__name__)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
# Our mock database.
users = {'foo#bar.com': {'pw': 'secret'}}
app.config.update({
'SECRET_KEY': 'u\x91\xcf\xfa\x0c\xb9\x95\xe3t\xba2K\x7f\xfd\xca\xa3\x9f\x90\x88\xb8\xee\xa4\xd6\xe4',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_ID_TOKEN_COOKIE_SECURE': False,
'OIDC_REQUIRE_VERIFIED_EMAIL': False,
'OIDC_VALID_ISSUERS': ['http://localhost:8080/auth/realms/MyDemo'],
'OIDC_OPENID_REALM': 'http://localhost:5000/oidc_callback'
})
oidc = OpenIDConnect(app)
#app.route('/')
def hello_world():
if oidc.user_loggedin:
return ('Hello, %s, See private '
'Log out') % \
oidc.user_getfield('email')
else:
return 'Welcome anonymous, Log in'
#app.route('/private')
#oidc.require_login
def hello_me():
info = oidc.user_getinfo(['email', 'openid_id', 'role'])
print(info)
return ('Hello, %s (%s)! Return' %
(info.get('email'), info.get('openid_id')))
#app.route('/api')
#oidc.accept_token(True, ['openid'])
def hello_api():
return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']})
#app.route('/logout')
def logout():
oidc.logout()
return 'Hi, you have been logged out! Return'
if __name__ == '__main__':
app.run('localhost', port=5000)
Inside hello_me() I try to get the 'openid_id' and 'role' and print it. The problem is I'm getting None in these fields. I can get the email correctly.
Can you help me with finding out what mistakes I'm making?
https://flask-oidc.readthedocs.io/en/latest/
user_getinfo(fields, access_token=None)
Returns: The values of the current user for the fields requested. The keys are the field names, values are the values of the fields as indicated by the OpenID Provider. Note that fields that were not provided by the Provider are absent.
Your provider (Keycloak) doesn't expose openid_id details in the token apparently, so field is absent. Very likely you didn't configure OIDC client in the Keycloak correctly. Make sure you have added correct mappers/scopes to used OIDC client in the Keycloak, which expose requested details into openid_id claim.

How to set HTTP password in gerrit while making a REST call using python-requests?

Using requests module want to query Gerrit server and for this need set authentication in below code. And this needs to be done without using any other module like pygerrit2. And if this can be done using HTTP password generated from gerrit. If this is not doable, share an example if auth has to be passed along with Get request.
import requests
import json
import os
GERRIT_SERVER = 'https://gerrit.abc.com'
class GerritServer():
def __init__(self):
self._base_url = GERRIT_SERVER
self._endpoints = {
'CHANGES': 'changes',
}
def _get_endpoint(self, token):
try:
endpoint = self._endpoints[token]
except ValueError:
raise Exception('endpoint not defined for {0}'.format(token))
return '{0}/{1}'.format(self._base_url, endpoint)
def get_endpoint_changes(self):
return self._get_endpoint('CHANGES')
Gerrit's REST API uses digest auth.
username = 'foo'
httpassword = 'bar'
api = 'baz'
auth = requests.auth.HTTPDigestAuth(username, httpassword)
r = requests.get(api, auth=auth)

getting NoAuthorization Header Missing Exception while using flask-jwt-extended

When I try this example and if the jet token is not provided by header I get error:
{
"msg": "Missing cookie \"access_token_cookie\""
}
example:
from flask import Flask, jsonify, request
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
jwt_refresh_token_required, create_refresh_token,
get_jwt_identity, set_access_cookies,
set_refresh_cookies, unset_jwt_cookies
)
from flask_jwt_extended.config import config
# NOTE: This is just a basic example of how to enable cookies. This is
# vulnerable to CSRF attacks, and should not be used as is. See
# csrf_protection_with_cookies.py for a more complete example!
app = Flask(__name__)
# Configure application to store JWTs in cookies. Whenever you make
# a request to a protected endpoint, you will need to send in the
# access or refresh JWT via a cookie.
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
# Set the cookie paths, so that you are only sending your access token
# cookie to the access endpoints, and only sending your refresh token
# to the refresh endpoint. Technically this is optional, but it is in
# your best interest to not send additional cookies in the request if
# they aren't needed.
app.config['JWT_ACCESS_COOKIE_PATH'] = '/api/'
app.config['JWT_REFRESH_COOKIE_PATH'] = '/token/refresh'
# Disable CSRF protection for this example. In almost every case,
# this is a bad idea. See examples/csrf_protection_with_cookies.py
# for how safely store JWTs in cookies
app.config['JWT_COOKIE_CSRF_PROTECT'] = False
# Set the secret key to sign the JWTs with
app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this!
jwt = JWTManager(app)
# Use the set_access_cookie() and set_refresh_cookie() on a response
# object to set the JWTs in the response cookies. You can configure
# the cookie names and other settings via various app.config options
#app.route('/token/auth', methods=['POST'])
def login():
# username = request.json.get('username', None)
# password = request.json.get('password', None)
# if username != 'test' or password != 'test':
# return jsonify({'login': False}), 401
# print dir(config)
# Create the tokens we will be sending back to the user
access_token = create_access_token(identity="test")
refresh_token = create_refresh_token(identity="test")
# Set the JWT cookies in the response
resp = jsonify({'login': True, "cookie_key": config.access_cookie_name, "cooke_value": access_token})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp, 200
# Same thing as login here, except we are only setting a new cookie
# for the access token.
#app.route('/token/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
# Create the new access token
current_user = get_jwt_identity()
access_token = create_access_token(identity=current_user)
# Set the JWT access cookie in the response
resp = jsonify({'refresh': True})
set_access_cookies(resp, access_token)
return resp, 200
# Because the JWTs are stored in an httponly cookie now, we cannot
# log the user out by simply deleting the cookie in the frontend.
# We need the backend to send us a response to delete the cookies
# in order to logout. unset_jwt_cookies is a helper function to
# do just that.
#app.route('/token/remove', methods=['POST'])
def logout():
resp = jsonify({'logout': True})
unset_jwt_cookies(resp)
return resp, 200
# We do not need to make any changes to our protected endpoints. They
# will all still function the exact same as they do when sending the
# JWT in via a header instead of a cookie
#app.route('/api/example', methods=['GET'])
#jwt_required
def protected():
username = get_jwt_identity()
return jsonify({'hello': 'from {}'.format(username)}), 200
if __name__ == '__main__':
app.run(debug=True)
But in my office I have similar setup except I am not calling
username = get_jwt_identity()
I get NoAuthorization exception get raised.
how does this work ...
It's mean you not login and flask-jwt can't find any token on your cookies.
Do you login before call this resource?
check your cookie that returned from app.
In my case it was CORS error, I was using a different api address from the website

How to use gspread with python-social-auth

I'm trying to use gspread with python-social-auth, I followed the sample describe on documentation and I've created this class to use as credential store:
class Credentials(object):
def __init__ (self, access_token=None, scope=None):
self.access_token = access_token
self.scope=scope
def refresh (self, http):
# get new access_token
# this only gets called if access_token is None
pass
And at my code I've used:
import gspread
credentials = Credentials(access_token=user.social_auth.get_access_token(),
scope=['https://spreadsheets.google.com/feeds'])
sh = gspread.authorize(credentials)
And asking any response from API using:
sh.get_spreadsheets_feed()
This error appears on the console:
*** RequestError: (401, '401: <HTML>\n<HEAD>\n<TITLE>Token invalid - AuthSub token has wrong scope</TITLE>\n</HEAD>\n<BODY BGCOLOR="#FFFFFF" TEXT="#000000">\n<H1>Token invalid - AuthSub token has wrong scope</H1>\n<H2>Error 401</H2>\n</BODY>\n</HTML>\n')
I have defined the scope at my settings and this is working well, for example trying to get contacts from Google Contacts
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
'https://www.googleapis.com/auth/contacts.readonly',
'https://www.googleapis.com/auth/spreadsheets.readonly'
]
Any ideas?
You have to add these scopes also to Python Social Auth settings:
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
...
'https://www.googleapis.com/auth/spreadsheets.readonly',
'https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive.file'
]