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,
}



20 January 2023

Special Flowers

Wow, it's been a really long time. I got distracted. Writing, for me, is a challenge. Talking on the other hand :-D So, I'm back, sorta. I'm going to try to get back into the blog cycle.

Special Flowers

(or Speaking of Design Consistency)

So what do I mean about consistency? The shape, feel, and structure of the code is more or less the same. Some people call this coherent design. Others refer to micro-patterns. I kinda like the term uniform design. Whatever word you want to use the point is that what you see in one part of the code is more or less the same as anywhere else in the code base. 

Here is an example. 

You may have made a choice to throw exceptions in order to communicate issues with validation. So you might have a function in your application that reviews the content of an HTTP request looking to see that the necessary parts are present. If something is missing you throw an exception. 

My expectation after seeing that approach is that it will be used everywhere in the code base. Every time we validate something we would expect an exception to be thrown if the validation fails. That would be 'uniform'.

OK, so what happens in your head when the next validation code you examine returns true if the content is valid and false if it isn't. Fair enough approach, but not the same as the first one. Not uniform.

Why does it make a difference?

I can give you at least two other ways that validation could be implemented. But two is one too many already. This inconsistency causes me to stop and think. What is the author doing? Is there a special reason that we're using true/false here and exceptions elsewhere? Now I have to research that and understand both. If I'm really diligent, I'll hunt down all the validation patterns and determine how many variations there are, try to figure out which is the predominant approach, and at least make any new validator consistent with that pattern. Very time-consuming. 

What's Our Job Again?

Our job as Software Engineers is to deliver value to the business. We measure that in terms of the features delivered. So thinking about how we can deliver features faster is pretty essential to our jobs. There are 1000 ways to deliver faster and we can get into some of those in another post. For this topic, I want to point out, that making yourself work harder is not an effective way to increase the rate of value delivery. 

Flowers: I only like tulips

So there should be no special flowers in the code. Ideally no variance in design and approach. If we can achieve that goal we reduce the total cognitive load necessary to understand the code. This is akin to pattern recognition (Sparrow's Deck anyone?). We should feel like, with respect to a particular code base, I've seen one validation block, I've seen them all. At least in terms of the mechanics. 

Benefits

By establishing a uniform design we are reducing the amount of work we need to do to gain comprehension of the code. That means we can learn the whole code base faster. If we can learn the code base faster we can make effective changes sooner. If we make changes sooner we can deliver value to the business quickly. That is our job* 

Related, and probably fodder for other posts, by reducing the difficulty of comprehending the code we increase the available time for carefully considering innovations. Imagine not feeling rushed to 'clean up' the code because you know the pattern and so you built it 'to spec' the first time; or at least close enough that it is not an arduous task to align with the uniform design.

What's Next

Looking back on my post history I've really taken plenty of time off. I'm going to do better in 2023. It's not a resolution, it's just what I will do. I'm open to writing based on prompts so if you have suggestions hit me up on Twitter @net_zer0 I have in mind a lot of posts around code quality, still more on TDD, and an update on my thoughts around Architecture and the role of the Architect in an organization.

* There is a lot to our job. Features are (potentially) value. Value delivery is how we get measured. I'll write more about the other aspect of our jobs soon.