Why we love OAuth 2.0 at Kinde and why you should too

By Evgeny Komarevtsev — Published

Maybe you know all about it, or maybe you’re just getting to know it. Either way, this article aims to help you understand how and why we use OAuth 2.0 at Kinde, and what this means for connecting your apps to Kinde for authentication and authorization.

First, I’ll describe a short history of OAuth, establish some basic terms, then discuss two ways of authenticating the front-end and back-end applications, highlighting the major things to look for.

As the cherry on top, I’ll show how we support multi-API authentication and authorization.

Why did we choose OAuth?

Link to this section

At Kinde we value and support open standards. As much as we like to innovate on the wheel, authentication and authorization are not wheels that need reinventing.

Every language we support has its own authentication libraries for OAuth 2.0, and we never want to lock you in with a proprietary protocol. We also don’t believe we could build a more secure one ourselves, and the popularity of OAuth proves that.

We want founders to be able to use Kinde with niche tech or any tech easily, even if we haven’t provided an SDK for it (yet).

With reasonable limitations, our authentication is both OpenID and OAuth 2.0 compliant, so you can use your library of choice and run with it. We support OpenID Connect Discovery, so you won’t even need to know the details to connect.

Although outside of scope, our API is described in the Open API spec, which opens it up to generation by anyone.

A brief history of OAuth

Link to this section

It’s Fall (Autumn) of 2006, Twitter introduces OpenID initiative to share user identities between services without exposing passwords.

OAuth is a survivor through smart adaption, with a rich history and battle-tested code.

Types of tokens and terms

Link to this section

ID Token identifies the user

Access token authorizes the user or an application

Refresh token can be used to generate a new ID and access token

Application - referred to OAuth 2.0 client in the specification, in this document called application

Client - a user agent authenticating against an application (web browser, mobile application or a server-side process)

Confidential vs public clients

Link to this section

OAuth 2.0 standard describes two types of clients (public & confidential), which can be characterized by their ability to securely hold secrets.

Here are examples of clients accessing Kinde applications:

  • A web browser is classified as a public client
  • A mobile application is classified as a public client
  • A back-end web server is classified as a confidential client
Kinde application typeSecret provided
is confidential?
Used fromGrants includedScopes includedAuthenticates id_tokenAuthorizes access_token
Single Page Web ApplicationNoWeb browsers
mobile applications
authorization_code
refresh_token
openid
profile
email
offline
UsersUsers
Organizations
APIs
Regular Web ApplicationYesBack-end systemsauthorization_code
refresh_token
client_credentials
implicit
openid
profile
email
offline
UsersUsers
Organizations
APIs
Machine to machine ApplicationYesBack-end systemsclient_credentials-n/aApplications
APIs

Think about a client secret like a password that you need to store securely. To prevent accidentally exposing the secret, we don’t generate it for Single Page Web Applications.

As you can see, not all clients are created equal, which brings us to the saga of public client authentication. But first, a bit about JWTs.

JWT token as a delivery mechanism

Link to this section

They say that even if you develop the best medicine in the world, without an effective delivery system, it’s useless. Same goes for token delivery.

There are multiple approaches to handling latency and consistency in computer systems. Some things, like changing a user’s password need to be consistent, and some things are ok to catch-up over time.

OAuth 2.0 specification doesn’t enforce the format of tokens, but JWT (RFC 7519) is a de-facto pairing for a good reason. It provides a way to verify tokens on the receiving side, as well as being extensible.

Kinde has opted to support asymmetric encryption for JWT tokens only (SHA256). We are deliberately not supporting symmetric HMAC. Although this generates a larger payload, it provides greater security.

When integrating without an SDK, be sure to check the token signing method is RS256. This prevents JWT algorithm confusion attacks.

By using refresh tokens and adjusting token lifetimes you can tweak latency if you would like the data to be consistent.

On each token refresh you’ll receive an up-to-date snapshot of the state of the user.

In addition to standard OpenID claims, such as sub, aud, etc, we extend our user, application and ID tokens with the following claims. This list will grow as we add more features.

TokenSubjectClaimDescription
ID tokenuserorg_codesA list of organizations user has access to
Access tokenuserorg_codeThe organization code user currently authorized to
Access tokenuserpermissionsThe list of user permissions for the authorized organization
Access tokenuserfeature_flagsThe list of feature flags available to the user in the current context
Access tokenapplicationazpThe application id requesting the token
Access tokenapplicationgtyThe list of grant types, mostly used to identify client_credentials tokens

OAuth 2.0 client authentication

Link to this section

Before authenticating the user, we need to know that the caller has access to the application in the first place. With the back-end applications it is usually achieved with the client secret.

As you can guess, without the secret, it is impossible to trust the caller (hence the name public client), which brings us to the evolution of the approaches to making public client authentication more secure.

The saga of public client authentication

Link to this section

To reduce chances of accidentally exposing the client secret, Kinde does not provide one for the single-page applications.

Chapter one - Implicit authentication flow

Link to this section

This saga starts with the Resource Owner Password Credentials Grant. It was introduced as part of the initial OAuth 2.0 specification with a set of security recommendations and a list of possible vulnerabilities.

This is the initial take on authenticating of public clients. With this authentication flow, the client receives the ID token directly as a result of calling the authorization endpoint. This flow cannot be used to receive an access token and is used only for authentication.

The risk of this approach is well-recognized and we highly discourage to use it for any new builds. This is the reason Kinde doesn’t support implicit authentication flow for public clients.

Limitations of the implicit flow includes the inability to use refresh tokens and instead using the hidden iframe to maintain long-running sessions.

Chapter two - Authorization code grant

Link to this section

This flow relies on a user agent that can follow redirects, and introduces separation of the initial request and the token exchange, effectively removing the direct exposure of the tokens to the user agent (usually browser).

Additionally, it introduced the state and redirect_uri parameters to correlate multiple requests in the flow.

This auth flow provides the benefit of client authentication without the use of a secret.

The authorization code flow is susceptible to the following attacks (not exhaustive):

  • Access token exposure (via browser history, in JS object state, browser local storage)
  • Client-side CSRF
  • Redirect URL poisoning
  • Clickjacking (this type of attack is not specific to the authorization code flow)

Chapter three - PKCE extension

Link to this section

In September 2015, the Proof Key for Code Exchange (PKCE) RFC 7636 was published.

The PKCE extension effectively prevents authorization code injection attacks, making clients more secure. It also enables public clients to support refresh tokens and long-running auth sessions without silent authentication.

For better security Kinde requires you to use the PKCE extension with public clients.

Refresh tokens in the browser?

Link to this section

Before the PKCE extension, refresh tokens were mostly the concern of backend servers. However, with a certain consideration this has opened-up the ability for the public clients to operate more like the confidential clients.

When an application uses the authorization code flow with the PKCE extension, it opens it up to offline access. This enables Kinde to issue a refresh token which can be used to exchange for the fresh set of tokens, limiting the active token exposure time.

Kinde uses refresh token rotation technique making both access and refresh tokens invalid after the refresh. This dramatically decreases the exposure time to intercept and use the token.

Can I make my app even more secure?

Link to this section

For front-end apps, authorization code with PKCE plus refresh token with rotation is pretty much state of the art, but there is more to that picture.

The most secure system you can have is the system that never accessed or connected the network. But no connected system is 100% secure. So it is less about making it “bullet-proof”, but making it as difficult as possible to exploit. We live in a connected world and we need to look into ways of balancing those two paradigms.

There are two way of securing your back-end API or a web application. Both come with their own caveats, benefits, and drawbacks. Although Kinde supports both approaches, you will need to make your own determination on which one to choose.

Kinde’s admin interface uses back-end authentication with session management via cookies, completely hiding the tokens in the back-end. This prevents a possibility of learning the internals of the system to be later exploited, e.g. API audiences usually point to the URLs of the APIs.

Front-end auth

Link to this section

This is where you use an authorization code flow with PKCE extension on a public client, then pass the acquired access token in the authorization header to the back-end APIs.

  • Access and refresh token are exposed to the public clients, making it more challenging to secure the application. Incorrect storage of tokens can make them vulnerable to attacks.
  • Rotating refresh tokens only reduces the risk, and does not fully eliminate it.
  • Build simplicity. You will only need a user agent to implement this type of authentication.

This is where you use an authorization code flow (preferably with PKCE) on a confidential client, then maintain a session using other mechanisms, e.g. cookies.

You can still expose the access tokens to the front-end code. But this will reduce the overall security of your application.

  • Extra engineering effort, as there are more moving parts involved. You need to securely store access and refresh tokens, then maintain a user session using other mechanisms.
  • You will need to run a backend. It can be lightweight and serverless, but you still need it in this case.
  • Strong application verification on token exchange using the client ID and client secret.
  • The token exchange will happen in the server-to-server communication, making it impossible to intercept the tokens.
  • You will never expose active access or refresh tokens to the public, making it the most secure approach available.

Bonus - authenticating your APIs

Link to this section

Let’s assume at some point you need to make an authorized call to your back-end API. Kinde provides support for registering one or more APIs, enabling them for your application.

To request a token to include the API authorization, you will need to include one or multiple API audiences to your initial authorization request.

Many authentication providers limit you to use a single API audience per request. Kinde doesn’t have this limitation, so you can authorize multiple APIs in a single token. OAuth 2.0 specification expects it to be an array of strings.

For example, if you want to authorize the following APIs https://evtest.com/api, https://evtest.com/api2, you will need to add the following to your request.

&audience=https%3A%2F%2Fevtest.com%2Fapi&audience=https%3A%2F%2Fevtest.com%2Fapi2

Assuming you enable APIs for your application, this will generate the access token with the aud claim populated.

"aud": [
    "https://evtest.com/api",
    "https://evtest.com/api2"
  ]

Now, you could pass the access token to the downstream APIs, which need to validate their respective audiences.

We hope that this discussion of OAuth 2.0 and auth tokens has helped you understand what approaches are possible for authentication and authorization with Kinde. And that you now love OAuth 2.0 as much as we do.