# Entra ID reference

## Spec

For all possible configuration options, see the [📚 Nais application reference](/workloads/application/reference/spec.md#azure).

## Runtime variables & credentials

Your application will automatically be injected with the following environment variables at runtime.

| Environment Variable | Description |
| --- | --- |
| `NAIS_TOKEN_ENDPOINT` | Used to [🎯 consume an API as an application](/auth/entra-id/how-to/consume-m2m.md). |
| `NAIS_TOKEN_EXCHANGE_ENDPOINT` | Used to [🎯 consume an API on behalf of an employee](/auth/entra-id/how-to/consume-obo.md). |
| `NAIS_TOKEN_INTROSPECTION_ENDPOINT` | Used to [🎯 secure your API](/auth/entra-id/how-to/secure.md) or [🎯 log in employees](/auth/entra-id/how-to/login.md). |

For further details about these endpoints, see the [OpenAPI specification](/auth/reference.md#openapi-specification).

### Variables for manually validating tokens

These variables are optional and should only be used for [manually validating tokens](#manual-token-validation) when:

- 🎯 [securing your API with Entra ID](/auth/entra-id/how-to/secure.md), or
- 🎯 [logging in employees with Entra ID](/auth/entra-id/how-to/login.md)

| Name | Description |
| :--- | :--- |
| `AZURE_APP_CLIENT_ID` | [Client ID](/auth/entra-id/explanations.md#client-id) that uniquely identifies the application in Entra ID. |
| `AZURE_APP_WELL_KNOWN_URL` | The well-known URL for the [metadata discovery document](/auth/explanations.md#well-known-url-metadata-document). |
| `AZURE_OPENID_CONFIG_ISSUER` | `issuer` from the [metadata discovery document](/auth/explanations.md#issuer). |
| `AZURE_OPENID_CONFIG_JWKS_URI` | `jwks_uri` from the [metadata discovery document](/auth/explanations.md#jwks-endpoint-public-keys). |

`AZURE_APP_WELL_KNOWN_URL` is optional if you're using `AZURE_OPENID_CONFIG_ISSUER` and `AZURE_OPENID_CONFIG_JWKS_URI` directly.

## Claims

Notable claims in tokens from Entra ID.
For a complete list of claims, see the [Access Token Claims Reference in Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/access-token-claims-reference).
We only use v2.0 tokens.

- **`azp` (*authorized party*)**: The [client ID](/auth/entra-id/explanations.md#client-id) of the application that requested the token (this would be your consumer).
- **`azp_name` (*authorized party name*)**: Human-readable [client name](/auth/entra-id/explanations.md#client-name) of the consumer application that requested the token.
  This complements the client ID found in the `azp` claim and is intended for display purposes only.

  Not guaranteed to be unique. Should **not** be used for authorization.
- **`groups`**: JSON array of [group identifiers](/auth/entra-id/explanations.md#group-identifier) that the user is a member of:

  ```json
  {
    "groups": [
      "43451d82-19cd-4828-918d-cbf7d5b572ec",
      "8f0bd3b2-9d3d-4f27-8543-5938db3e6a3e",
      "2d7..."
    ]
  }
  ```

  Used to implement group-based authorization logic in your application.

  **Only appears in flows where an end-user is involved**, i.e. either [login](/auth/entra-id/how-to/login.md) or [on-behalf-of](/auth/entra-id/how-to/consume-obo.md).

  In order for a group to appear in the claim, all the following conditions must be true:

  - The given user is a direct member of the group.
  - The group has been [granted access to the application](/auth/entra-id/how-to/secure.md#user-access).
- **`idtyp` (*identity type*)**: This is a special claim used to determine whether a token is a [machine-to-machine](/auth/entra-id/how-to/consume-m2m.md) (app-only) token or a [on-behalf-of](/auth/entra-id/how-to/consume-obo.md) (user) token.

  A token is a machine-to-machine token if and only if this claim exists and has the value equal to `app`.
- **`NAVident`**: Internal identifier for the employees in NAV.

  **Only appears in flows where an end-user is involved**, i.e. either [login](/auth/entra-id/how-to/login.md) or [on-behalf-of](/auth/entra-id/how-to/consume-obo.md).
- **`roles`**: JSON array of roles that the application has access to:

  ```json
  {
    "roles": [
      "access_as_application",
      "role-a",
      "role-b"
    ]
  }
  ```

  **Only appears in [machine-to-machine](/auth/entra-id/how-to/consume-m2m.md) tokens**.

  Consumers defined in the [access policy](/auth/entra-id/how-to/secure.md#application-access) are always assigned the default role named `access_as_application`.
  You can optionally define and grant them [custom roles](#custom-roles).
- **`scp` (*scope*)**: The value of this claim is a *space-separated string* that lists the scopes that the application has access to:

  ```json
  {
     "scp": "defaultaccess scope1 scope2"
  }
  ```

  **Only appears in [on-behalf-of](/auth/entra-id/how-to/consume-obo.md) tokens**.

  Consumers defined in the [access policy](/auth/entra-id/how-to/secure.md#application-access) are always assigned the default scope named `defaultaccess`.
  You can optionally define and grant them [custom scopes](#custom-scopes).

## Access policies

### Application access

Consumer applications may request tokens from Entra ID that targets your API application.
Before issuing a token, Entra ID will check that the consumer is authorized to access your API application.

Consumers are not authorized by default.
To authorize consumers, specify inbound access policies in your application's configuration:

```yaml
spec:
  azure:
    application:
      enabled: true
  accessPolicy:
    inbound:
      rules:
        - application: app-a  # same namespace and cluster

        - application: app-b  # same cluster
          namespace: other-namespace

        - application: app-c
          namespace: other-namespace
          cluster: other-cluster
```

The above configuration authorizes the following applications:

- application `app-a` running in the same namespace and same cluster as your application
- application `app-b` running in the namespace `other-namespace` in the same cluster
- application `app-c` running in the namespace `other-namespace` in the cluster `other-cluster`

### User access

When performing logins, end-users are redirected to Entra ID to authenticate themselves.
After logging in, Entra ID will check that the user is authorized to access your API application.
Unauthorized users are stopped in Entra ID during the login flow.

Consumer applications acting on behalf of a user may also request tokens from Entra ID that targets your API application.
Before issuing a token, Entra ID will check that the user is authorized to access your API application.

Users are not authorized by default.
To authorize users, specify access for either [specific groups](#groups), [all users](#all-users), or [both](#groups-and-all-users).

#### Groups

To authorize users that belong to a specific set of groups, you must do two things:

- specify the [group identifiers](/auth/entra-id/explanations.md#group-identifier). To find your group's identifier, see [finding the group identifier](/auth/entra-id/explanations.md#finding-the-group-identifier).
- set the `allowAllUsers` property to `false`

```yaml
spec:
  azure:
    application:
      enabled: true
      allowAllUsers: false
      claims:
        groups:
          - id: "<group identifier>"
```

Only *direct* members of the specified groups are authorized.
Transitive membership through nested groups is not supported.

[The `groups` claim](/auth/entra-id/reference.md#claims:~:text=groups) in JWTs will include all matching group identifiers that the user is a direct member of.

> **Warning**
> Invalid group identifiers are skipped and will not be authorized to access the application in Entra ID.
> Ensure that the identifiers are correct and that the groups exist in Entra ID.

#### All users

To authorize *all* users, set the `allowAllUsers` property to `true`:

```yaml
spec:
  azure:
    application:
      enabled: true
      allowAllUsers: true
```

In practice, the property is equivalent to specifying a set of extra groups which covers all users in the Entra ID tenant.

[The `groups` claim](/auth/entra-id/reference.md#claims:~:text=groups) in JWTs will also include these extra groups that the user is a direct member of.

#### Groups and all users

You can also combine the above two configurations to authorize both specific groups and all users:

```yaml
spec:
  azure:
    application:
      enabled: true
      allowAllUsers: true
      claims:
        groups:
          - id: "<group identifier>"
```

This has the following effects:

- All users are authorized to access your Entra ID application, i.e. through logins or on-behalf-of token requests.
- [The `groups` claim](/auth/entra-id/reference.md#claims:~:text=groups) in JWTs will include matching groups identifiers that the user is a direct member of.
  This also includes the extra groups added by the `allowAllUsers` property.

The combined configuration is useful if you want to authorize all users through Entra ID and
additionally use the `groups` claim in your application code to implement custom authorization logic.

### Fine-grained permissions

You may define custom permissions for your application in Entra ID and grant them to other consumer applications.
Permissions will appear as *claims* in the consumer's token.
Your application can then use these claims to implement custom authorization logic.

> **Warning**
> Custom permissions only apply in the context of *your own application*.
> They are *not* global permissions.

> All the following conditions must be met for the custom permission to appear:

> 1. The token is acquired by a consumer of your application.
> 2. The consumer has been granted a custom permission in your access policy definition.
> 3. The target *audience* is your application.

#### Custom scopes

A *scope* only applies to tokens acquired [on behalf of an employee](/auth/entra-id/how-to/consume-obo.md).

Applications defined in the access policy are always assigned the default scope named `defaultaccess`.

```yaml
spec:
  accessPolicy:
    inbound:
      rules:
        - application: app-a
          namespace: other-namespace
          cluster: other-cluster
          permissions:
            scopes:
              - "custom-scope"
```

The above configuration grants the application `app-a` the scope `custom-scope`.

Scopes will appear as a *space separated string* in the `scp` claim within the user's token.

> **Example decoded on-behalf-of token (click to expand)**
> ```json
> {
>   "aud": "8a5...",
>   "iss": "https://login.microsoftonline.com/.../v2.0",
>   "iat": 1624957183,
>   "nbf": 1624957183,
>   "exp": 1624961081,
>   "aio": "AXQ...",
>   "azp": "e37...",
>   "azpacr": "1",
>   "groups": [
>     "2d7..."
>   ],
>   "name": "Navnesen, Navn",
>   "oid": "15c...",
>   "preferred_username": "Navn.Navnesen@nav.no",
>   "rh": "0.AS...",
>   "scp": "custom-scope defaultaccess",
>   "sub": "6OC...",
>   "tid": "623...",
>   "uti": "i03...",
>   "ver": "2.0"
> }
> ```

#### Custom roles

A *role* only applies to tokens acquired [as an application](/auth/entra-id/how-to/consume-m2m.md) (machine-to-machine calls).

Applications defined in the access policy are always assigned the default role named `access_as_application`.

```yaml
spec:
  accessPolicy:
    inbound:
      rules:
        - application: app-a
          namespace: other-namespace
          cluster: other-cluster
          permissions:
            roles:
              - "custom-role"
```

The above configuration grants the application `app-a` the role `custom-role`.

Roles will appear in the `roles` claim as an *array of strings* within the application's token.

> **Example decoded client credentials token (click to expand)**
> ```json
> {
>   "aud": "8a5...",
>   "iss": "https://login.microsoftonline.com/.../v2.0",
>   "iat": 1624957347,
>   "nbf": 1624957347,
>   "exp": 1624961247,
>   "aio": "E2Z...",
>   "azp": "e37...",
>   "azpacr": "1",
>   "oid": "933...",
>   "rh": "0.AS...",
>   "roles": [
>     "access_as_application",
>     "custom-role"
>   ],
>   "sub": "933...",
>   "tid": "623...",
>   "uti": "kbG...",
>   "ver": "2.0"
> }
> ```

## Tenants

Available [tenants](/auth/entra-id/explanations.md#tenants) in Entra ID.

NAV has two tenants in Entra ID:

| Tenant Name | Description |
| --- | --- |
| `nav.no` | Production tenant |
| `trygdeetaten.no` | Development tenant |

> **Logging into the `trygdeetaten.no` tenant**
> The `trygdeetaten.no` tenant is used for development purposes.
> To log in to this tenant, you will need a separate account other than your personal NAV account.

> There are two types of accounts you can use:

> 1. **Synthetic test accounts**: Visit the [IDA self-service portal](https://ida.intern.nav.no/). See the [#ida](https://nav-it.slack.com/archives/CA9GVNYRE) channel on Slack for details.
> 2. **Personal account**: Visit [navikt/devuser-check](https://github.com/navikt/devuser-check/blob/main/README.md) and see the "FAQ" section. Otherwise, consult the [#tech-azure](https://nav-it.slack.com/archives/C0190RZ6HB4) channel on Slack.

See also [`.spec.azure.application.tenant`](/workloads/application/reference/spec.md#azureapplicationtenant).

## Troubleshooting

This section lists common problems and solutions.

### Missing application access policy

Your application (named `your-application` in the examples below) attempts to consume another application (named `target-application`).

When requesting a token from Entra ID, your application may receive a `400 Bad Request` with an `invalid_grant` error response and the following message:

> **AADSTS501051**:

> Application `'<client ID>'` (`<cluster>:<namespace>:<your-application>`) is not assigned to a role for the application '`api://<cluster>.<namespace>.<target-application>`'

Or the other variant:

> **AADSTS65001**:

> The user or administrator has not consented to use the application with ID '`<client ID>`' named '`<cluster>:<namespace>:<your-application>`'.

> Send an interactive authorization request for this user and resource.

> **Solution / Answer**
> - Ensure that the [scope](/auth/entra-id/explanations.md#scopes) for your request follows the correct format:

>   ```
>   api://<cluster>.<namespace>.<target-application>/.default
>   ```
> - If you're using [Texas](/auth/explanations.md#texas), the `target` parameter also accepts the following format:

>   ```
>   <cluster>:<namespace>:<target-application>
>   ```
> - Ensure that the target application has configured an inbound [access policy](/auth/entra-id/how-to/secure.md#application-access) that grants your application access to the target application.
> - If all else fails, request assistance in the `#nais` channel on Slack.

### Missing user access policy

When attempting to sign-in or perform the on-behalf-of flow, an application may receive a `400 Bad Request` with an `invalid_grant` error response and the following message:

> **AADSTS50105**:

> Your administrator has configured the application `<cluster>:<namespace>:<application>` ('`<client id>`') to block users unless they are specifically granted ('assigned') access to the application.

> The signed in user '{EmailHidden}' is blocked because they are not a direct member of a group with access, nor had access directly assigned by an administrator.

> Please contact your administrator to assign access to this application

> **Solution / Answer**
> - Ensure that the application has configured appropriate [user access policies](/auth/entra-id/how-to/secure.md#user-access).
> - Ensure that the given user is a *direct* member of any configured groups.
> - If all else fails, request assistance in the `#nais` channel on Slack.

### Tenant mismatch for signed-in user

While attempting to log in, you may receive the following error message from Entra ID:

> Selected user account does not exist in tenant '`some-tenant`' and cannot access the application '`<client-id>`' in that tenant.

> The account needs to be added as an external user in the tenant first.

> Please use a different account.

> **Solution / Answer**
> - Ensure that the user uses an account that matches your application's [tenant](/auth/entra-id/reference.md#tenants) when logging in.
> - If all else fails, request assistance in the `#nais` channel on Slack.

## Manual token validation

While we recommend using [the `NAIS_TOKEN_INTROSPECTION_ENDPOINT` endpoint for validating tokens](#runtime-variables-credentials),
you can alternatively validate tokens natively within your application.

Manual validation can be useful if you want to avoid the small overhead of an additional network call
and rather depend on a native library within your ecosystem of choice.
You should be familiar with the [auth concepts](/auth/explanations.md).

Validating a JWT involves a number of steps.
These steps are outlined and described below in a language- and framework-agnostic way.

> **Libraries for token validation**
> We recommend using a library in your language of choice to handle all the validation steps described below.
> Here are some recommended libraries:

> - [navikt/oasis](https://github.com/navikt/oasis) (JavaScript)
> - [navikt/token-support](https://github.com/navikt/token-support) (Java / Kotlin)

> Validation is also supported by many popular frameworks:

> - [Ktor](https://ktor.io/docs/server-jwt.html) (Kotlin)
> - [Spring Security](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html) (Java / Kotlin)

To validate the token, start by validating the [signature and standard time-related claims](/auth/explanations.md#token-validation).

Additionally, perform the following validations:

**Issuer validation**

Validate that the `iss` claim has a value that is equal to either:

1. the `AZURE_OPENID_CONFIG_ISSUER` environment variable, or
2. the `issuer` property from the [metadata discovery document](/auth/explanations.md#well-known-url-metadata-document).
  The document is found at the endpoint pointed to by the `AZURE_APP_WELL_KNOWN_URL` environment variable.

**Audience validation**

Validate that the `aud` claim is equal to the `AZURE_APP_CLIENT_ID` environment variable.

**Signature validation**

Validate that the token is signed with a public key published at the JWKS endpoint.
This endpoint URI can be found in one of two ways:

1. the `AZURE_OPENID_CONFIG_JWKS_URI` environment variable, or
2. the `jwks_uri` property from the metadata discovery document.
  The document is found at the endpoint pointed to by the `AZURE_APP_WELL_KNOWN_URL` environment variable.

**Claims validation**

[Other claims](#claims) may be present in the token.
Your application should validate these other claims according to your own requirements.
