26 January 2023

Auth0 - Testing APIs with Live Users

So I've been working on a API for a few weeks now. Big issues with security in my testing is that I was cheating, testing behind the authentication/authorization tier. That's fine for some tests, but we need to prove that the system is secure. In particular, we're using Row Level Security and we need an undeniable way to ensure that the holder of the Bearer token is allowed to see the data they are requesting. I'll spare you the gory details of how our RLS works and the nuances of getting a user ID associated with particular data sets (thought thats interesting too). The issue we ran into was, how do I get the Bearer Token from a Python Script using username and password. 

This seems like it should be a trivial thing, but it was not. In fact, there were many hoops jumped, misunderstandings, and retrospectively trivial discoveries that need to be made. So in that light, I'm memorializing that journey in this post. Again, I will spare you the details and myself the indignity of describing all the missteps in the hopes that this post can be used as a direct guide to solving a problem for others (and a cheat sheet for me in 6 months when I've forgotten how to do this).

Setup Auth0:

So obviously you need an Auth0 account setup with an application or API configured and at least one user configured and authorized appropriately. 

Setup Python:

In our case we're working in Python 3.7 using PyTest and the Auth0-python library. The first thing we need is some fixtures to sign us in and get a bearer token.

Environmental Considerations

In the code examples below all the UPPER_CASE stuff are either constants or environment variables. I used dotenv to configure the environment and excluded the .env file from git. I won't expose those details here for obvious reasons. 

Create some PyTest Fixtures

Since we only want to sign in once, we'll use a few session level fixtures. 

First we need to initialize the API to get a Token, this is pretty trivial;

@pytest.fixture(scope='session')
def auth0_get_token():
return GetToken(AUTH0_DOMAIN)

Second we need to get an active Auth0 client connected to our management API

@pytest.fixture(scope='session')
def admin_access_token(auth0_get_token):
mgmt_api_url = 'https://{}/api/v2/'.format(AUTH0_DOMAIN)
token = auth0_get_token.client_credentials(MGMT_API_CLIENT_ID, MGMT_API_CLIENT_SECRET, mgmt_api_url)
return token['access_token']

And lastly we need to get the bearer token itself

@pytest.fixture(scope='session')
def bearer_token(auth0_get_token):
return auth0_get_token.login(WEBAPP_CLIENT_ID,
WEBAPP_CLIENT_SECRET,
getenv('TEST_USER'),
getenv('TEST_PASSWORD'),
scope='openid profile email',
realm='email',
audience=AUDIENCE,
grant_type='password')['access_token']

Use the Bearer Token in a Test

In the end, that was all pretty trivial. So, now we can build a test using that bearer_token fixture like this;
def test_using_the_wrong_customer_id_fails(bearer_token):
customer_id = 999
test_user = getenv('TEST_USER')
event['headers'][X_CUSTOMER_ID_HEADER] = customer_id
event['headers']['Authorization'] = 'Bearer {}'.format(bearer_token)
expected_response = 'cannot find user {} at customer {}'.format(test_user, customer_id)

resp = lambda_handler(event, None)

assert resp == {
'body': json.dumps({'error': expected_response}),
'headers': DEFAULT_HEADERS,
'statusCode': 400,
}



No comments:

Post a Comment

Note: Only a member of this blog may post a comment.