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
Related
I am trying to access some GitLab resources via Rest API. I am using the Resource owner password credentials flow and I wrote a simple class:
class GitLab:
def __init__(
self,
username: str,
password: str,
app_id: str = config.get("GITLAB_APP_ID"),
app_secret: str = config.get("GITLAB_APP_SECRET"),
) -> NoReturn:
self.app_id = app_id
self.username = username
self.base_url = config.get("GITLAB_BASE_URL")
data = {
"password": password,
"grant_type": "password",
"username": self.username,
}
auth = httpx.BasicAuth(self.app_id, app_secret)
response = httpx.post(f"{self.base_url}/oauth/token", data=data, auth=auth)
print(response.url)
print("response: ", response.json())
But when I run this, I get the following:
https://gitlab.com/api/v4/oauth/token
response: {'error': '404 Not Found'}
I do not understand why this is happening. Please help
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"}
my models
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
print(phone_number, 'phone_number')
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
**extra_fields
)
user.phone_number = phone_number
# user.phone_number = 333333
print(user, 'user')
user.set_password(password)
user.save(using=self._db)
return user
def create_staffuser(self, email, password):
"""
Creates and saves a staff user with the given email and password.
"""
user = self.create_user(
email,
password=password,
)
user.staff = True
user.save(using=self._db)
return user
def create_superuser(self, email, password):
"""
Creates and saves a superuser with the given email and password.
"""
user = self.create_user(
email,
password=password,
)
user.staff = True
user.admin = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
username = models.CharField(max_length=100, default="")
phone_number = models.IntegerField(default=0, verbose_name='phoneNumber')
is_active = models.BooleanField(default=True)
staff = models.BooleanField(default=False) # a admin user; non super-user
admin = models.BooleanField(default=False) # a superuser
slug = models.SlugField(max_length=255, unique=True)
objects = UserManager()
def save(self, *args, **kwargs):
print(self.phone_number, 'before') #this print statement return none
if not self.slug:
self.slug = slugify(utils.rand_slug() + "-" + self.username)
super(User, self).save(*args, **kwargs)
print(self.phone_number, 'after') #this print statement return none
# notice the absence of a "Password field", that is built in.
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [] # Email & Password are required by default.
def get_full_name(self):
# The user is identified by their email address
return self.email
def get_short_name(self):
# The user is identified by their email address
return self.email
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
return self.staff
#property
def is_admin(self):
"Is the user a admin member?"
return self.admin
#property
def owner(self):
return self.user
my serializer
class UserRegisterSerializer(ModelSerializer):
password = CharField(style={'input_type':'password'}, write_only=True)
token = SerializerMethodField(read_only=True)
expires = SerializerMethodField(read_only=True)
message = SerializerMethodField(read_only=True)
status_code = SerializerMethodField(read_only=True)
phone_number = IntegerField()
class Meta:
model =User
fields = [
'email',
'username',
'phone_number',
'token',
'slug',
'expires',
'message',
'status_code',
'password'
]
extra_kwargs = {'password': {'write_only':True}, 'email': {'required':True}}
def validate_phone_number(self, value):
print(value) #This print the actua phone_number serializer value
def get_status_code(self, obj):
data = 200
return data
def get_message(self, obj):
return 'Thank you for registering. Please verify your email before continuing'
def get_token(self, obj):
user = obj
token = get_tokens_for_user(user)
return token
def validate_email(self,value):
qs = User.objects.filter(email__iexact=value)
if qs.exists():
raise ValidationError("User with this email already exists")
return value
def validate_username(self, value):
qs = User.objects.filter(username__iexact=value)
if qs.exists():
raise ValidationError("User with this username already exists")
return value
def create(self, validated_data):
user_obj = User(
username=validated_data.get('username'),
email=validated_data.get('email')
)
user_obj.set_password(validated_data.get('password'))
user_obj.save()
return user_obj
def get_expires(self, obj):
return timezone.now() + timedelta(minutes=5) - datetime.timedelta(seconds=200)
my Views
class RegisterAPIView(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = UserRegisterSerializer
permission_classes = [AnonPermissionOnly]
after sending a post man request to that endpoint this is the response
{
"email": "philipssevarist#gmail.com33w4dd636",
"username": "philipsd34d6364433564w3",
"phone_number": 0,
"token": {
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY1MjI2NDI4NSwiaWF0IjoxNjUyMTc3ODg1LCJqdGkiOiI3ZjBjYjBhZTY5YWE0YzIzYjU4YTc1MWQ3N2M3YWVmZiIsInVzZXJfaWQiOjMzfQ.gLderlN9eMSkjpvaIg6I3eIuiGvo6Xzs_1lhq9hvKQ8",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjUyMTc4MTg1LCJpYXQiOjE2NTIxNzc4ODUsImp0aSI6IjEzNWJiOTNkN2I2YzRlNzlhOTgxM2I4ODA2ODEyNjJjIiwidXNlcl9pZCI6MzN9.US3prDUaNNY9bChNakzRFO8MUam_HIQ_w5UI9_vDIgc"
},
"slug": "vl1yyt-philipsd34d6364433564w3",
"expires": "2022-05-10T10:19:45.407716Z",
"message": "Thank you for registering. Please verify your email before continuing",
"status_code": 200
Note:that phone_number returns 0 which is the default value
fortunately if i manuall add the phone number using my admin dashboard it works but when eve i try using a form or service like postman it doesn'work
i finally discovered what the problem was, so i be posting the answer so anyone who encounters it my find it useful, the problem is i was overiding the create method in my serializer, i passed in the email, password and username but not phone_number to the model create method, though the phone_number field in the forms was getting the value, it wasn't saving it to the database, because i wasn't creating it,
so the proper thing should have been
def create(self, validated_data):
user_obj = User(
username=validated_data.get('username'),
email=validated_data.get('email'),
phone_number = validated_data.get('phone_number')
)
user_obj.set_password(validated_data.get('password'))
user_obj.save()
return user_obj
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.
I have a serializer like following:
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255, write_only=True)
password = serializers.CharField(max_length=255, write_only=True)
def validate(self, validate_data):
username = validate_data.get('username', None)
password = validate_data.get('password', None)
if username is None:
raise serializers.ValidationError({'error': 'Email is required!'})
if password is None:
raise serializers.ValidationError({'error': 'Password is required!'})
user = authenticate(username=username, password=password)
token = Token.objects.get_or_create(user=user)
return {'username': user.username, 'token': 'token'}
and a view for this:
class LoginAPIView(APIView):
def post(self, request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
print(serializer.data) # this prints {}
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response({'error': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
I don't know what is wrong here, do I have to return validate_data?
You have to use create method:
class LoginSerializer(serializers.Serializer):
# required=True to prevent hard-code validation
username = serializers.CharField(max_length=255, write_only=True, required=True)
password = serializers.CharField(max_length=255, write_only=True, required=True)
def create(self, validated_data):
user = authenticate(username=username, password=password)
token = Token.objects.get_or_create(user=user)
return {'username': user.username, 'token': 'token'}
And this will be your post method:
class LoginAPIView(APIView):
def post(self, request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
data = serializer.save() # call save to create token
return Response(data, status=status.HTTP_200_OK)
else:
return Response({'error': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)