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
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')
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()
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)
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, )