Request authorization do not work as expected - python-3.x

I'm trying to use the following:
auth = request.authorization
if not auth or not metrites.check( auth.username, auth.password ):
return metrites.authenticate()
and within module metrites i have:
def check( username, password ):
# This function is called to check if a username/password combination is valid
return username == 'nikos' and password == '*****'
def authenticate():
# Sends a 401 response that enables basic auth
return Response( 'Credentials of a registered user required!', 401, {'WWW-Authenticate': 'Basic realm="User!"'} )
But every time i type a user/pass combo it failes to accept it properly and is resking me.
Do i have something wrong?

Related

Keycloak API :- Identify Users Realm for login

I am creating a microservice that will proxy keycloak for user creation, reset password, login etc. I don't want to expose any keycloak page like re-set password or login page so I am using the keycloak API and everything is fine so far.
The only issue is for login, where I need to know the realm to get the token as the API to get the token is realm specific.
realms/{REALM_NAME}/protocol/openid-connect/token
So is there a way to get a list of all the users from all the realms by admin user?
or any other way to find it?
You can get the realm information by decode user's access token.
The "iss" (issuer) claim identifies the principal that issued the JWT.
This is decode example by JWT.io
I demo make two realms (realm1 and realm2)
each realm add single user (both user same username: user and password: 1234)
And call get access token and decode it by Python
import requests
import ast
import jwt
def get_issuer(realm, user_name, password):
url = 'http://localhost:8180/auth/realms/'+realm+'/protocol/openid-connect/token'
body = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : user_name,
'password': password
}
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
response = requests.post(url, data=body, headers=headers).content.decode('utf-8')
token = ast.literal_eval(response)['access_token']
# print(token)
decoded = jwt.decode(token, options={"verify_signature": False})
# print(decoded)
return decoded['iss']
print('realm1 with user -->', get_issuer('realm1','user','1234'))
print('realm2 with user -->', get_issuer('realm2','user','1234'))
get this output
$python get_realm.py
realm1 with user --> http://localhost:8180/auth/realms/realm1
realm2 with user --> http://localhost:8180/auth/realms/realm2
If you want to get all users of realm,
you can get this API with master realm's admin token
GET /{realm}/users
After talking with No_One, I realize he want to get a relam name by username.
I made 600 realms and 3 users for each realm. So total user 1.8K and each user unique username. (If may same username, we can extend username and e-mail). I made python program to create relams and users.
So I demo search realm by username with for loop.
Check username exist for every realm
For loop all of relams
{keycloak_URL}/auth/admin/realms/{realm_name}/users/?username={user_name}
if you want to get list of realms,
{keycloak-url}/auth/admin/realms
The realm name format is realm_0xxx
example) realm_0001, realm_0002, ..., realm_0600
each ream has three users
example) In realm_0001,
user01_in_realm0001,
user02_in_realm0001,
user03_in_realm0001
In realm_0002,
user01_in_realm0002,
user02_in_realm0002,
user03_in_realm0002
...
In realm_0600,
user01_in_realm0600,
user02_in_realm0600,
user03_in_realm0600
This Python code search user by for loop
import admin
import random
admin = admin.Admin()
token = admin.get_master_token()
random_realm_num = random.randint(1, 600)
random_user_num = random.randint(1, 3)
realm_name = "realm_{:04d}".format(random_realm_num)
user_name = "user{:02d}_in_realm{:04d}".format(random_user_num, random_realm_num)
print('random realm_name:', realm_name)
print('random user_name:', user_name)
found = False
for realm_index in range(1,600,1):
realm_name = "realm_{:04d}".format(realm_index)
if(admin.is_user_exist(token, realm_name, user_name)):
print('user_name:', user_name,' belong to',realm_name)
found = True
break
if (not found):
print('user_name:', user_name,'is not belong to any realms')
This admin class
from urllib import response
from urllib.error import HTTPError
import requests
import ast
import json
class Admin:
# Keycloak master realm URL
url = 'http://localhost:8180/auth/realms/master/protocol/openid-connect/token'
# Keycloak master credential
params = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : 'admin',
'password': 'admin'
}
def get_master_token(self):
try:
response = requests.post(self.url, self.params, verify=False).content.decode('utf-8')
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
print('Keycloak container is not running, Please check your docker container!')
raise SystemExit
else:
return ast.literal_eval(response)['access_token']
def is_user_exist(self, token, realm_name, user_name):
url ='http://localhost:8180/auth/admin/realms/'+realm_name+'/users/?username='+user_name.replace(" ", "%20")
headers = {
'content-type': 'application/json',
'Authorization' : 'Bearer '+ str(token)
}
try:
response = requests.get(url, headers=headers)
# print (response)
# print (response.content)
# If the response was successful, no Exception will be raised
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
else:
# print('Success!')
# print(response.text)
if len(response.content) == 2: # []
return False
if (json.loads(response.text)[0]['username'] == user_name.lower()):
return True
else:
return False
Result
random realm_name: realm_0430
random user_name: user03_in_realm0430
user_name: user03_in_realm0430 belong to realm_0430
[Done] exited with code=0 in 21.248 seconds
We also encountered such problem but we finally implemented by this way:
create a new SPI and provide a new rest endpoint like '/realm-list'
it will return a list of realms that doesn't require admin privilege to access
provide a page to list and choose your realm and than click a button
forward current page to login page(the realm will reprenset in url path)
one thing needs to note, the backend of keycloak needs to check if user is logged in, we add a new cookie value to mark whether the user is logged in.

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.

Error while creating middleware in Django to authenticate a user from different microservice

Building Microservices With Django and pyJWT
Let me explain this in summarised manor, maybe someone can help me out been at this for too long
I have three microservices i.e.
Authentication : Which creates the User and makes the token for a user using RS256 algorithm
Student : which is basically a user type that needs to be verified using JWT
Investor : which is also a user type that needs to be verified using JWT
[I have tried to make a generic create token function in authentication service something like]
data = {"id": user.uuid.hex, "type": user.user_type}
encoded = jwt.encode(data, private_key, algorithm="RS256")
return encoded
It is generating a correct token i have verified using JWT.io
In my student service i have created a middleware something like this
class JWTAuthenticationMiddleware(object):
#Simple JWT token based authentication.
#Clients should authenticate by passing the token key in the "Authorization"
#HTTP header, prepended with the string "Bearer ". For example:
# Authorization: Bearer <UNIQUE_TOKEN>
keyword = "Bearer"
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
auth = request.META.get("AUTHORIZATION", None)
splitted = auth.split() if auth else []
if len(splitted) == 2 and splitted[0] == self.keyword:
if not splitted or splitted[0].lower() != self.keyword.lower().encode():
return None
if len(splitted) == 0:
msg = _("Invalid token header. No credentials provided.")
raise exceptions.AuthenticationFailed(msg)
elif len(splitted) > 2:
msg = _("Invalid token header. Token string should not contain spaces.")
raise exceptions.AuthenticationFailed(msg)
user = get_user(request)
decoded = verify_token(auth)
try:
student = Student.objects.get(user_id=decoded.get("id"))
except Student.DoesNotExist:
student = Student.objects.update(user_id=decoded.get("id"))
response = self.get_response(request)
return response
My verify_token function looks like
def verify_token(token):
decoded = jwt.decode(token, JWT_AUTH["PUBLIC_KEY"], algorithms=["RS256"])
return decoded
I have also added my middleware in my settings just below authentication middleware
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"config.middleware.authentication.JWTAuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.common.BrokenLinkEmailsMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
I am just not able to decode my token and assign the payload to respective user type, Can someone help me out where i am going wrong? I have also tried using Authentication backend something like this, it doesn't works, have implemented simple_jwt_djangorestframework package but it doesn't decode my payload on student service and says invalid token so i don't want to add it for increasing unnecessary code too.
My viewset looks like this
class StudentViewset(viewsets.ViewSet):
queryset = Student.objects.filter(is_active=True)
serializer_class = StudentSerializer
lookup_field = "uuid"
permission_classes = [IsAuthenticated]
My error is always saying when i am using isAuthenticated as permission class
"message": "Authentication credentials were not provided.",
Maybe validating raw token should work :
decoded = verify_token(splitted[1])
Actually you don't have to implement it. There is simple jwt package for it. Install the package by its documentation and if it wouldn't work provide errors to help you.
To answer this question myself for future references
class JWTAuthenticationMiddleware(object):
#Simple JWT token based authentication.
#Clients should authenticate by passing the token key in the "Authorization"
#HTTP header, prepended with the string "Bearer ". For example:
# Authorization: Bearer <UNIQUE_TOKEN>
keyword = "Bearer"
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
auth = request.META.get("AUTHORIZATION", None)
splitted = auth.split() if auth else []
if len(splitted) == 2 and splitted[0] == self.keyword:
if not splitted or splitted[0].lower() != self.keyword.lower().encode():
return None
if len(splitted) == 0:
msg = _("Invalid token header. No credentials provided.")
return JsonResponse(data=msg, status=403, safe=False)
elif len(splitted) > 2:
msg = _("Invalid token header. Token string should not contain spaces.")
return JsonResponse(data=msg, status=403, safe=False)
user = get_user(request)
decoded = verify_token(auth)
try:
student = Student.objects.get(user_id=decoded.get("id"))
return self.get_response(request)
except Student.DoesNotExist:
msg = _("Invalid User Not found")
return JsonResponse(data=msg, status=403, safe=False)
couple of things that i was doing wrong was not passing a response when i found a student that's why middleware was not bypassed at that time, also exceptions cannot be used at middleware level middleware only returns HTTPResponses, thus i thought of using JsonResponse.

How to use Cognito for AppSync mutation call (Python)

I'd like to call mutations from AppSync using my Python function but use a Cognito user for the authorization as "API-KEY", "IAM" and other methods are not suitable for my application.
My mutation looks like this (test purposes):
mutation XYZ {
updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {
id
name
}
}
I am assuming that the user is already created and enabled by some means. If your AppSync API is secured only using Cognito, you are always going to need a username and a password to begin with. For example, you can use below code to login and get the AccessToken from the response:
import boto3
def get_user_auth(event, context):
client = boto3.client('cognito-idp')
response = client.initiate_auth(
UserPoolId='xxxxxxxxx',
ClientId='xxxxxxxxxxxxxx',
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': 'xxxxxx',
'PASSWORD': 'xxxxxx'
}
)
return response
Note: Make sure that you have "Enable username password based authentication (ALLOW_USER_PASSWORD_AUTH)" enabled.
Once you have the access token, you can use this in HTTP headers within your request as follows:
{
"authorization": "<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>"
}
For example:
import requests
from requests_aws4auth import AWS4Auth
import boto3
session = requests.Session()
APPSYNC_API_ENDPOINT_URL = '<YOUR-API-URL>'
mutation = """mutation XYZ {updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {id, name}}"""
response = session.request(
url=APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'authorization': '<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>'},
json={'mutation': mutation}
)
print(response.json()['data'])
Since this access token has some expiration, you might also need to refresh this token by using the RefreshToken from the above response. Like so:
def refresh_token(self, username, refresh_token):
try:
return client.initiate_auth(
ClientId=self.client_id,
AuthFlow='REFRESH_TOKEN_AUTH',
AuthParameters={
'REFRESH_TOKEN': refresh_token,
# 'SECRET_HASH': self.get_secret_hash(username)
# If the User Pool has been defined with App Client secret,
# you will have to generate secret hash as well.
}
)
except botocore.exceptions.ClientError as e:
return e.response
Example of how you can generate secret hash.

cannot create flask login test function

I have a login function with user and pass parameters that returns a token. I'am using flask.
Now i need a test case to test my function, i am lost here.
Neve done testing and i can't come up with the solution.
I need a test case that verifies a token was created when the login is made.
Any help?
def login():
user = request.form['user']
passwd = request.form['passwd']
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
response = jsonify(message='Invalid Credentials')
return response ,401
access_token = create_access_token(identity=user)
return jsonify(access_token=access_token), 200```
After 2 days i finally manage to come up with the solution.
I checked if access_token was in the response.data
def test_token_sent(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data =dict(user="hermes", passwd ="hermes"),
follow_redirects=True)
self.assertIn(b'access_token', response.data)```

Resources