OAuth 2.0 Grant Types: Which One Should You Use?
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:
- Is a user signing in? → Authorization Code + PKCE
- Is it machine-to-machine with no user? → Client Credentials
- Is the device input-constrained (no browser, limited keyboard)? → Device Authorization
- 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:
- The client generates a random
code_verifierand derives acode_challengefrom it (SHA-256 hash, base64url-encoded). - The client redirects the user to the authorization server's authorize endpoint, including the
code_challengeandcode_challenge_method=S256. - The user authenticates and consents at the authorization server.
- The authorization server redirects back to the client with an authorization
code. - The client sends the
codeand the originalcode_verifierto the token endpoint. - The authorization server verifies the
code_verifiermatches the previously receivedcode_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 APIHow 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:
- The device requests a device code and user code from the authorization server.
- The device displays the user code and a verification URL to the user.
- The user opens the URL on a separate device (phone, laptop), enters the code, and authenticates.
- The device polls the token endpoint until the user completes authorization.
- 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.CodeAndClientCredentialsYou 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
- Duende IdentityServer Quickstarts for hands-on walkthroughs of each grant type
- Client Configuration Documentation for the full reference on
AllowedGrantTypesand related settings - Duende.AccessTokenManagement for handling token refresh and caching in your .NET clients
- Duende BFF Security Framework for securing SPAs with the Authorization Code grant on the server side
Related posts: