Introducing the next era of Duende IdentityServer.

Read our CEO’s announcement

OAuth 2.0 Grant Types: Which One Should You Use?

Khalid Abuhakmeh
Two blue circles

Grant types define how a client obtains tokens from an authorization server. The right choice depends on two questions: what kind of client are you building, and is a user involved? If you need foundational context on OAuth itself, start with What Is OAuth?.

What Is an OAuth 2.0 Grant Type?

A grant type is the mechanism a client uses to request an access token from the authorization server. It specifies what credentials or proof the client presents and what flow it follows to obtain that token.

The grant_type parameter in the token request identifies which mechanism is in use. For example, a token request with grant_type=client_credentials tells the authorization server the client is authenticating directly with its own credentials, while grant_type=authorization_code indicates the client is exchanging an authorization code obtained through user interaction.

The OAuth 2.0 framework (RFC 6749) defines the core grant types, and subsequent RFCs extend the framework with grants for specific scenarios.

Which Grant Type Should You Use?

Grant Type Use When Client Type User Present? RFC

Authorization Code + PKCE

Users sign in via a browser

Web apps, SPAs, mobile/desktop apps

Yes

Client Credentials

Service-to-service communication

Backend services, daemons

No

Device Authorization

Input-constrained devices

Smart TVs, CLI tools, IoT

Yes

Refresh Token

Renewing an expired access token

Any client issued a refresh token

No (at renewal time)

Token Exchange

Swapping one token for another

Microservices, delegation scenarios

Varies

Decision flowchart:

  1. Is a user signing in? → Authorization Code + PKCE
  2. Is it machine-to-machine with no user? → Client Credentials
  3. Is the device input-constrained (no browser, limited keyboard)? → Device Authorization
  4. Do you need to exchange one token for another between services? → Token Exchange

How Does Authorization Code with PKCE Work?

Authorization Code with PKCE is the recommended grant for any scenario where a user signs in. It works for web apps, SPAs, mobile apps, and desktop apps.

The flow:

  1. The client generates a random code_verifier and derives a code_challenge from it (SHA-256 hash, base64url-encoded).
  2. The client redirects the user to the authorization server's authorize endpoint, including the code_challenge and code_challenge_method=S256.
  3. The user authenticates and consents at the authorization server.
  4. The authorization server redirects back to the client with an authorization code.
  5. The client sends the code and the original code_verifier to the token endpoint.
  6. The authorization server verifies the code_verifier matches the previously received code_challenge, then issues tokens.

Why PKCE matters: Without PKCE, an attacker who intercepts the authorization code (via a malicious app or compromised redirect) can exchange it for tokens. PKCE binds the token exchange to the original client that initiated the request, because only that client knows the code_verifier. RFC 7636 defines the mechanism. PKCE is required for public clients and strongly recommended for confidential clients too.

Duende IdentityServer client configuration:

Csharp

using Duende.IdentityServer.Models;

new Client
{
    ClientId = "webapp",
    ClientName = "Interactive Web App",
    AllowedGrantTypes = GrantTypes.Code,
    RequirePkce = true,
    ClientSecrets = { new Secret("secret".Sha256()) },
    RedirectUris = { "https://localhost:5002/signin-oidc" },
    PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
    AllowedScopes = { "openid", "profile", "api1" },
    AllowOfflineAccess = true // enables refresh tokens
};

How Does the Client Credentials Grant Work?

Client Credentials is for machine-to-machine communication. No user, no browser. The client authenticates directly with its ID and secret and receives an access token representing itself (not a user).

This grant is ideal for backend services calling APIs, scheduled jobs, and any workload that runs without user interaction.

IdentityServer client configuration:

Csharp

using Duende.IdentityServer.Models;

new Client
{
    ClientId = "service",
    AllowedGrantTypes = GrantTypes.ClientCredentials,
    ClientSecrets = { new Secret("secret".Sha256()) },
    AllowedScopes = { "api1" }
};

Requesting a token from the client application:

Csharp

using IdentityModel.Client;

var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");

var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
    Address = disco.TokenEndpoint,
    ClientId = "service",
    ClientSecret = "secret",
    Scope = "api1"
});

// Use tokenResponse.AccessToken to call your protected API

How Does the Device Authorization Grant Work?

The Device Authorization Grant (RFC 8628) serves input-constrained devices: smart TVs, CLI tools, IoT devices, and anything without a convenient browser or keyboard.

The flow:

  1. The device requests a device code and user code from the authorization server.
  2. The device displays the user code and a verification URL to the user.
  3. The user opens the URL on a separate device (phone, laptop), enters the code, and authenticates.
  4. The device polls the token endpoint until the user completes authorization.
  5. The authorization server issues tokens to the device.

IdentityServer client configuration:

Csharp

using Duende.IdentityServer.Models;

new Client
{
    ClientId = "device",
    AllowedGrantTypes = GrantTypes.DeviceFlow,
    RequireClientSecret = false,
    AllowedScopes = { "openid", "profile", "api1" },
    AllowOfflineAccess = true
};

How Do Refresh Tokens Work?

Refresh tokens are not a primary login mechanism. They are issued alongside access tokens when you enable AllowOfflineAccess = true on a client. When the access token expires, the client exchanges the refresh token at the token endpoint for a new access token, without requiring user interaction.

Key configuration options in Duende IdentityServer:

  • One-time use (rotation): Set RefreshTokenUsage = TokenUsage.OneTimeOnly. Each refresh token can only be used once. The server issues a new refresh token with every refresh, invalidating the old one. This limits the damage if a refresh token is leaked.
  • Absolute expiration: The refresh token expires after a fixed duration regardless of use (AbsoluteRefreshTokenLifetime).
  • Sliding expiration: The refresh token lifetime extends each time it is used, up to the absolute limit (SlidingRefreshTokenLifetime).

For production applications, consider using Duende.AccessTokenManagement to handle refresh token lifecycle in your .NET clients. See the Duende docs on refresh tokens for all available options.

What Is Token Exchange?

Token Exchange (RFC 8693) allows one service to exchange a token it received for a new token with different scopes, audience, or subject. This is common in microservice architectures where:

  • Delegation: Service A receives a user's token and needs to call Service B on behalf of that user, but with a token scoped specifically for Service B.
  • Impersonation: A service acts as the user for downstream calls.

Token Exchange uses grant_type=urn:ietf:params:oauth:grant-type:token-exchange at the token endpoint, passing the original token as the subject_token. See the Duende IdentityServer token exchange documentation for implementation details.

Which Grant Types Are Deprecated?

Grant Status Why Deprecated Replacement

Implicit

Removed in OAuth 2.1

Tokens exposed in URL fragments, no client authentication, vulnerable to token leakage

Authorization Code + PKCE

Resource Owner Password Credentials (ROPC)

Removed in OAuth 2.1

Exposes user credentials directly to the client, defeats the purpose of OAuth delegation

Authorization Code + PKCE

Implicit was originally designed for SPAs that could not make cross-origin POST requests. Modern browsers support CORS, making Authorization Code + PKCE viable for all client types.

ROPC sends the username and password directly to the client application, which then forwards them to the authorization server. This eliminates the security boundary OAuth exists to create. If you have a legacy application using ROPC, migrate to Authorization Code + PKCE.

How Do You Configure Grant Types in Duende IdentityServer?

The AllowedGrantTypes property on the Client model controls which grants a client can use. Duende IdentityServer provides predefined constants:

Csharp

using Duende.IdentityServer.Models;

// Interactive user login
AllowedGrantTypes = GrantTypes.Code

// Machine-to-machine
AllowedGrantTypes = GrantTypes.ClientCredentials

// Input-constrained devices
AllowedGrantTypes = GrantTypes.DeviceFlow

// Multiple grants on one client (use sparingly)
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials

You can also register custom grant types for extension grants like Token Exchange. The client configuration documentation covers all options, including combining grant types and restricting which grants a client may use.

Get Started

Related posts: