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!
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'm using Django-rest-auth for authentication (https://django-rest-auth.readthedocs.io).
but when I register a new account, the api sent back to me a Token who never change after.
For more security, how can I do to have a new token every time I login ?
If you are working with an API, please structure your code in such a way.
class LoginAPIView(GenericAPIView):
serializer_class = LoginFormSerializer
#csrf_exempt
def post(self, request):
serializer = LoginFormSerializer(data=request.data)
if not serializer.is_valid():
return error_message(message=MessageKey.ERROR_MISSING_USERNAME_OR_PASSWORD.value)
username = serializer.data['username']
password = serializer.data['password']
try:
user = authenticate(username=username, password=password)
if not user:
return error_message(message=MessageKey.ERROR_INVALID_USERNAME_OR_PASSWORD.value)
if user.record_status == RecordStatus.ACTIVE.name:
# Create a new auth token using your token service
auth_token = create_auth_token(user)
user_serializer = UserSerializer(user)
user_data = user_serializer.data
user_data['auth_token'] = str(auth_token[0])
data = generate_user_account_profile(language, user, user_data)
return success_message(data=data)
else:
return error_message(MessageKey.ERROR_INVALID_USERNAME_OR_PASSWORD.value)
except Exception as ex:
traceback.print_exc()
return error_message(MessageKey.ERROR_DEFAULT_ERROR_MESSAGE.value)
The function create_auth_token(user) can be structured as folloows;
def create_auth_token(user):
"""
This is used to create or update an auth token
:param user:
:return:
"""
try:
token = Token.objects.filter(user=user)
if not token:
token = Token.objects.get_or_create(user=user)
else:
token = Token.objects.filter(user=user)
new_key = token[0].generate_key()
# Encrypt random string using SHA1
sha1_algorithm = hashlib.sha1()
sha1_algorithm.update(new_key.encode('utf-8'))
first_level_value = sha1_algorithm.hexdigest()
# Encrypt random string using MD5
md5_algorithm = hashlib.md5()
md5_algorithm.update(first_level_value.encode('utf-8'))
second_level_value = md5_algorithm.hexdigest()
token.update(key=second_level_value)
return token
except Exception as ex:
logging.error(msg=f'Failed to create auth token {ex}', stacklevel=logging.CRITICAL)
pass
Note: This can be adjusted to fit in any place of your choice i.e you can adjust it to work in your view.py file, services, etc
I have a problem I am not able to solve. I want to make use of http cookies in flask. This is the code from documentation:
#app.route('/token/auth', methods=['POST'])
def login():
username = request.json.get('username', None)
password = request.json.get('password', None)
if username != 'test' or password != 'test':
return jsonify({'login': False}), 401
# Create the tokens we will be sending back to the user
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
# Set the JWT cookies in the response
resp = jsonify({'login': True})
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp, 200
I use flask_restx which automatically turns the response into JSON, so that jsonify in the first example is not needed. However, still still need to jsonify it, because i can not use set_access_cookie on a dictionary. This results at the end in a nested response like this jsonify(jsonify(x))
#api.route("/login")
class UserLogin(Resource):
def post(self):
"""Allows a new user to login with his email and password"""
email = request.get_json()["email"]
password = request.get_json()["password"]
user = User.query.filter_by(email=email).one_or_none()
if user is None:
return {"message": "user does not exist"}, 404
user = user.format()
if bcrypt.check_password_hash(pw_hash=user["password"], password=password):
if user["active"]:
resp = jsonify({"login": True})
access_token = create_access_token(identity=user)
refresh_token = create_refresh_token(user)
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp, 200
# return (
# {"access_token": access_token, "refresh_token": refresh_token},
# 200,
# )
else:
return {"message": "User not activated"}, 400
else:
return {"message": "Wrong credentials"}, 401
This is the error: TypeError: Object of type Response is not JSON serializable
Any ideas how can I overcome this?
Was able to solve it like this:
data = dict(login=True)
resp = make_response(jsonify(**data), 200)
access_token = create_access_token(identity=user)
refresh_token = create_refresh_token(user)
set_access_cookies(resp, access_token)
set_refresh_cookies(resp, refresh_token)
return resp
I'm trying to authorize from my djano-app on vk.com. I'm using requests and client authorization. I'm trying to authorize by this way and getting an error:
{"error":"invalid_request","error_description":"Security Error"}
Internet suggests to re-login on VK in browser, but there isn't any solution for authorization from code.
My code:
class VkApiSingleton(object):
api_version = '5.95'
def __init__(self,
app_id=config.APP_ID,
login=config.ACCOUNT_LOGIN,
pswd=config.ACCOUNT_PASSWORD,
permissions='video,offline,groups'
):
# type: (int, str, str, str) -> None
self.app_id = app_id
self.app_user_login = login
self.app_user_pass = pswd
self.access_token = None
self.user_id = None
self.session = requests.Session()
self.form_parser = FormParser()
self.permissions = permissions
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super(VkApiSingleton, cls).__new__(cls, *args, **kwargs)
return cls.instance
#property
def get_session(self):
return self.session
def _parse_form(self, response):
# type: (requests.models.Response) -> None
self.form_parser = FormParser()
try:
self.form_parser.feed(str(response.content))
except Exception as err:
logger.exception(
'Error checking HTML form',
extra={'Error body': str(err)}
)
def _submit_form(self, **kwargs):
# type: (dict) -> requests.models.Response
if self.form_parser.method == 'post':
payload = copy.deepcopy(self.form_parser.params)
if kwargs.get('is_login', False):
payload.update({
'email': self.app_user_login,
'pass': self.app_user_pass
})
with self.get_session as session:
try:
return session.post(self.form_parser.url, data=payload)
except Exception as err:
logger.exception(
'Error submitting auth form',
extra={'Error body': str(err)}
)
raise VkApiError('Error submitting auth form: %s' % str(err))
def _log_in(self):
# type: () -> requests.models.Response
response = self._submit_form(is_login=True)
self._parse_form(response)
if response.status_code != 200:
raise VkApiError('Auth error: cant parse HTML form')
if 'pass' in response.text:
logger.error(
'Wrong login or password'
)
raise VkApiError('Wrong login or password')
return response
def _submit_permissions(self, url=None):
# type: () -> requests.models.Response
if 'submit_allow_access' in self.form_parser.params and 'grant_access' in self.form_parser.url:
return self._submit_form(token_url=url)
else:
logger.warning(
'Cant submit permissions for application'
)
def _get_token(self, response):
# type: (requests.models.Response) -> None
try:
params = response.url.split('#')[1].split('&')
self.access_token = params[0].split('=')[1]
self.user_id = params[2].split('=')[1]
except IndexError as err:
logger.error(
'Cant get access_token',
extra={'Error body': str(err)}
)
def auth(self):
auth_url = 'https://oauth.vk.com/authorize?revoke=1'
redirect_uri = 'https://oauth.vk.com/blank.html'
display = 'wap'
request_params = {
'client_id': self.app_id,
'scope': self.permissions,
'redirect_uri': redirect_uri,
'display': display,
'response_type': 'token',
'v': self.api_version
}
with self.get_session as session:
response = session.get(
auth_url,
params=request_params
)
self._parse_form(response)
if not self.form_parser.form_parsed:
raise VkApiError('Invalid HTML form. Check auth_url or params')
else:
login_response = self._log_in()
permissions_response = self._submit_permissions()
self._get_token(login_response)
If someone has a similar problem - I found some reasons of this.
1) Invalid type of authorization - try to use another type of authorization (it describes in official documentation)
2) Too many authorizations.
I solved problem like this:
1) Get token with "offline" permission by "Client Application Authorization"
2) Every time I need to use vk.api method - I am checking my token for expiring with secure method "secure.checkToken" (you need to get Service token to use this method. There are a lot of information in official documentation)
3) If my token expires - i am getting the new one.
I am using Django - ldap authentication in my project . Once if the user is authenticated , i need to set a cookie and return as a response to the server .
def post(self,request):
userData = json.loads(request.body)
username = userData.get('username')
password = userData.get('password')
oLdap = LDAPBackend()
if username == "admin" and password == "admin":
User_Grps = "AdminLogin"
else:
try:
User = oLdap.authenticate(username=username,password=password)
if User is not None:
User_Grps = User.ldap_user.group_dns
else:
User_Grps = "Check your username and password"
except ldap.LDAPError:
User_Grps = "Error"
return HttpResponse(User_Grps)
How to add a cookie to the response and send it with a the User_Grps to the client
response = HttpResponse(User_Grps)
response.set_cookie(key, value)
return response
That's it.