Overview
Authentication can generally be defined as the act of confirming the identity
of a resource - in this case the consumer of an API. Once a user has been
authenticated - they are usually authorized to get access to desired
resources/APIs, therefore we can say that.
-
Authentication is used to determine who the user of an
API is.
-
Authorization is used to determine what resources the
identified user has access to.
Authentication standards
There are multiple standards and technologies available for authenticating
users, for example;
-
Form-based authentication - Web/HTML based authentication
that commonly uses HTTP cookies.
-
Basic/Digest/NTLM authentication - Uses HTTP headers to
identify users.
-
WS-Security SAML and Username Tokens - SOAP/XML based
authentication, passes credentials and assertions in SOAP message headers,
optionally signed and encrypted
-
API Key based authentication - each request to an API
contains a key uniquely identifying the client.
-
OAuth 1.x/2 - HTTP-based interactions and flows that
authorize usage of HTTP resources (API, Web, etc). OAuth indirectly includes
a step for authentication but makes no claims on how that authentication
should be done.
When relaying authentication credentials and keys over HTTP it should be
mandatory to use HTTPS/SSL instead of unencrypted HTTP to avoid
“eavesdropping” and stealing of credentials by potential attackers.
When it comes to testing APIs that require authentication there are number of
general best practices to consider - both in regard to general test management
but also in regard to testing of the authentication mechanism at hand.
Store Credentials Centrally and Safely
If your test scripts include user credentials or access tokens these should be
stored centrally so they can be easily changed and safely (preferably
encrypted) so no one that gets access to your test scripts can actually read
them. Make sure that they don’t show up in log files or test results; for
example if you have a test that validates a login - have error messages that
conceal the actual username or password.
Authenticate as Real Users Would
Do not create some kind of “super-user” login or access key to be used for
testing and demonstration purposes. It is a dubious practice; not using the
same kind of authentication mechanism as your users are (as cumbersome as it
may be) puts you in the risk of not identifying corresponding issues, for
example expiring sessions, unauthorized access errors or errors in the
authentication process itself. Therefore, if practically possible please make
sure to authenticate with your system in the same way as your users would;
don’t create backdoors that hide other issues from your tests.
Create Negative Tests
Make sure you have negative tests in regard to authentication and
authorization. Some examples:
- pass invalid usernames and passwords
- attempt to access protected resources without credentials
- attempt to use invalid credentials/session tokens
-
provoke lockout of an account and validate the locking logic/timeframe is
enforced
-
attempt to send (invalid) credentials over non-secured channels, i.e. HTTP
instead of HTTPS, un-encrypted XML or JSON, et c.
-
ensure that an API client does not get access to resources that he/she does
not have access to.
Any of these negative tests should validate that the response message contains
relevant error codes and messages, but no information to the client that could
be used to compromise the system. For example, a failed login attempt should
conceal if the provided username was actually registered in the system. (See
the Best Practices section article
Negative Testing .)
Design Tests with Authentication in Mind
When designing your tests there are couple of techniques that can make
authentication-related flow easier:
-
Modularize your tests - if possible move flows used for
authentication to a shared script or test and call that from your other
tests. This makes it easy to modify authentication if the underlying
technology or requirements change.
-
Parameterize for environments - if your tests are supposed
to run against different environments, (dev, test, staging, production, etc)
- make sure that it easy to switch between these.
-
Use data-driven techniques for running same tests for a
large number of users/credentials - to ensure that these are all handled
correctly in regard to access control.
Run As Load/Stress Tests
Any of the above examples should be run as a load-test to make sure that the
authentication mechanism of an API isn’t compromisable under extreme load
(which is a common strategy for attackers). This could be related to incorrect
thread management, database connection sharing, etc. If possible combine
different types of authentication tests, for example run both negative
authentication and authorization tests simultaneously with tests for bad error
messages.