API Security, Diving into Authentication and Json Web Token
Authentication and JWT best practices
As a developer, you may have seen how software architecture has evolved over the last decades. From monolith to Service-Oriented Architecture (SOA) with (not so) Simple Object Access Protocol (SOAP) web services, and later, Representational State Transfer (ReST) APIs. Nonetheless, I would like to briefly outline some authentication protocols that came out along with these architecture changes, leading to interoperable authentication and stateless services.
LDAP — my first authentication experience
Almost twenty years ago, my first main software development was a kind of Enterprise Resource Planning (ERP) based on the Company’s framework. It was a monolith, where access to non self-managed resources used to be done through database replications. A good example was the login process. To authenticate a user, the application checked the user’s credentials and roles onto a local replicated user directory, using the Lightweight Directory Access Protocol (LDAP). The main benefit was for the user to have a single account (identifier & password) to remember. But the drawback was to replicate critical resources (user’s information) across all the applications infrastructures.
SAML — delegating authentication
Many years later, we moved to the Security Assertion Markup Language (SAML) authentication and authorization protocol, which played an important role in enhancing security and User eXperience. Without going too deep on how the framework works, we can sum up SAML as the ability for an application called Service Provider (SP), to safely trust (through signed assertions) user’s information (identity, roles, …), embedded into a XML provided by the Identity Provider (IdP). The key point to remember here is we have three main actors:
- The subject (a human or a machine) owning the authentication credentials.
- The Identity Provider (IdP) in charge of performing authentication, issuing authentication assertions.
- The Service Provider (SP) is an application/system that expects and validates authentication assertions.
In order for it to work, the SP must register with the IdP. It is during this configuration step that the SP will obtain the sign in, sign out URL, and most importantly the public key needed to validate the signed SAML assertions.
The protocol main benefits are:
- Uncouple application and user directories. Applications do not need to be connected to the directories (LDAP, …). This increases security (no shared component, no credential management) and decreases the complexity of infrastructure and operation (no need to build directory replication, a single authentication service is connected to the directory).
- Enhance User eXperience. Having a dedicated service to authenticate users, enables Single Sign On (SSO). A user will only need to log in once among multiple applications sharing the same identity provider.
- Centralizing the authentication service. All the applications will benefit once for all from the login features such as Multi-Factor Authentication (MFA), One Time Password (OTP), QR Code based authentication, account management (reset password), and many more…
During the same period, we started to develop some web services. In a few words, an application was able to communicate and fetch resources over the network. From a security point of view, the web-service was granting access to the application, no matter which user was connected to the application. The application is trusted to access only the data of the authenticated user in the session, but nothing can ensure it.
OpenID Connect — secured stateless services
OpenID Connect (OIDC) is another famous authentication protocol built on top of OAuth 2.0 (an authorization framework). I already explained in a previous publication (c.f here the “Understand Authentication and Authorization” section). It is pretty similar to SAML, it can be summed up as the ability for an application called Client, to safely trust user’s information (identity, roles, …), embedded into a Json token named id_token (instead of an XML), provided by the OpenID Provider (OP). Like SAML, we share the same three actors involved in the authentication workflow, named differently:
- The subject (a human or a machine). In OAuth 2.0, the subject is referred as Resource Owner (RO)
- The OpenID Provider (OP), a server capable of Authenticating the user and providing claims to a Relying Party by issuing tokens. In OAuth 2.0 the OP is referred as Authorization Server (AS)
- The Relying Party (RP), client application requiring user authentication and claims. In OAuth 2.0 the RP is referred as Client
Access, Refresh and ID tokens
When the user (subject) is authenticated on a RP, the OP generates up to three distinct kind of tokens:
- access_token: token intended to access subject resources without additional authentication
- refresh_token: token intended to renew access_token
- id_token: token containing claims about the authentication event. It may carry claims like identity, role, personal information, …
While the ID token will always be a Json Web Token (JWT), the access token may be an opaque token (unreadable token contents except for the issuer), or may use the JWT format too. The relying party can now, on behalf of the user, consume resources that are managed independently of the Relying Party.
SAML Vs OIDC
At this stage, OIDC shares the same benefits mentioned above with SAML. But there is a big difference, OIDC has been built on top of an authorization framework (OAuth 2.0). Basically, the OpenID Provider enables a Relying Party to obtain a token, identifying the user, and limited to resources approved by the user. This token then can be used to access multiple resource servers (RS). Hence, the RS is ensured to return resources bound to the authenticated user.
Let’s take a break for a few seconds and focus on the OAuth 2.0 actor’s names. We mentioned a fourth actor (Resource Server), inherited from the OAuth 2.0 specification, and not being involved in the authentication workflow. It is basically a (HTTP) API service, exposing endpoints able to create, read, update and delete data. Expecting an access_token to allow access to the user’s resources. The user is called Resource Owner.
I really appreciate the way the OAuth 2.0 specifications named these actors, because it highlights the fact that we, as a company, are not the owner of the data. The owner is the user (RO). We are the host of these data, in whom the user has given their trust to manage them in a secure way. Then, our Authorization Server is in charge to issue a token for a Client, granting access to some specific resources, previously approved by the user.
As a user, you are probably using one of these brands (Facebook, Google, Apple, WeChat, …) as the main means of authentication. You do this to allow applications to access your Facebook profile and friends list, your Google email address or even your Apple Health information. OIDC helps us to define boundaries between UI, the application business logic, and highly secured resource dependencies. That is for sure what makes the difference and explains its wide adoption. For more than a decade, we have been building software using service-oriented architecture principles. Moving from monolith to small (micro) services, easier to maintain, API first or better, contract First. OIDC is separating concerns between authentication, authorization, and resource server. A design principle coined in 1974.
Json Web Tokens
Now, as we better understand the benefits of OIDC and OAuth 2.0, let us concentrate on the access_token and in particular towards a specific format: JWTs.
Self Container Security Tokens
JWT is part of the Javascript Object Signing and Encryption JOSE specification composed of several RFC, among them:
- JWA (Json Web Algorithm)
- JWK (Json Web Key)
- JWS (Json Web Signature)
- JWE (Json Web Encryption)
- JWT (Json Web Token)
JWT are tokens containing claims encoded as a Json object, that can be digitally signed and/or encrypted. It is composed of two to three parts:
- Header (json format), containing information about the token itself such as the key identifier (kid claim) and/or the algorithm (alg claim) used to sign the token…
- Payload (json format), containing information such as the issuer (iss claim) which has generated it, scope approvals (scopes claim), expiration (exp claim), the subject unique identifier (sub claim), the client identifier (client_id claim), and many more…
- Signature is an optional (but strongly recommended) part of the JWT. It contains a hash of the header and payload. Hashing ensures integrity, as we can verify the header and payload content has not been altered. But the hash is not enough to trust the content, it must be digitally signed. The signature ensures the authenticity of the token origin, as the key used must be only known by the issuer. The key may also be known by the relying party according to the used algorithm. (please see here the“The blue or the red pill? Symmetric vs Asymmetric” section)
If for any good reasons, we need to ensure privacy of the JWT, then we encrypt the payload using Json Web Encryption cryptographic mechanisms.
Because JWT are signed, they enable relying parties and/or resource servers to self validate the token and then trust the content. Therefore, the issuer and the relying party are loosely coupled, and the issuer will not become a Single Point Of Failure (SPOF) in the system architecture.
Technically speaking, there is a way to revoke JWT access_token. That would imply the issuer to manage a white list of Json Token Identifiers (jti payload claim), and remove it from the white list when the user logs out. Unfortunately, in such a case, resources servers have to perform a token validation (using the OIDC token introspection endpoint), re-coupling both services and making the issuer a SPOF.
JWT recommendations
With any solution comes some drawbacks.
The “Self Container” tokens cannot be revoked. As such the access token stays valid until it expires. So the lifetime must be as short as possible. When the relying party requires longer lifetime access, it is only possible if token storage is secured. If so, a refresh token is delivered alongside the access token. For instance, on mobile apps, tokens can be stored into the device’s secure storage. So you can extend the refresh_token lifetime in such a manner the user will not re-authenticate for a long time (way more than the default SSO lifetime). A constraint is that when the user logs out, the RP must call the OIDC logout endpoint to revoke the refresh_token, ensuring no access_token renewal.
JWT has a precise validation mechanism explained in the RFC. This exists to prevent some hacks like an override in the header algorithm (alg claim) to set the value to NONE. This may lead the RS to not check the signature. Some claims have also to be validated. For instance the “nbf” claim (not before) means even if a token is not expired, it can be rejected if it is used too early. The takeaway here is to never trust JWT claims until JWT has been fully validated. And in the validation, the signature is mandatory.
With a basic use, you may think a JWT like an on/off switch. If it is validated, then you grant the access. You have to prevent that! Instead, use the “scopes” claims as much as possible to finely filter which resources or sub-resources the application has been allowed to acces. It can be “address”, “email”, “name”… You may also rely on the audience (aud claim) to specifically target a resource server.
If you expose some real critical resources, you may want to reduce the risk of a leaked token usage. Thus, you should ensure the JWT has been issued from a recent user authentication. To do so, you can rely on the auth_time claim, which indicates when the user has performed the authentication on the login page. If you are a relying party, you may consider forcing a new authentication (no matter if the SSO session is not yet expired) thanks to the prompt=login
query parameter in the “OIDC” authorization endpoint. You may even rely on the authentication method reference (amr claim), which indicates how the user has logged in. For instance, values might indicate password, OTP, both or others… It is up to the critical RS to define what are the security requirements in its API contract.
The JWT can represent a human (usually the token is obtained with the OAuth 2.0 authorization_code flow, or any other flows based on a human login interaction), but it can also represent a machine (via the OAuth 2.0 client credentials flow). As a resource server, you have to be able to make the difference. Speaking of subject, if your API needs the user’s unique identifier to do something (process data, retrieve data…) do not use one from an URI query parameter, but rely instead on the JWT subject (sub claims), as unlike an HTTP request query parameter, a signed JWT ensures integrity and authenticity. Similarly, use this JWT subject in addition to other URI parameters to correctly process data ( if someone calls a GET /address/123456, verify the address 123456 belongs to the subject described by the JWT before sending back the data). OWASP Api top 10 list this kind of attack, named Broken Object Level Authorization (BOLA) as the first in its 2019 version.
On the front (web) side, you might be tempted to rely on this JWT (access token) to make it a session identifier for your web applications. This would have the advantage of relieving you of managing storage on the backend side. Thus making your application stateless. But it would be better to avoid it. You will face storage problems for this token on the front side. Browser local & session storage are not secure enough (unlike mobile device secured storage). You will experience CORS issues for consuming resources that are not under the same domain. Going through cookies (secure, same-site, http only) reduces the convenience of making calls from JavaScript. And as said before, a JWT is not revocable. So if a user keeps it, he can fix the session again after a log-out. Even worse, a hacker could forge attacks by pushing their own token into forms to steal personal information. Obviously, refresh tokens must never be provided to a public client (stateless web applications).
All those drawbacks seem time-consuming to fix, but if you check standard open source libraries, they will help you a lot. Keep an eye on their updates! Pay attention to known vulnerabilities and best practices, they are available here: https://datatracker.ietf.org/doc/html/rfc8725 Also, if you want to go deeper, have a look at IANA standards JWT claims. Some JWT claims may interest you.
Conclusion
OpenID Connect along with Json Web Token are today a de facto standard. The first one enables SSO among multiple applications, providing fine-grained authorization (user approvals) and uncoupling authentication and authorization from applications and services. The last one enables stateless application (no session storage on resource servers). Nevertheless, it comes with few drawbacks. Following best practices can easily solve many of them.
How about you?
Do you have experience using JWT? Do you have some additional recommendations? Let us know in the comments below!
🙏🏼 If you enjoy reading this article, please consider giving it a few 👏👏👏.
💌 Follow our latest posts on Twitter and LinkedIn and discover our sport tech initiatives like open APIs on our developers website 🚀
Who am I
Staff Engineer, working for Decathlon. I am more interested in architecture and backend development, especially on APIs and authentication/security topics. Sharing knowledge is what I prefer in my daily job. Open source world is also a fascinating topic for me.