Three lines

Uber

Developers

Client Asymmetric Key Authentication

Overview

This guide explains how to obtain Uber OAuth access tokens with asymmetric client authentication and how to generate a client_assertion JWT for the private_key_jwt authentication method.

Uber supports RS256 client assertions signed with RSA keys and ES256 client assertions signed with EC P-256 keys. Use the signing algorithm that matches the public key registered for your application.

Prerequisite

We assume you have already done the following when you are reading this page

  1. registered an account and created Uber developer application at https://developer.uber.com
  2. requested OAuth scopes for your application(if not please contact your Uber Partner Engineer or Account Executive)
  3. configured asymmetric key authentication for your application in one of these ways:
    • downloaded an asymmetric key file from your app’s Setup tab, or
    • registered a client through Dynamic Client Registration and retained the private key that matches one of the public JWKS kid values you submitted

Generate Access Token - Manual integration

It’s encouraged to conduct manual test before programmatically integrating with your production system. This section provides step-by-step guide to manually generate access token with the asymmetric key.

Example request and response

The example below generates access token with client_credentials grant type. Please refer to Supported Grant Types for more examples for other grant types such as authorization_code, refresh_token, or token_exchange.

# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'grant_type=client_credentials' \
     --data-urlencode 'client_id=${client_id}' \
     --data-urlencode 'scope=${space_delimited_scopes}' \
     --data-urlencode 'client_assertion=${client_assertion_you_generated}' \
     --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

# response
{
    "access_token": "${access_token}",
    "token_type": "Bearer",
    "expires_in": "${expires_in_seconds}",
    "scope": "${scopes_you_requested}"
}

Explanation of the request parameters

  • scope: it’s a list of OAuth scopes granted to your app delimited by space
  • client_id: it’s the client ID of the application you are authenticating
  • client_assertion: it’s a JWT to authenticate your application, please follow the tutorial below to generate it

How to generate client assertion

step 1: prepare header and payload

header:

  • alg: RS256 for an RSA key or ES256 for an EC P-256 key, String
  • typ: JWT, String
  • kid: key id from key file, String

payload:

  • iss: The client ID of the developer application, String
  • sub: The client ID of the developer application, String
  • aud: auth.uber.com, String
  • jti: A unique identifier for the JWT, String
  • iat: The issued-at time of the JWT as Unix epoch seconds, Int

❗️ The jti (JWT ID) claim must be unique for every access token request. Reusing the same jti will cause the request to be rejected. It is recommended to use high-cardinality value such as UUID to ensure uniqueness

  • exp: The expiration time of the JWT as Unix epoch seconds, Int. Use a short validity window, for example 5-15 minutes.

step 2: get the public key from the downloaded key file and convert it to PEM format. This is useful when using jwt.io to verify the signature locally. If you registered through DCR, use the public key from the same key pair you put in your JWKS.

echo "${public_key_from_key_file}" | sed 's/\\n/\n/g'

step 3: get the private key and convert it to PEM format. If you registered through DCR, use the private key that matches the JWKS kid you will put in the JWT header.

echo "${private_key_from_key_file}" | sed 's/\\n/\n/g'

step 4: generate client assertion JWT. We will use jwt.io as an example, you are welcome to use any library or tool

jwt_io_guide

Supported signing algorithms

JWT alg Key type Key requirements
RS256 RSA RSA-2048 or stronger
ES256 EC P-256 curve only

The kid in the JWT header must match the key ID for the RSA or EC public key registered on the application. If the application was registered through DCR, use the private key that matches one of the public JWKS keys you submitted.

Generate Access Token - Programmatic integration

To integrate your backend with Uber OAuth server, please refer to Authorization API and Access Token API.

This section provides code snippets that you can use in programmatic integration, please note that we do NOT provide key file I/O logic because different customers might handle I/O differently.

Golang

expand to see code snippets
import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rsa"
	"fmt"
	"time"

	"github.com/golang-jwt/jwt"
	"github.com/google/uuid"
)

func GenerateJWT(clientID string, privateKey interface{}, keyID string) (string, error) {
	now := time.Now()
	claims := jwt.MapClaims{
		"iss": clientID,
		"sub": clientID,
		"aud": "auth.uber.com",
		"jti": uuid.New().String(),
		"iat": now.Unix(),
		"exp": now.Add(15 * time.Minute).Unix(),
	}

	var signingMethod jwt.SigningMethod
	switch key := privateKey.(type) {
	case *rsa.PrivateKey:
		signingMethod = jwt.SigningMethodRS256
	case *ecdsa.PrivateKey:
		if key.Curve != elliptic.P256() {
			return "", fmt.Errorf("unsupported EC curve")
		}
		signingMethod = jwt.SigningMethodES256
	default:
		return "", fmt.Errorf("unsupported private key type")
	}

	token := jwt.NewWithClaims(signingMethod, claims)
	token.Header["kid"] = keyID

	return token.SignedString(privateKey)
}

func LoadPrivateKey(path string) (*rsa.PrivateKey, error) {
	// Implementation to load private key from file
	// ...
}

Java

expand to see code snippets
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.time.Instant;
import java.util.Date;
import java.util.UUID;

public class JWTGenerator {
    public static String generateJWT(String clientId, PrivateKey privateKey, String keyId) {
        Instant now = Instant.now();
        Algorithm algorithm;
        if (privateKey instanceof RSAPrivateKey) {
            algorithm = Algorithm.RSA256(null, (RSAPrivateKey) privateKey);
        } else if (privateKey instanceof ECPrivateKey) {
            algorithm = Algorithm.ECDSA256(null, (ECPrivateKey) privateKey);
        } else {
            throw new IllegalArgumentException("Unsupported private key type");
        }

        return JWT.create()
                .withKeyId(keyId)
                .withIssuer(clientId)
                .withSubject(clientId)
                .withAudience("auth.uber.com")
                .withJWTId(UUID.randomUUID().toString())
                .withIssuedAt(Date.from(now))
                .withExpiresAt(Date.from(now.plusSeconds(900)))
                .sign(algorithm);
    }

    public static PrivateKey loadPrivateKey(String path) throws Exception {
        // Implementation to load private key from file
        // ...
    }
}

Python

expand to see code snippets
import jwt
import uuid
from datetime import datetime, timedelta

def generate_jwt(client_id, private_key, key_id, algorithm="RS256"):
    now = datetime.utcnow()
    payload = {
        "iss": client_id,
        "sub": client_id,
        "aud": "auth.uber.com",
        "jti": str(uuid.uuid4()),
        "iat": int(now.timestamp()),
        "exp": int((now + timedelta(minutes=15)).timestamp())
    }
    headers = {
        "alg": algorithm,
        "typ": "JWT",
        "kid": key_id
    }
    return jwt.encode(payload, private_key, algorithm=algorithm, headers=headers)

def load_private_key(path):
    # Implementation to load private key from file
    # ...

For ES256, pass an EC P-256 private key and set algorithm="ES256". For RS256, pass an RSA private key and keep algorithm="RS256".

Supported Grant Types

Asymmetric key authentication is supported for these grant types:

  • client_credentials
  • authorization_code
  • refresh_token
  • urn:ietf:params:oauth:grant-type:token-exchange

This section will provide CLI examples for each grant type

Client Credentials

expand to see example
# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'grant_type=client_credentials' \
     --data-urlencode 'client_id=${client_id}' \
     --data-urlencode 'scope=${space_delimited_scopes}' \
     --data-urlencode 'client_assertion=${client_assertion_you_generated}' \
     --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

# response
{
    "access_token": "${access_token}",
    "token_type": "Bearer",
    "expires_in": "${expires_in_seconds}",
    "scope": "${scopes_you_requested}"
}

Authorization Code

expand to see example

firstly call /authorize to get the authorization code

# end user will trigger this request from their browser
https://auth.uber.com/oauth/v2/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${REDIRECT_URI}&scope=${SPACE_DELIMITED_SCOPES}

# response
HTTP/1.1 302 Found
Location: https://${REDIRECT_URI}?code=${AUTHORIZATION_CODE}

Authorization codes are single-use and expire in 10 minutes. Exchange the code for tokens exactly once. Reusing an authorization code or retrying a failed exchange with the same code will result in an invalid_grant error — restart the authorization flow to obtain a new code.

secondly call /token to exchange for access token

# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'grant_type=authorization_code' \
     --data-urlencode 'client_id=${CLIENT_ID}' \
     --data-urlencode 'redirect_uri=${REDIRECT_URI}' \
     --data-urlencode 'code=${AUTHORIZATION_CODE_FROM_ABOVE_STEP}' \
     --data-urlencode 'client_assertion=${client_assertion_you_generated}' \
     --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

# response
{
    "access_token": "${access_token}",
    "refresh_token": "${refresh_token}",
    "token_type": "Bearer",
    "expires_in": "${expires_in_seconds}",
    "scope": "${scopes_you_requested}"
}

Refresh Token

expand to see example

Prerequisite

To integrate with the refresh token flow, you need to obtain refresh token from Authorization Code response

# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'grant_type=refresh_token' \
     --data-urlencode 'client_id=${CLIENT_ID}' \
     --data-urlencode 'refresh_token=${refresh_token_value}' \
     --data-urlencode 'client_assertion=${client_assertion_you_generated}' \
     --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

# response
{
    "access_token": "${access_token}",
    "refresh_token": "${refresh_token}",
    "token_type": "Bearer",
    "expires_in": "${expires_in_seconds}",
    "scope": "${scopes_you_requested}"
}

Token Exchange

expand to see example
# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
     --data-urlencode 'client_id=${CLIENT_ID}' \
     --data-urlencode 'scope=${space_delimited_scopes}' \
     --data-urlencode 'subject_token=${id_token}' \
     --data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
     --data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' \
     --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
     --data-urlencode 'client_assertion=${jwt_assertion_you_generated}'

# response
{
    "access_token": "${jwt_token}",
    "token_type": "N_A",
    "issued_token_type": "urn:ietf:params:oauth:token-type:jwt"
}

Error Code

This section covers possible error code and message

expand to see error codes
Type Code Description
unsupported_grant_type 400 grant type is not supported
invalid_grant 400 user has no authorized client for required scopes
invalid_grant 400 code verifier failed verification
invalid_request 400 could not parse token request
invalid_request 400 code cannot be empty
invalid_request 400 missing <claim_name> claim
invalid_request 400 sub claim must be equal to iss claim
invalid_request 400 aud must be auth.uber.com
invalid_request 400 exp claim must be greater than current time
invalid_request 400 public key disabled, kid: <key_id>
invalid_request 400 public key not found, kid: <key_id>
invalid_client 401 client secret, jwt bearer and code verifier cannot be all empty for client authentication
invalid_client 401 client ID is invalid
unauthorized_client 401 the current application environment is mismatched with the OAuth server runtime environment
access_denied 403 client authentication failed because the client_id + jti already used
server_error 500 there was an unexpected error; please try again later

Uber

Developers
© 2026 Uber Technologies Inc.