How to use a jwt from a different issuer? - python

I am building an flask API that will manipulate data to be used in thingsboard.
Long story short, client logs in via thingsboard, get a jwt from that.
But the API rejects the jwt saying that signature verification failed.
Same secret key is being used on both sides.
Both thingsboard is on a remote server, API is currently local during development.
New to jwt manipulation, what am I missing?

Make sure that JWT_AUTH_USERNAME_KEY fit with the username that the library uses to get the user.
For example, when you get a Thingsboard JWT the username is in the sub key, and many libraries use username by default.
Maybe this piece of code can help you (implementation with JWT Rest Framework:
class CustomAuth(JSONWebTokenAuthentication):
def authenticate_credentials(self, payload):
"""
Returns an active user that matches the payload's user id and email.
"""
User = get_user_model()
# Thingsboard payload
if payload.get('sub') is not None:
username = payload.get('sub')
else:
# Our payload
username = payload.get('username')
if not username:
msg = _('Invalid payload.')
raise exceptions.AuthenticationFailed(msg)
try:
user = User.objects.get_by_natural_key(username)
except User.DoesNotExist:
msg = _('Invalid signature.')
raise exceptions.AuthenticationFailed(msg)
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.AuthenticationFailed(msg)
return user

Version 3.21.0 of flask-jwt-extended adds a JWT_DECODE_ISSUER option which should solve this for you: https://github.com/vimalloc/flask-jwt-extended/releases/tag/3.21.0

Related

Fastapi How to restrict token creation to defined users

I have a dictionary of users with (username, password) as key, value pair.
i would like to restrict the authorisation creation to only users in my dictionary.
So any other user who is not in the dictionary shouldn't be able to create a token.
I try this but it's not working, I can still create token to a new user.
#api.post("/token")
async def get_token(form_data: OAuth2PasswordRequestForm = Depends()):
if not authenticate_user(username=form_data.username,
password=form_data.password):
raise HTTPException(status_code=403, detail='Authentication failed')
else:
return {
"access_token": form_data.username,
"token_type": "bearer"
}
raise HTTPException(status_code=403,
detail='Authentication failed')
You never compare anything against form_data.username - the only thing you do is that you start looping over the user, and you check whether the first users password match - well, the user's password. This will always be true.
Instead, retrieve the user you're looking for and compare the password if present:
#api.post("/token")
async def get_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = users.get(form_data.username)
if user and authenticate_user(form_data=username, password=form_data.password):
return {
"access_token": form_data.username,
"token_type": "bearer"
}
raise HTTPException(status_code=403,
detail='Authentication failed')
I'd also like to point out that you're using the users username as the token. This means that anyone can fake a token by simply supplying the username they want to be authenticated as.
Instead, use secrets.token_urlsafe() to generate a token that can be used as an authentication secret. You'll need to store these tokens somewhere, but in this example it seems like you're storing everything in the currently running Python application for users, so you can do the same for tokens. Define valid_tokens = {} somewhere when initializing your application, then insert rows into this dict to assign a username.
token = secrets.token_urlsafe(32)
valid_tokens[token] = form_data.user_name
return {
"access_token": token,
"token_type": "bearer"
}
You can then verify this token in a separate function and use it to look up the username of the logged in user from the token.

Logged User Fixture Does Not Work (FastApi with Pytest)

I am making an authenticated client that is a pytest fixture of my fastapi application,the code below shows an authenticated user,that should be able for example to create posts,update and delete them.But when I use it in my tests,it shows an 401 response error (that is returned when user is not logged)
#pytest.fixture()
def authenticated_user(client):
user_data = {
'email': 'julioooo#gmail.com',
'password': '123',
}
res = client.post('/users/create', json={
**user_data,
'name': 'julin',
'job': 'farmer',
})
user_id = res.json() # this returns the user id
# to send a request to api to confirm user creation
user_id = user_id['id']
verify_user = client.post(f'/users/confirm-user/{user_id}').json()
print(verify_user) #print the confirmation message (debugging process)
log_user_res = client.post('/auth/login', json={
**user_data
}) # this returns in the header a httponly cookie...
# response.set_cookie() method
client.headers={**log_user_res.headers}
return client
After some time trying to debug this fixture I thought,well,the headers actually DO HAVE the httponly cookie in it,so the problem is in another part of the code,maybe in the part that checks if the user is logged or not
def get_current_user(
request: Request,
db=Depends(connect)
):
user_id = request.cookies.get('Authorization')
user = User.objects(id=user_id).first()
if not user:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Could not validate credentials or Not Logged or User Does Not Exist",
headers={"WWW-Authenticate": "Bearer"},
)
raise credentials_exception
return user
And I couldn't find the problem,then I realized that this dependency of connect function actually returns a connection from the production db, not from the testing one,soo I created a new one that is :
def test_get_current_user(request: Request, db=Depends(test_connect)):
load_dotenv()
test_connection_url = os.getenv('test_connection_url')
user_id = request.cookies.get('Authorization')
client = MongoClient(test_connection_url)
users = client['TestJC']['users']
user = users.find_one({"_id": user_id})
if not user:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Could not validate credentials or Not Logged or User Does Not Exist",
headers={"WWW-Authenticate": "Bearer"},
)
raise credentials_exception
return user
PS: In the original function I use mongoengine to be more practical,in this one I use pymongo actually because the test_connect function that returns a pymongo connection instance,is used for tests,because it was the only way I found out to fix a bug that I had in my code that every time I deleted the test db after ending up the tests,it also deleted the production db (which is not cool), because Users.objects().delete() did not work and users.drop() did,I am using this way,but returning to the point....
PS2: If you have seen this question before I updated it,sorry for the bad question,I was very sleepy XD
I even overrided the get_current_user dependency to this test one trying to fix the problem
I even created a "headers version" of this authenticated user fixture where I only returned client.headers and in the request just put in the post method headers={**authenticated_headers}
BUT NOTHING SEEMS TO WORK,every single time is just 401..401...AND MORE 401
Thank you guys for your time here I appreciate A LOT :)

Accessing Username and Password in django request header returns None

I'm creating a view which is expected to be accessed by a bot passing a username and password in the header. (It's a google feed bot to be specific). However, I can never seem to access the username and password to authenticate the bot's credentials. request.GET.get('username') and request.GET.get('password') both return None since both request.GET and request.POST return empty QueryDicts. I am using Postman with basic authentication to test my requests.
Here is my code from views.py:
def authenticate_bot(request):
username = request.GET.get('username')
password = request.GET.get('password')
feed_bot = authenticate(username=username, password=password)
if feed_bot is not None:
# Confirmed as user credentials.
login(request, feed_bot)
How do I retrieve the username and password from my basic authentication header?
Thank you nthall for pointing me in the right direction - finding the request.META dictionary was key.
Since I couldn't find much in the way of resources which explained the process, I'll post the entire Django process for retrieving and authenticating data from the Authorization header here.
import base64
from django.contrib.auth import authenticate
def header_auth_view(request):
auth_header = request.META['HTTP_AUTHORIZATION']
encoded_credentials = auth_header.split(' ')[1] # Removes "Basic " to isolate credentials
decoded_credentials = base64.b64decode(encoded_credentials).decode("utf-8").split(':')
username = decoded_credentials[0]
password = decoded_credentials[1]
feed_bot = authenticate(username=username, password=password)
# if the credentials are correct, then the feed_bot is not None, but is a User object.
Django capitalizes and affixes the 'HTTP_' prefix to any header passed in the request, and as nthall correctly pointed out, it can be accessed via request.META.
I isolate the base64 encoded information, which is in the format 'Basic username:password' by splitting the header over the space so it's just 'username:password'. Then I decode using base64 and then decode the result to convert the byte-like string to a utf-8 string. Then it's just a matter of isolating the username and password. Then go through the process of authentication.
Neither request.GET nor request.POST refers to request headers. The data you're looking for is most likely available in the dictionary at request.META -- more details on that in the HttpRequest docs. Not sure about the details of your setup but it sounds like you'd be looking for request.META['username'] and request.META['password']

OAUTH2gLogging user out of their session Django

I am using the OAUTH API to verify access to the users google calendar. They are not logging into my system using OAUTH, just accepting my site accessing their data.
The problem is, if the user is logged out of their google account and after they hit verify, it force logs them out of their session and I have no way of linking them back up.
This DOES work if they are already logged into the google account in their browser session and hit accept, they will be redirected to the right page.
I replicate the error when the cache, cookies are clear and they need to relogin into their google account to verify.
I've tried storing the session ID, etc, but the request parameter is not containing the same request data as the initial view, so there is a conflict in the data I am trying to retrieve.
The user is logged in using the standard Django libraries for the credentials model.
CODE
FLOW = flow_from_clientsecrets(
CLIENT_SECRETS,
scope='https://www.googleapis.com/auth/calendar.readonly',
redirect_uri='http://127.0.0.1:8000/oauth2callback')
'''''''''''''''''''''''''''''''''''''''''''''''''''
Main function dealing with auth verification
'''''''''''''''''''''''''''''''''''''''''''''''''''
def index(request):
current_user = User.objects.get(username=request.user.username)
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
credential = storage.get()
if credential is None or credential.invalid == True:
FLOW.params['state'] = xsrfutil.generate_token(settings.SECRET_KEY,
request.user.id)
authorize_url = FLOW.step1_get_authorize_url()
return redirect(authorize_url)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''
User then calls the data function once authenticated
''''''''''''''''''''''''''''''''''''''
def auth_return(request):
print("THE CURRENTLY REQUESTED USER IN THIS SESSION REQUEST IS %s"%(request.user.username))
credential = FLOW.step2_exchange(request.REQUEST)
try:
current_user = User.objects.get(id=request.user.id)
except:
return HttpResponseRedirect("/login")
storage = Storage(CredentialsModel, 'id', current_user, 'credential')
storage.put(credential)
return HttpResponseRedirect("/get_cal")
Ok, so this was a bit more involved than I thought.
I fixed this by adding a state parameter of the currently logged in username.
logged_in_username = request.user.username
user_data = {'var_test' : logged_in_username}
pass_param = urllib.urlencode(user_data)
FLOW.params['state']=pass_param
authorize_url = FLOW.step1_get_authorize_url()
This gave me the ability to query the user from the DB via the User model in Django contrib. I parsed out the state var from the URL:
#Get the currently logged in username
user_variable_data = str(FLOW.params['state'])
#get rid of the var_test= preprended text data for parsing reasons
user_variable_data = user_variable_data[9:]
#Get that user from the database in the form of a user object
current_user = User.objects.get(username=user_variable_data)
and then built a custom backend authentication file to auth the user without a password to maintain the request like nothing weird ever even happened.
user = authenticate(username=user_variable_data)
login(request, user)
print("AUTHENTICATED")
Appended this to settings file
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'procrastinate.auth_backend.PasswordlessAuthBackend',
)
Custom Backend File
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
class PasswordlessAuthBackend(ModelBackend):
"""Log in to Django without providing a password.
"""
def authenticate(self, username=None):
try:
return User.objects.get(username=username)
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None

Using python social auth to register users by access token

I found this snippet of code that helps me authenticate a user and then create a rest_framework token for them. The client I am using is a native android app and I will get the access token from the client side and post it to django in the ObtainAuth class.
Here is the code for the server side.
#psa('social:complete')
def register_by_access_token(request, backend):
backend = request.strategy.backend
# Split by spaces and get the array
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
msg = 'No token header provided.'
return msg
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
return msg
access_token = auth[1]
user = backend.do_auth(access_token)
return user
class ObtainAuthToken(APIView):
model = Token
serializer_class = AuthTokenSerializer
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
def post(self,request):
serializer = self.serializer_class(data= request.DATA)
if backend == 'auth':
if serializer.is_valid:
token, created = Token.objects.get_or_create(user=serializer.object['user'])
if token:
return Response({'token': token.key})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
user = register_by_access_token(request, backend)
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'email': user.email, 'firstname': user.first_name, 'userRole': 'user', 'token': token.key})
The register_by_access_token method will get the facebook access token and then create a user with the rest_framework.It takes a request and the backend to be used e.g 'facebook'.
If a user logs in with my backend then the backend is 'auth' and it uses the normal process of retrieving the email and password and then giving me a token to use.As detailed here
My question is how do I post the authentication backend be it 'facebook' or 'auth' so that I can receive the token?
What I've tried.
I have tried sending the backend type ('facebook' or 'auth') with the access token but I get an error that the method takes 3 arguments and I've only provided 2.
I've tried making the url take a backend like this:
url(r'^login/(?P<backend>[^/]+)/$',views.ObtainAuthToken.as_view())
then sending the access token to a url like this mysite.com:8000/login/facebook.
None of these work and I don't have much expereience with psa or django to know how to pass this parameter.
How do I send which backend to use so that it can be accepted by the method? If anyone has ever had this use case please help me out.
according to my understanding social login requires a access token , so when you are login with facebook when you call 'mysite.com:8000/login/facebook' it is expecting a access token,
for my project i defined my urls as 'url(r'^login/(?P[^/]+)/$',register_by_access_token,name='register_by_access_token')',with the token i am sending it completes the login, for facebook i send backend as 'facebook' for google i send backend as 'google-oauth2' but both the case i am sending a token given my respective backend, when you are not using a third party backend you wont get the token and the login expects that.
so my suggestion is if you are going with auth use normal login post, not the same URL.

Categories