def validate(user, encoded_token, kind): """ Validate a token generated by TokenManager.generate(...). :param user: The user the token was generated for. :type user: authservice.models.User :param encoded_token: The token to verify. :type encoded_token: Base64 Encoded JWT Token :param kind: The expected type of the token :type kind: TokenType :return: JWT encoded claims or False :rtype: str or bool :Example: >>> user = User.objects.create(...) >>> token = TokenManager.generate(user, TokenType.LEVEL_ZERO) >>> TokenManager.validate(user, token, TokenType.LEVEL_ZERO) """ assert(isinstance(user, User)) assert(isinstance(kind, TokenType)) assert(kind in list(TokenType)) jwt = PyJWT(options={ 'require_exp': True, 'require_iat': True, }) try: claims = jwt.decode( encoded_token, user.private_key(), algorithms=['HS256']) except InvalidTokenError as e: log.warning(str(e)) return False if ('sub' not in claims) or (claims['sub'] != user.uuid): log.debug("Missing SUB") return False if ('typ' not in claims) or (claims['typ'] is not int(kind.value)): log.debug("Missing TYP or invalid TYP") return False if 'jti' not in claims: log.debug("Missing JTI") return False # Is the jti in our database of valid jti? try: entry = ValidTokens.objects.get(jti=claims['jti'], user=user) except ValidTokens.DoesNotExist: return False else: seconds = int(entry.exp.timestamp()) if seconds != claims['exp']: return False return claims
def __init__(self): self.pyjwt = PyJWT() self.header_prefix = 'JWT' self.token_ttl = timedelta(seconds=300) self.not_before = timedelta(seconds=0) self.algorithm = 'HS256' self.verify_claims = ['signature', 'exp', 'iat', 'nbf'] self.require_claims = ['exp', 'iat', 'nbf'] self.leeway = 0 self.secret_key = None
def test_decode_when_algorithm_not_available(self): token = jwt.encode(self.payload, PRIVATE_KEY, algorithm='RS256').decode('utf-8') pyjwt_without_rsa = PyJWT() pyjwt_without_rsa.unregister_algorithm('RS256') with patch.object(jwt, 'decode', new=pyjwt_without_rsa.decode): with self.assertRaisesRegex(TokenBackendError, 'Invalid algorithm specified'): self.rsa_token_backend.decode(token)
def __init__(self, private_key: RSAPrivateKey = None, public_key: RSAPublicKey = None, expires=None, nbf_delta=None, algorithm="RS512"): self.__private_key = private_key self.__public_key = public_key self.__jwt = PyJWT(algorithms=self.ALGORITHMS) self.__expires = expires or self.DEFAULT_EXPIRATION self.__nbf_delta = nbf_delta or self.NBF_DELTA self.__algorithm = algorithm
def encode(self, payload, headers=None): """ Returns an encoded token for the given payload dictionary. """ jwt_payload = payload.copy() if self.audience is not None: jwt_payload['aud'] = self.audience if self.issuer is not None: jwt_payload['iss'] = self.issuer token = PyJWT().encode(jwt_payload, self.signing_key, algorithm=self.algorithm, headers=headers) if isinstance(token, bytes): # For PyJWT <= 1.7.1 return token.decode('utf-8') # For PyJWT >= 2.0.0a1 return token
def generateToken(): headers = { "alg": "HS256", "typ": "JWT" } salt = "2095132720951327" exp = int(time.time()) payload = { "userName": '******', "exp": exp } token = PyJWT().encode(payload=payload, key=salt,algorithm='HS256', headers=headers) return token
def generate(user, kind): """ Generate a session token for the user, providing the level and exp. :param user: The user to generate the token for. :type user: models.User :param kind: The type of token to generate :type kind: models.TokenType :return: JWT encoded claims :rtype: str :Example: >>> user = User.objects.create(...) >>> token = TokenManager.generate(user, TokenType.LEVEL_ZERO) """ assert(isinstance(kind, TokenType)) assert(kind in list(TokenType)) now = timezone.now() claims = { 'iat': now, 'exp': now + kind.ttl(), 'sub': user.uuid, 'jti': user.nonce(), 'typ': int(kind.value), } jwt = PyJWT(options={ 'require_exp': True, 'require_iat': True, }) ValidTokens.objects.create( jti=claims['jti'], exp=claims['exp'], user=user) log.info("Generated a {} for {}".format(kind, user.uuid)) return jwt.encode(claims, user.private_key(), algorithm='HS256')
def decode(self, token, verify=True): """ Performs a validation of the given token and returns its payload dictionary. Raises a `TokenBackendError` if the token is malformed, if its signature check fails, or if its 'exp' claim indicates it has expired. """ try: pj = PyJWT() payload = pj.decode( token, self.verifying_key, algorithms=[self.algorithm], verify=verify, audience=self.audience, issuer=self.issuer, options={'verify_aud': self.audience is not None}) header = pj.get_unverified_header(token) return {'payload': payload, 'header': header} except InvalidAlgorithmError as ex: raise JWTInvalidError('Invalid algorithm specified') from ex except InvalidTokenError: raise JWTInvalidError('Token is invalid or expired')
def login_user(): email = request.json['email'] password = request.json['password'] user = User.query.filter_by(email=email).first_or_404() if not user.verify_password(password): return jsonify({"error": "Incorrect password"}), 403 payload = {"id": user.id, "exp": datetime.utcnow() + timedelta(hours=12)} token = jwt.encode(self=None, payload=payload, key=current_app.config['AUTH_SECRET_KEY']) return jsonify({"token": token}), 200
class Jwt(object): """ Base class implementing JWT support. """ # For easier access Error = exc.JwtError AuthHeaderMissingError = exc.AuthHeaderMissingError ClaimMissing = exc.ClaimMissing BadAuthHeaderError = exc.BadAuthHeaderError InvalidTokenError = exc.InvalidTokenError NotAuthorizedError = exc.NotAuthorizedError UserNotFoundError = exc.UserNotFoundError TokenExpired = exc.TokenExpired def __init__(self): self.pyjwt = PyJWT() self.header_prefix = 'JWT' self.token_ttl = timedelta(seconds=300) self.not_before = timedelta(seconds=0) self.algorithm = 'HS256' self.verify_claims = ['signature', 'exp', 'iat', 'nbf'] self.require_claims = ['exp', 'iat', 'nbf'] self.leeway = 0 self.secret_key = None def authorize(self, auth_header: Optional[str]) -> User: """ Given an Authorization Header try to get the matching user. Args: auth_header (Optional[str]): The full content of the 'Authorization' header as read from the request. The way it's stored in the request will depend on framework used. Returns: User: The user instance represented by the token read from *auth_header*. Raises: Jwt.AuthHeaderMissingError: If the given auth header is empty or `None`. Jwt.BadAuthHeaderError: If the given auth header cannot be parsed. This is either if the Authorization header is completely wrong or the header prefix does not match whatever is set in `Jwt.header_prefix` Jwt.InvalidTokenError: Cannot decode the JWT token. Jwt.UserNotFoundError: User represented by the token was not found. This might happen if the user is deleted after the token is issued but before it expires. """ if not auth_header: raise self.AuthHeaderMissingError() token = self.get_token_from_header(auth_header) return self.authorize_token(token) def authorize_token(self, token: str) -> User: """ Get user for a given token. This method can be useful if the token is not coming from an HTTP header but other means (like websockets). Args: token: JWT token representing a user. This actually only stores the user ID, the actual user is retrievieved with `user_from_payload()` method. Returns: A user corresponding to the given token (if it's valid). Raises: Jwt.InvalidTokenError: If the token can't be decoded. Jwt.UserNotFoundError: If the user ID stored in the token cannot be found (.user_from_payload() call returned ``None``). """ try: payload = self.decode_token(token) except PyJwtInvalidTokenError: raise self.InvalidTokenError(f"Failed to decode token '{token}'") user = self.user_from_payload(payload) if user is None: raise self.UserNotFoundError() return user def get_token_from_header(self, auth_header: str) -> str: """ Parse auth header and extract the token Args: auth_header: The content of the auth header as received with in the request. Returns: The JWT token stored in the header """ # Verify the token is in the right format parts = auth_header.split() if parts[0] != self.header_prefix: raise self.BadAuthHeaderError( f"Bad auth header: '{parts[0]}', expected '{self.header_prefix}'" ) elif len(parts) == 1: raise self.InvalidTokenError("Missing or empty token") return parts[1] def user_payload(self, user) -> JsonDict: """ Return payload for the given user. This method must be implemented by the subclasses in order to integrate with any storage used by the project (jwtlib itself is framework agnostic). """ raise NotImplementedError("user_payload() method must be implemented") def user_from_payload(self, payload: JsonDict) -> User: """ Return a user for the given JWT payload. This method must be implemented by the subclasses in order to integrate with any storage used by the project (jwtlib itself is framework agnostic). This method is the opposite of `user_payload`. """ raise NotImplementedError( "user_from_payload() method must be implemented") def generate_token(self, user: Optional[User]) -> str: """ Generate JWT token for the given user. """ if user is None: raise self.NotAuthorizedError("No user to generate token for") headers = self.create_headers() payload = self.create_payload() payload.update(self.user_payload(user)) missing = frozenset(self.require_claims) - frozenset(payload.keys()) if missing: raise self.ClaimMissing("JWT payload is missing claims: {}".format( ', '.join(missing))) return self.pyjwt.encode(payload, self.secret_key, algorithm=self.algorithm, headers=headers) def create_headers(self) -> Optional[JsonDict]: """ Create general JWT token headers. This method can be overloaded in subclasses to customize the way tokens are generated. """ return None def create_payload(self) -> JsonDict: """ Create core JWT payload. This will contain the fields that are required by JWT like expiration and will be included in every token generated. Be careful when you overload this method in subclasses as you will have to take care of including the necessary fields or the jwtlib will break. """ iat = datetime.utcnow() return { 'iat': iat, 'exp': iat + self.token_ttl, 'nbf': iat + self.not_before, } def decode_token(self, token: str) -> JsonDict: """ Decode the token and return it's payload. """ opts = {'require_' + claim: True for claim in self.require_claims} opts.update({'verify_' + claim: True for claim in self.verify_claims}) try: return self.pyjwt.decode(token, key=self.secret_key, options=opts, algorithms=[self.algorithm], leeway=self.leeway) except ExpiredSignatureError: raise self.TokenExpired()
"""Extensions registry All extensions here are used as singletons and initialized in application factory """ from flask_bcrypt import Bcrypt from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from jwt import PyJWT from skel.configurations.logger import log db = SQLAlchemy() migrate = Migrate() bcrypt = Bcrypt() cors = CORS(resources={r"/v1/*": {"origins": "*"}}) log.debug("in debug mode") pwd_context = bcrypt jwt = PyJWT()
class JWT: __slots__ = ('__private_key', '__public_key', '__jwt', '__expires', '__nbf_delta', '__algorithm') DEFAULT_EXPIRATION = 86400 * 30 # one month NBF_DELTA = 20 ALGORITHMS = tuple({ 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES521', 'ES512', 'PS256', 'PS384', 'PS512' }) def __init__(self, private_key: RSAPrivateKey = None, public_key: RSAPublicKey = None, expires=None, nbf_delta=None, algorithm="RS512"): self.__private_key = private_key self.__public_key = public_key self.__jwt = PyJWT(algorithms=self.ALGORITHMS) self.__expires = expires or self.DEFAULT_EXPIRATION self.__nbf_delta = nbf_delta or self.NBF_DELTA self.__algorithm = algorithm def _date_to_timestamp(self, value, default, timedelta_func=add): if isinstance(value, timedelta): return timedelta_func(time.time(), value.total_seconds()) elif isinstance(value, datetime): return value.timestamp() elif isinstance(value, (int, float)): return value elif value is Ellipsis: return default() raise ValueError(type(value)) def encode(self, expired: DateType = ..., nbf: DateType = ..., **claims) -> str: if not self.__private_key: raise RuntimeError("Can't encode without private key") claims.update( dict( exp=int( self._date_to_timestamp( expired, lambda: time.time() + self.__expires)), nbf=int( self._date_to_timestamp( nbf, lambda: time.time() - self.__nbf_delta, timedelta_func=sub)), )) return self.__jwt.encode( claims, self.__private_key, algorithm=self.__algorithm, ).decode() def decode(self, token: str, verify=True, **kwargs) -> dict: if not self.__public_key: raise RuntimeError("Can't decode without public key") return self.__jwt.decode(token, key=self.__public_key, verify=verify, algorithms=self.ALGORITHMS, **kwargs)
from flask import Flask, request from flask_restful import Resource, Api from jwt import PyJWT from security import authenticate, identity app = Flask(__name__) app.secret_key = 'jose' api = Api(app) jwt = PyJWT(app, authenticate, identity) items = [] class Item(Resource): def get(self, name): item = next(filter(lambda x: x['name'] == name, items), None) return {'item': item}, 200 if item else 404 def post(self, name): if next(filter(lambda x: x['name'] == name, items), None): return { 'message': "An item with name '{}' already exists.".format(name) }, 400 data = request.get_json() item = {'name': data['name'], 'price': data['price']} items.append(item) return item, 201
class JWT: __slots__ = ( "__private_key", "__public_key", "__jwt", "__expires", "__nbf_delta", "__algorithm", ) DEFAULT_EXPIRATION = 86400 * 30 # one month NBF_DELTA = 20 ALGORITHMS = tuple({ "RS256", "RS384", "RS512", "ES256", "ES384", "ES521", "ES512", "PS256", "PS384", "PS512", }) def __init__( self, private_key: Optional[RSAPrivateKey] = None, public_key: Optional[RSAPublicKey] = None, expires: Optional[int] = None, nbf_delta: Optional[int] = None, algorithm: str = "RS512", ): self.__private_key = private_key self.__public_key = public_key self.__jwt = PyJWT(algorithms=self.ALGORITHMS) self.__expires = expires or self.DEFAULT_EXPIRATION self.__nbf_delta = nbf_delta or self.NBF_DELTA self.__algorithm = algorithm def _date_to_timestamp( self, value: DateType, default: Callable[[], R], timedelta_func: Callable[[float, float], int] = add, ) -> Union[int, float, R]: if isinstance(value, timedelta): return timedelta_func(time.time(), value.total_seconds()) elif isinstance(value, datetime): return value.timestamp() elif isinstance(value, (int, float)): return value elif value is Ellipsis: return default() raise ValueError(type(value)) def encode( self, expired: DateType = ..., nbf: DateType = ..., **claims: int ) -> str: if not self.__private_key: raise RuntimeError("Can't encode without private key") claims.update( dict( exp=int( self._date_to_timestamp( expired, lambda: time.time() + self.__expires, ), ), nbf=int( self._date_to_timestamp( nbf, lambda: time.time() - self.__nbf_delta, timedelta_func=sub, ), ), ), ) return self.__jwt.encode( claims, self.__private_key, algorithm=self.__algorithm, ).decode() def decode( self, token: str, verify: bool = True, **kwargs: Any ) -> Dict[str, Any]: if not self.__public_key: raise RuntimeError("Can't decode without public key") return self.__jwt.decode( token, key=self.__public_key, verify=verify, algorithms=self.ALGORITHMS, **kwargs, )