I am implementing a custom JSONWebTokenSerializer. It's working fine so far but I need to enable token refresh but I when I do and try to refresh the token, I get the validation error orig_iat field is required. On inspecting the payload returned from jwt_payload_handler, there isn't any orig_iat field attribute.
class CustomJWTSerializer(JSONWebTokenSerializer):
#property
def username_field(self):
return "username_or_email_or_phone"
def validate(self, attrs):
username = attrs.get('username_or_email_or_phone', None)
credentials = {
'username': username,
'password': attrs.get('password')
}
if all(credentials.values()):
user = authenticate(**credentials)
if user:
if not user.is_active:
raise serializers.ValidationError(
'This user has been deactivated.'
)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
raise serializers.ValidationError(
'A user with this credentials was not found.'
)
else:
msg = _('Please provide an (username or email or phone number) and password.')
raise serializers.ValidationError(msg)
Here is are my JWT_AUTH setting:
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'common.utilities.auth.jwt_response_payload_handler',
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
'JWT_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_payload_handler',
'JWT_ALLOW_REFRESH': False,
}
If JWT_ALLOW_REFRESH is True, non-expired tokens can be "refreshed" to obtain a brand new token with renewed expiration time.[JWTDoc]
Sorry, found my bug. Changed 'JWT_ALLOW_REFRESH': False to 'JWT_ALLOW_REFRESH': True,.. It now works
Related
I`m receiving 401 in my JWT requests to the method get user infos from token (), but the tokens doesnt seem to be the same.
I already made the following checks: 1. Checked the token string is exactly the same both in the localStorage and in the create_access_token result. 2. The secret key is the same both in the token creation and in the token decoding 3. The algorithm used for encoding is also the same as the one used for decoding
What else can I do to fix it?
app = Flask(__name__)
app.config.from_object(__name__)
app.config['JWT_SECRET_KEY'] = 'example-key'
#app.route('/login', methods=['POST'])
def login():
# Extract the username and password from the request
user_email = request.get_json(silent=True).get('user_email', None)
user_password = request.get_json(silent=True).get('user_password', None)
# Validate the credentials using the UserController
user = UserController().find_user_by_user_email_and_password(user_email, user_password)
if user is False:
return jsonify({'login': False}), 401
# Calculate the expiration time
exp = datetime.utcnow() + timedelta(days=7)
# Create a JWT token with the user identity and expiration time
access_token = create_access_token(identity=user['user_id'], expires_delta=timedelta(days=7))
print("Token: '" + str(access_token) + "'")
# Return the JWT token in the response
return jsonify({'access_token': access_token}), 200
#app.route('/get_user_infos_from_token', methods=['GET'])
def get_user_infos_from_token():
try:
authorization_header = request.headers.get('Authorization')
token = authorization_header.split(" ")[1]
print("Token: '" + str(token) + "'")
decoded_token = jwt.decode(token, app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
user_id = decoded_token['identity']
user = UserController().find_user_by_user_id(user_id)
if user is None:
return jsonify({'error': 'User not found'}), 404
return jsonify(user), 200
except:
return jsonify({'error': 'Invalid token'}), 401
the method below I'm using to authenticate the user, generate the token and insert in localStorage:
async login(){
let self = this;
axios.post('http://127.0.0.1:5000/login', {
user_email: self.email
, user_password: self.password
})
.then(function (response) {
localStorage.setItem('token', response.data.access_token);
console.log(response.data)
self.login_error = '';
window.alert('Uhu, deu certo! Vou te redirecionar para a página principal.');
self.$router.push({path: '/'});
})
.catch(function (error) {
self.login_error = 'Usuário ou senha incorretos. Tente novamente.';
console.log(error);
});
},
and the method below I am using to get the infos from the user, based on the token that is in the localStorage, that was created during the authentication.
mounted(){
if (localStorage.getItem('token') != null == true){
let self = this;
const token = localStorage.getItem('token').toString();
console.log(token);
axios.get('http://localhost:5000/get_user_infos_from_token', {
headers: { Authorization: `Bearer ${token}` }
}).then(response => {
self.user_data = response.data;
}).catch(error => {
console.error(error);
});
localStorage.setItem('user_data', self.user_data);
self.user_auth_flag = true;
}
},
What do I need to do to make sure the jwt decoder will use the same secret key and algorithm to decode the token generated during the authentication?
..
you did not post your jwt create and validate function. I have a jwt_helper class, you can use it for your own purpose:
PyJWT==1.4.2
import jwt
import os
class JWTHelper:
#staticmethod
def gen_auth_token(identity, email) -> str:
return JWTHelper.encode_auth_token({
"id": identity,
"email": email,
"expired": Helper.get_now_unixtimestamp() + 60 * 60, # expire in 1h,
})
#staticmethod
def encode_auth_token(payload) -> str:
secret_key = os.environ["JWT_SECRET"] # your secret
print(secret_key)
"""
Generates the Auth Token
:return String
"""
try:
return jwt.encode(
payload,
secret_key,
algorithm='HS256'
).decode('utf-8')
except Exception as e:
print(e)
#staticmethod
def decode_auth_token(token):
secret_key = os.environ["JWT_SECRET"]
"""
Decode the auth token
"""
try:
payload = jwt.decode(token, secret_key, algorithms='HS256')
return payload
except Exception as e:
print(e)
raise Unauthorized(message="Please log in again.")
# return None
#staticmethod
def validate_token(token: str):
data = JWTHelper.decode_auth_token(token)
print(data)
if not data:
raise Unauthorized(message='invalid token')
expired = data.get("expired") or None
user_id = data. Get("id") or None
if expired is None or user_id is None:
raise Unauthorized(message='invalid token')
if expired < Helper.get_now_unixtimestamp():
raise Unauthorized(message='token expired')
use your class
token = JWTHelper.gen_auth_token(
identity=1,
email="tuandao864#gmail.com",
role_id=1,
)
validate = JWTHelper.validate_token(auth_token)
I have made a view where I send a Refresh Token to email for activation account purpose. If token is valid everything works fine. The problem is when jwt token expire, I want to be able in backend to extract payload (user_id) from token when jwt.decode throw ExpiredSignatureError and in this way be able to auto resend an Email based on user_id extracted from token.
Here is how I generate the token:
def activation_link(request, user, email):
token = RefreshToken.for_user(user)
curent_site = "localhost:3000"
relative_link="/auth/confirm-email"
link = 'http://' + curent_site + relative_link + "/" + str(token)
html_message = render_to_string('users/email_templates/activate_account.html',{
'activation_link': link,
})
text_content = strip_tags(html_message)
email_subject = 'Activate your account'
from_email = 'notsure#yahoo.com'
to_email = email
#api_view(['POST'])
def ConfirmEmailView(request):
try:
activation_token = request.data['activation_token']
payload = jwt.decode(activation_token,settings.SECRET_KEY, algorithms=['HS256'])
user = User.objects.get(id = payload['user_id'])
if user.is_confirmed:
return Response('Already verified!', status=status.HTTP_200_OK)
user.is_confirmed = True
user.save()
return Response(status=status.HTTP_202_ACCEPTED)
except jwt.ExpiredSignatureError as identifier:
// =>>> Here I want to decode activation_token and extract user_id
return Response("Link- expired!", status=status.HTTP_403_FORBIDDEN)
except Exception as e:
print(e)
return Response(status=status.HTTP_400_BAD_REQUEST)
Well, apparently the solution is easy:
def ConfirmEmailView(request):
try:
activation_token = request.data['activation_token']
payload = jwt.decode(activation_token,settings.SECRET_KEY, algorithms=['HS256'])
user = User.objects.get(id = payload['user_id'])
if user.is_confirmed:
return Response('Already verified!', status=status.HTTP_200_OK)
user.is_confirmed = True
user.save()
return Response(status=status.HTTP_202_ACCEPTED)
except jwt.ExpiredSignatureError as identifier:
# Here we are:
payload = jwt.decode(request.data['activation_token'],settings.SECRET_KEY, algorithms=['HS256'],options={"verify_signature": False})
user_id = payload['user_id'];
return Response({'user_id':user_id, status=status.HTTP_2OO_OK})
By adding options={"verify_signature": False} is posible to decode the token just fine!
When a user registers on my app, an account verification link is sent to his email. When clicking on the link the first time, everything is fine and the account is verified, but when clicking on the same link again, the validation goes through, whereas it should raise an "authentication failed" error, since the "check_token" should return false, right?
Here's the verification serializer:
class VerifyAccountSerializer(serializers.Serializer):
uid = serializers.CharField(min_length=1, write_only=True)
token = serializers.CharField(min_length=1, write_only=True)
class Meta:
fields = ['uid', 'token']
def validate(self, attrs):
uid = attrs.get('uid')
token = attrs.get('token')
uidb64 = force_text(urlsafe_base64_decode(uid))
user = UserAccount.objects.get(pk=uidb64)
if user is None:
raise AuthenticationFailed('Invalid account. Please contant support')
if not PasswordResetTokenGenerator().check_token(user, token):
raise AuthenticationFailed('Account verify link is invalid. Please contant support.')
user.is_guest = False
user.save()
return user
And the view function:
#api_view(['POST'])
def verify_account(request):
if request.method == 'POST':
data = {}
serializer = VerifyAccountSerializer(data=request.data)
if serializer.is_valid():
user = serializer.validated_data
data['user'] = UserSerializer(user).data
data['token'] = AuthToken.objects.create(user)[1]
# delete previous token
tokens = AuthToken.objects.filter(user=user.id)
if len(tokens) > 1:
tokens[0].delete()
return Response(data, status=status.HTTP_200_OK)
data = serializer.errors
return Response(data, status=status.HTTP_400_BAD_REQUEST
It's kinda weird why it's not raising an error, because, in my other serializer for resetting the password via a link as well, I have the exact same thing going on, except there's one more extra field for the password, and if click on that link for the second time, I get a validation error.
I'm using django rest framwork with jwt.
In setting I used RS256 ALGORITHM
I want send token after user authentication, this is my function I'm trying to send token with user_id and is_user data it will produce token but when I pass token in request to server server response:
detail
:
"Error decoding signature."
Why?
here is my http://localhost:8000/login/ server response:
{
"username": "admin",
"email": "",
"token": "b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJpc191c2VyIjp0cnVlfQ.kIz9TBYqVJVgFV5siM3QfNWHxBN28BlZRY_t8TFADmg'",
"is_staff": false
}
login func:
JWT_SECRET = 'secret'
JWT_ALGORITHM = 'HS256'
JWT_EXP_DELTA_SECONDS = 20
def validate(self, data):
user_obj = None
email = data.get('email', None)
username = data.get('username', None)
password = data.get('password')
if not email and not username:
raise ValidationError("email or username is required!")
if '#' in username:
email = username
user = User.objects.filter(
Q(email=email) |
Q(username=username)
).distinct()
# user = user.exclude(email__isnull=True).exclude(email__iexact='')
if user.exists() and user.count() == 1:
user_obj = user.first()
else:
raise ValidationError("this username/email is not valid")
if user_obj:
if not user_obj.check_password(password):
raise ValidationError("password is incorrect")
# payload_handler(user)
# payload = payload_handler(user_obj)
payload = {
'user_id': user_obj.id,
'is_user': True,
}
jwt_token = jwt.encode(payload, JWT_SECRET, JWT_ALGORITHM)
# code = json_response({'token': jwt_token.decode('utf-8')})
data['token'] = jwt_token
return data
jwt setting:
JWT_AUTH = {
'JWT_SECRET_KEY': SECRET_KEY,
'JWT_ALGORITHM': 'RS256',
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=6600),
}
For your "logic func" you use HS256 algorithm and in your "jwt setting" you use RS256. I think it must be a problem.
I am new to django and python development and am naive in my understanding of how to handle exceptions.
I am registering a user through an api call by calling the method register, and would like to push the success status or the error messages while registration.
def register(self,request, **kwargs):
try:
data = self.deserialize(request, request.raw_post_data, format=request.META.get('CONTENT_TYPE', 'application/json'))
email = data['email']
password = data['password']
firstname = data['firstname']
lastname = data['lastname']
newdata = {'email' : email , 'password1': password , 'password2':password, 'firstname':'firstname' , 'lastname':lastname }
registrationform = UserEmailRegistrationForm(newdata)
print registrationform.errors.as_text
print registrationform.cleaned_data
cleaned_data = registrationform.cleaned_data
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
new_user = RegistrationProfile.objects.create_inactive_user(cleaned_data['username'],cleaned_data['email'],cleaned_data['password1'], site)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request,**cleaned_data)
registerUser = collections.OrderedDict()
registerUser['return']='0'
registerUser['code']='0'
registerUser['message']='registered user'
return HttpResponse(registerUser, content_type="application/json")
except Exception, e:
logging.exception(e)
registerUser = collections.OrderedDict()
registerUser['return']='0'
registerUser['code']='0'
registerUser['message']='registered user'
return HttpResponse(registerUser, content_type="application/json")
When I execute this, for example with an already registered email, I get the following in registrationform.errors.as_text
bound method ErrorDict.as_text of {'email': [u'A user with that email already exists.']}>
What would be the right way to code exceptions so that I can pass the success message if the form was validated and user was registered, and the error message if there was a validation error?
Any help is much appreciated!
You might want to have a look in the form's is_valid() method: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.is_valid
For example
if registrationform.is_valid():
//do your stuff
....
register['error'] = False
else:
//return the errors
registerUser['message'] = _('Oops! Please fix the following errors')
register['error'] = True
register['errors'] = registrationform.errors
....