Auth0 applications aren’t just about who can log in, but how your different services talk to each other securely.

Let’s see this in action. Imagine a simple scenario: a user logs into your Single Page Application (SPA), and that SPA needs to call your backend API to fetch user data. This involves two Auth0 application types working in concert.

First, your SPA. When you create an "SPA" application in Auth0, you’re essentially telling Auth0, "This is a client-side application running in a user’s browser. It will handle user logins and redirects."

{
  "name": "MyAwesomeSPA",
  "grant_types": [
    "implicit",
    "refresh_token"
  ],
  "callbacks": [
    "https://my-awesome-spa.com/callback"
  ],
  "web_origins": [
    "https://my-awesome-spa.com"
  ],
  "allowed_logout_urls": [
    "https://my-awesome-spa.com"
  ],
  "token_endpoint_auth_method": "none"
}

The grant_types like implicit and refresh_token are crucial here. implicit is an older flow but still common for SPAs, where tokens are returned directly to the browser. refresh_token allows your SPA to get new access tokens without requiring the user to log in again. The callbacks are where Auth0 sends the user back after they authenticate – think of it as Auth0’s return address. web_origins are the domains allowed to make requests to your Auth0 tenant, preventing other sites from impersonating yours.

Next, your API. When you create an "API" application in Auth0, you’re telling Auth0, "This is a backend service that will receive requests and needs to validate tokens sent by clients."

{
  "name": "MyAwesomeAPI",
  "identifier": "https://api.my-awesome-app.com",
  "scopes": [
    {
      "value": "read:user_data",
      "description": "Allows reading user data"
    }
  ],
  "allow_offline_access": true
}

The identifier is a unique URI that your API uses to identify itself to Auth0. This is also what your SPA will request tokens for. The scopes define the permissions your API can grant. allow_offline_access is important if your API needs to issue refresh tokens.

Now, the magic happens. When a user logs into your SPA, Auth0 issues an ID token (for the user’s identity) and an access token. The SPA then uses this access token to make a request to your API.

GET /users/me Authorization: Bearer eyJhbGciOiJSUzI1NiIsImRra...

Your API, configured as an Auth0 API, receives this request. It then validates the Bearer token by calling Auth0’s introspection endpoint or by verifying the token’s signature against Auth0’s public keys. If the token is valid and contains the necessary scope (e.g., read:user_data), the API processes the request and returns the user data.

The key takeaway is that these application types define distinct roles and security flows. An SPA is a public client, meaning it cannot securely store secrets and relies on flows like implicit or authorization code with PKCE. An API is a resource server, responsible for protecting its endpoints and validating incoming tokens.

Auth0 also has Machine-to-Machine (M2M) applications. These are for scenarios where one service talks to another service without a user present. Think of your backend API needing to call a third-party service, or a scheduled job needing to access your own API.

For M2M, you create an "Machine to Machine Application" in Auth0.

{
  "name": "MyBackgroundWorker",
  "grant_types": [
    "client_credentials"
  ],
  "scopes": [
    "read:internal_data"
  ]
}

Here, the grant_types is typically client_credentials. This flow involves the machine application exchanging its client ID and client secret directly with Auth0’s token endpoint to get an access token. No user interaction, no redirects. The access token issued for an M2M application is usually scoped to represent the application itself, not an end-user.

The most surprising aspect of M2M applications is how they often lead to a false sense of security if not managed properly. Because there’s no user context, the credentials (client ID and secret) become the sole gatekeepers. If these are compromised, an attacker gains the full privileges of that machine application, often without any immediate indication of a user being involved. This means rigorous management of client secrets, including regular rotation and restricting their scope to the absolute minimum necessary, is paramount.

Understanding these distinctions is crucial for building a secure and scalable architecture. It allows you to delegate authentication and authorization appropriately, ensuring that only authorized clients and users can access your resources.

The next step in securing your architecture involves understanding token scopes and claims and how to leverage them effectively within your API logic.

Want structured learning?

Take the full Auth0 course →