Add aws cognito token to OAuth2PasswordBearer Fast Api - amazon-web-services

I have implemented sign up and login successfully with amazon cognito and fast api, but now I want to secure other endpoints with returned token of aws.
these are functions implemented so far:
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login", auto_error=False)
async def get_current_user(*, db: Session = Depends(deps.get_db), token: str = Depends(optional_oauth2_scheme)):
try:
payload = jwt.decode(token, options={"verify_signature": False})
user = UserController.get_user_by_email(db, payload.get('username'))
except:
raise HTTPException(status_code=401, detail='Invalid username or password')
return user
def login(self, username, password, db: Session):
client = boto3.client('cognito-idp',region_name=os.getenv('COGNITO_REGION_NAME'))
try:
response = client.initiate_auth(
ClientId=os.getenv('COGNITO_USER_CLIENT_ID'),
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': username,
'PASSWORD': password
}
)
user = controllers.user.get_by_email(db, username)
if user:
user.access_token = response['AuthenticationResult']['AccessToken']
db.flush()
db.commit()
user = controllers.user.get_by_email(db, username)
return {"detail": {'data': user}}
else:
raise HTTPException(status_code=404, detail="That User doesn't exist")
now I want to use this function to logout endpoint and to have token to request headers.
Any help is appreciated

Related

Django: invalid token for password reset after account creation

within an application, a user with an administrator role, through a DRF endpoint, is able to create new user accounts.
The need is to automatically send the password reset link to the emails of the newly created users.
I have defined an url:
path('v1/account/register/',
AccountCreationView.as_view(),
name='custom_account_creation'),
the view that first of all check that user role allow the creations of new users:
class AccountCreationView(RegisterView):
"""
Accounts Creation
"""
serializer_class = RegisterWithMailSendSerializer
def get_response_data(self, user):
# print('get_response_data', user)
self.user = user
def create(self, request, *args, **kwargs):
role_section = 'UsersAdmins'
#
rights_check = role_rights_check(
request.user,
role_section,
"R",
)
if rights_check[0] == False:
return Response({"error": rights_check[1]},
status=status.HTTP_401_UNAUTHORIZED)
response = super().create(request, *args, **kwargs)
and a custom serializer for that views, where after validating data, save and then create the password reset link and send via email to the newly created user:
class RegisterWithMailSendSerializer(RegisterSerializer):
def save(self, request, **kwargs):
adapter = get_adapter()
user = adapter.new_user(request)
self.cleaned_data = self.get_cleaned_data()
user = adapter.save_user(request, user, self, commit=False)
if "password1" in self.cleaned_data:
try:
adapter.clean_password(self.cleaned_data['password1'],
user=user)
except DjangoValidationError as exc:
raise serializers.ValidationError(
detail=serializers.as_serializer_error(exc))
user.save()
self.custom_signup(request, user)
setup_user_email(request, user, [])
pg = PasswordResetTokenGenerator()
pg_token = pg.make_token(user)
print('>>> pg_token', pg_token)
frontend_site = settings.FRONTEND_APP_BASE_URL
token_generator = kwargs.get('token_generator',
default_token_generator)
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
email = self.get_cleaned_data()['email']
get_adapter(request).send_mail('password_reset_key', email, context)
return user
in settings.py
CSRF_COOKIE_SECURE isn't set and has it's default False value.
everything seems to work, the user is created and the link with uid and token is sent to the relative email BUT the token seem is invalid when the user tries to reset his password...
Printed 'pg_token' is the same founded into the sended URL.
For completeness here the custom serializer used to reset the password:
in settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'api.serializers.serializers_auth.CustomPasswordResetSerializer',
'TOKEN_SERIALIZER': 'api.serializers.serializers_auth.TokenSerializer',
}
serializers_auth.py
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def save(self, request, **kwargs):
frontend_site = settings.FRONTEND_APP_BASE_URL
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator',
default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
full_url = frontend_site + path
context = {
'current_site': frontend_site,
'user': user,
'password_reset_url': full_url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail('password_reset_key', email,
context)
return self.cleaned_data['email']
class CustomPasswordResetSerializer(PasswordResetSerializer):
#property
def password_reset_form_class(self):
return CustomAllAuthPasswordResetForm
I tried everything, including the same calls for creation and reset through Postman thinking that, for some reason, the token was invalidated by the automatic login in the DRF web interface after the user was created but I don't understand why the token is not valid.
If i try manually POST email address on /api/v1/auth/password/reset/ and then use provided uid/token on /api/v1/auth/password/reset/confirm/ the password reset works as expected.
Some experience and tips are really appreciated.
you could easily implement full user authentication with Django Djoser
Check the docs: https://djoser.readthedocs.io/en/latest/getting_started.html
Available endpoints
/users/
/users/me/
/users/confirm/
/users/resend_activation/
/users/set_password/
/users/reset_password/
/users/reset_password_confirm/
/users/set_username/
/users/reset_username/
/users/reset_username_confirm/
/token/login/ (Token Based Authentication)
/token/logout/ (Token Based Authentication)
/jwt/create/ (JSON Web Token Authentication)
/jwt/refresh/ (JSON Web Token Authentication)
/jwt/verify/ (JSON Web Token Authentication)
Solved by calling password reset endpoint with email parameter immediately after the user is created, without any custom logic or overrides:
from rest_framework.test import APIClient
if settings.SEND_EMAIL_PWD_CHANGE_TO_NEW_USERS == True:
client = APIClient()
client.post('/api/v1/auth/password/reset/', {'email': user.email}, format='json')
And now the email with the reset link contain a valid token for the password reset.

django rest frame work API Test case (Authentication needed)

I am using (simple JWT rest framework) as a default AUTHENTICATION CLASSES
Now I want to write an API test case for one of my view which needed authentication
I don't know how to add "access" token and how to use this in rest framework test cases
I will be thankful if you answer to my question
You can do this using a rest_framework.APITestCase.
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
Before that you need an access token which you can get from the API you are using to obtain a JWT access token. This is what I did in making test cases:
class BaseAPITestCase(APITestCase):
def get_token(self, email=None, password=None, access=True):
email = self.email if (email is None) else email
password = self.password if (password is None) else password
url = reverse("token_create") # path/url where of API where you get the access token
resp = self.client.post(
url, {"email": email, "password": password}, format="json"
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertTrue("access" in resp.data)
self.assertTrue("refresh" in resp.data)
token = resp.data["access"] if access else resp.data["refresh"]
return token
def api_authentication(self, token=None):
token = self.token if (token is None) else token
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)

Django - Transforming Form data to REST API, POST request

Given an arbitrary html form, what is the fastest and smoothest way of transforming the entered data to a REST API JSON POST request to an arbitrary address?
Are there any good libraries for this in Django?
Thanks
The simplest would be to use requests.
Code example for login would be:
import requests
def user_login(request):
# If token was already acquired, redirect to home page
if request.session.get('api_token', False):
return HttpResponseRedirect(reverse('index'))
# Get username and password from posted data, authenticate and
# if successful save api token to session
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
r = requests.post('http://localhost/api-token-auth/', data={'username': username, 'password': password})
if r.status_code == 200:
response = r.json()
token = response['token']
# Save token to session
request.session['api_token'] = token
else:
messages.error(request, 'Authentication failed')
return HttpResponseRedirect(reverse('login'))
else:
return render(request, 'login.html', {})
That's just a simple example. The key is this part:
r = requests.post('http://localhost/api-token-auth/', data={'username': username, 'password': password})

Generating an access token programatically with Django OAuth2 Toolkit

I'm using Python Social Auth and Django OAuth Toolkit to manage my user accounts and restrict access to my REST API.
I can create a token for users that sign up manually with my app by using the regular
curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/
But when I register my users with PSA by their access token, I'd like to create a OAuth2 Toolkit token for my own app and return it as JSON to the client so it can use it for making requests with my API.
Presently, I generate token simply using generate_token from oauthlib, is that good practice? Should I take into consideration other factors?
from oauthlib.common import generate_token
...
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
login(request, user)
app = Application.objects.get(name="myapp")
# We delete the old one
try:
old = AccessToken.objects.get(user=user, application=app)
except:
pass
else:
old.delete()
# We create a new one
tok = generate_token()
AccessToken.objects.get_or_create(user=user,
application=app,
expires=now() + timedelta(days=365),
token=tok)
return "OK" # I will eventually return JSON with the token
else:
return "ERROR"
I've recently used https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 for this purpose like user Felix D. suggested. Below is my implementation:
class TokenHandler:
application = Application.objects.filter(name=APPLICATION_NAME).values('client_id', 'client_secret')
def handle_token(self, request):
"""
Gets the latest token (to access my API) and if it's expired, check to see if the social token has expired.
If the social token has expired, then the user must log back in to access the API. If it hasn't expired,
(my) token is refreshed.
"""
try:
token_list = AccessToken.objects.filter(user=request.user)\
.order_by('-id').values('token', 'expires')
if token_list[0]['expires'] < datetime.now(timezone.utc):
if not self.social_token_is_expired(request):
token = self.refresh_token(request)
else:
token = 'no_valid_token'
else:
token = token_list[0]['token']
except IndexError: # happens where there are no old tokens to check
token = self.convert_social_token(request)
except TypeError: # happens when an anonymous user attempts to get a token for the API
token = 'no_valid_token'
return token
def convert_social_token(self, request):
grant_type = 'convert_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
user_social_auth = request.user.social_auth.filter(user=request.user).values('provider', 'extra_data')[0]
backend = user_social_auth['provider']
token = user_social_auth['extra_data']['access_token']
url = get_base_url(request) + reverse('convert_token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'backend': backend,
'token': token}
if backend == 'azuread-oauth2':
fields['id_token'] = user_social_auth['extra_data']['id_token']
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except IndexError:
return {'error': 'You must use an OAuth account to access the API.'}
except UserSocialAuth.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
def refresh_token(self, request):
grant_type = 'refresh_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
refresh_token_object = RefreshToken.objects.filter(user=request.user).order_by('-id').values('token')[0]
token = refresh_token_object['token']
url = get_base_url(request) + reverse('token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'refresh_token': token}
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except RefreshToken.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
#staticmethod
def social_token_is_expired(request):
user_social_auth = UserSocialAuth.objects.filter(user=request.user).values('provider', 'extra_data')[0]
try:
return float(user_social_auth['extra_data']['expires_on']) <= datetime.now().timestamp()
except KeyError: # social API did not provide an expiration
return True # if our token is expired and social API did not provide a time, we do consider them expired

django and angularJS permission_required anonymousUser

i'm writing a django / angularjs application, and i'm trying to use the #permission_required for user permission authorization.
I'm returning from the client side both headers- sessionId and csrf token, and yet the #permission_required method user, is anonymousUser, although when i'm logging in the user, i use - login(request, user) method, and the user arg is updated to the current user:
#api_view(['GET', 'POST', 'DELETE'])
def log_in_view(request):
body = request.body
json_body = json.loads(body)
email = json_body.get("email")
password = json_body.get('password')
user = authenticate(email=email, password=password)
session = request.session
if user is not None:
request.session['email'] = email
request.session['password'] = password
session.set_expiry(900)
session.save()
session_key = session.session_key
login(request, user)
crcf = get_token(request)
response_body = {}
response_body.update({"session_key" : session_key})
response_body.update({"csrf" : crcf})
return HttpResponse(json.dumps(response_body), content_type="text/json", status=200)
else:
return HttpResponse("could not authenticate user", content_type="text/plain", status=401)
does anyone have any idea what am i doing wrong?
cheers
you dont need all those stuff to login the user. I mean you dont need to set up session manually, either return csrf token.
#api_view(['GET', 'POST', 'DELETE'])
def log_in_view(request):
body = request.body
json_body = json.loads(body)
email = json_body.get("email")
password = json_body.get('password')
user = authenticate(email=email, password=password)
if user:
login(request, user)
response_body = {}
response_body['user'] = user
return HttpResponse(json.dumps(response_body), content_type="text/json", status=200)
else:
return HttpResponse("could not authenticate user", content_type="text/plain", status=401)
By use of #api_view I'm assuming that you're using Django REST Framework.
Each api view of django rest framework don't rely by default on django authentication. If you want your view to use django authentication, you should add proper #authentication_classes(...) decorator to your view or specify it globally in your settings.py file.