Client applications¶
If you are writing a client application, you will probably want to test the nominal authentication case, i.e. the case when the users successfully logs in and give their consent to your application. Depending on your implementation, you might also need to test how your application behaves in case of error during the authentication process.
You can also test how your application deals with OIDC registration or refresh token exchange.
pytest-iam will help you set up some of those scenarios in your tests.
Setting up your test¶
Users & groups¶
You can use the available User
and Group
models to set up their
IAM server for your tests. Optionnally you can put them in pytest fixtures so they are re-usable:
@pytest.fixture
def user(iam_server):
user = iam_server.models.User(
user_name="user",
emails=["email@example.org"],
password="password",
)
user.save()
yield user
user.delete()
@pytest.fixture
def group(iam_server, user):
group = iam_server.models.Group(
display_name="group",
members=[user],
)
group.save()
yield group
group.delete()
If you don’t care about the data your users and group, you can use the available random generation utilities.
@pytest.fixture
def user(iam_server):
user = iam_server.random_user()
user.save()
yield user
user.delete()
@pytest.fixture
def group(iam_server, user):
group = iam_server.random_group()
group.members = group.members + [user]
group.save()
yield group
group.delete()
OIDC Client registration¶
Before your application can authenticate against the IAM server, it must register and give provide details
such as the allowed redirection URIs. To achieve this you can use the Client
model. Let us suppose your application have a /authorize
endpoint for the authorization code - token exchange:
@pytest.fixture
def client(iam_server):
inst = iam_server.models.Client(
client_id="client_id",
client_secret="client_secret",
client_name="My Application",
client_uri="http://example.org",
redirect_uris=["http://example.org/authorize"],
grant_types=["authorization_code"],
response_types=["code", "token", "id_token"],
token_endpoint_auth_method="client_secret_basic",
scope=["openid", "profile", "groups"],
)
inst.save()
yield inst
inst.delete()
Note that the IAM implements the OAuth2/OIDC dynamic client registration protocol, thus you might not need a client fixture if your application dynamically register one. No initial token is needed to use dynamic client registration. Here is an example of dynamic registration you can implement in your application:
response = requests.post(
f"{iam_server.url}/oauth/register",
json={
"client_name": "My application",
"client_uri": "http://example.org",
"redirect_uris": ["http://example.org/authorize"],
"grant_types": ["authorization_code"],
"response_types": ["code", "token", "id_token"],
"token_endpoint_auth_method": "client_secret_basic",
"scope": "openid profile groups",
},
)
client_id = response.json()["client_id"]
client_secret = response.json()["client_secret"]
Nominal authentication case¶
Let us suppose that your application have a /protected
that redirects users
to the IAM server if unauthenticated. With your User
and Client
fixtures, you can use the
login()
and consent()
methods
to skip the login and the consent page from the IAM.
We suppose you have a test client fixture like werkzeug Client
that allows to test your application endpoints without real HTTP requests. Let
us see how to implement an authorization_code authentication test case:
def test_login_and_consent(iam_server, client, user, testclient):
iam_server.login(user)
iam_server.consent(user)
# 1. attempt to access a protected page
res = testclient.get("/protected", status=302)
# 2. authorization code request
res = requests.get(res.location, allow_redirects=False)
# 3. load your application authorization endpoint
res = testclient.get(res.headers["Location"], status=302)
# 4. redirect to the protected page
res = res.follow(status=200)
What happened?
A simulation of an access to a protected page on your application.
That redirects to the IAM authorization endpoint. Since the users are already logged and their consent already given, the IAM redirects to your application authorization configured redirect_uri, with the authorization code passed in the query string. Note that
requests
is used in this example to perform the request. Indeed, generally testclient such as the werkzeug one cannot perform real HTTP requests.Access your application authorization endpoint that will exchange the authorization code against a token and check the user credentials.
For instance, your application can redirect the users back to the page they attempted to access in the first place.
Error cases¶
The OAuth2 and the OpenID Connect specifications details how things might go wrong:
The OAuth2 error codes:
invalid_request
unauthorized_client
access_denied
unsupported_response_type
invalid_scope
server_error
temporarily_unavailable
The OIDC error codes:
interaction_required
login_required
account_selection_required
consent_required
invalid_request_uri
invalid_request_object
request_not_supported
request_uri_not_supported
registration_not_supported
You might or might not be interested in testing how your application behaves when it encounters those situations, depending on the situation and how much you trust the libraries that helps your application perform the authentication process.