jetbridge / flask_cognito

Flask authentication with JWT against AWS Cognito
MIT License
94 stars 30 forks source link

Disable authorization during development #21

Closed ostwalprasad closed 3 years ago

ostwalprasad commented 3 years ago

Hello!

Is there a flag or way to disable Authorization @cognito_auth_required globally during development? I don't want to remove all the decorators while testing on the postman.

revmischa commented 3 years ago

No but please feel free to contribute!

joshfriend commented 3 years ago

You could wrap the decorator in another decorator to apply it conditionally, like this:

from typing import Callable, Func
from functools import wraps
from flask import current_app

def conditional_decorator(condition: Func[bool], decorator: Func[Callable]):
    def _decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            if condition():
                return decorator(function)(*args, **kwargs)
            else:
                return function(*args, **kwargs)
        return wrapper
    return _decorator

@app.route("/example")
@conditional_decorator(lambda: not current_app.debug, cognito_auth_required)
def view_func():
    return Response()

Which would only apply cognito_auth_required when your app is not in debug mode.

Additionally, you can do unit tests with the auth decorators in place if you sign your own JWTs and tell the library the public key you are using:

# tests/conftest.py
@pytest.fixture(name='client', scope='function')
def fixture_client(app):
    # found this setting in the underlying cognito jwt library
    os.environ['AWS_COGNITO_JWKS_PATH'] = os.path.join(os.path.dirname(__file__), 'util', 'jwks.json')

    yield app.test_client()
// Generate this at https://mkjwk.org/
{
  "keys": [
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "test",
      "kty": "RSA",
      "n": "...",
      "use": "sig"
    }
  ]
}
# tests/util/jwt.py
from typing import Any, Dict

from jose import jws
from jose.constants import ALGORITHMS

# This is the private key referenced in jwks.json (which is basically the public key in a json format)
# Generated using https://mkjwk.org/
_rsa_private_key = """-----BEGIN PRIVATE KEY-----
...etc...
-----END PRIVATE KEY-----"""

def create_token(payload: Dict[str, Any]) -> str:
    return jws.sign(payload, _rsa_private_key, headers={'kid': 'test'}, algorithm=ALGORITHMS.RS256)
# tests/conftest.py
@pytest.fixture(name='jwt')
def fixture_jwt():
    user_id = str(uuid4())
    now = int(time())
    payload = { # can be whatever you want for these fields, but this is what a Cognito ID token has.
        "sub": user_id,
        "aud": "test",
        "token_use": "id",
        "auth_time": now,
        "iss": "test",
        "name": "Test User",
        "cognito:username": user_id,
        "exp": now + 100,
        "iat": now,
        "email": "test@example.com"
    }
    yield create_token(payload)
ostwalprasad commented 3 years ago

That's a very cool approach.

Thanks for the detailed response.