Three lines

Uber

Developers

Client Asymmetric Key Authentication

Overview

In this doc we will introduce how to authenticate your client in /token endpoint with asymmetric key method. This method is implemented based on the OIDC Core 1.0 specification – Client Authentication with private_key_jwt.

Benefits of using asymmetric key authentication

The well-known approach to OAuth client authentication is using client_id and client_secret. However, we also support client authentication using an asymmetric key authentication approach, which offers several advantages over the traditional method. Here are some highlights:

  • Stronger Authentication Using Asymmetric Cryptography: private_key_jwt utilizes asymmetric cryptography, which is inherently more secure. It involves a pair of keys: a private key (kept secret by the client) and a public key (shared with the authorization server). The private key signs the JWT, and the server uses the public key to verify the signature.
  • Reduced Risk of Secret Exposure: The private key never leaves the client, significantly reducing the risk of interception or leakage compared to client_secret, which must be stored and transmitted securely.
  • Non-Repudiation: Because the JWT is signed with the client’s private key, it ensures that the request genuinely originates from the client, providing non-repudiation. The client cannot deny having sent the request.
  • Key Rotation: Public keys can be openly shared, making key rotation more straightforward. The authorization server can maintain a set of public keys to validate JWTs from different time periods, facilitating smoother key rotations without downtime.

Generate public/private key pairs

  1. login to https://developer.uber.com
  2. go to your application -> click Setup on the left side panel
  3. under Authentication using Asymmetric Key section -> click Add Asymmetric Key button
  4. keep the downloaded key file in a secure place, you will need it to sign your JWT

Prepare the JWT assertion

Gather claim data for header and payload

Let’s represent the claim in a JSON format, the first section is header, the second section is payload.

// the header
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "${key_uuid_from_key_file}"
}
// the payload
{
  "iss": "${client_id}",
  "sub": "${client_id}",
  "aud": "auth.uber.com",
  "jti": "${random_generated_uuid}",
  "exp": "${token_expiry_in_epoch}"
}

Generate the signature

There are many libraries available to generate signature with your private key, just pick the one you like for your target programming language. You can also give it a try in https://jwt.io/.

Generate the JWT

The result will look like this, it is essentially base64UrlEncoded(header) + “.” + base64UrlEncoded(payload) + “.” + signature

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ

Token request and response

With the JWT ready, you can now call our /token endpoint to request access token. Asymmetric key authentication is supported for these grant types:

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

You will need to prepare the following query parameters in the token request:

  • client_assertion: value of the JWT you generated from previous step
  • client_assetion_type: MUST BE urn:ietf:params:oauth:client-assertion-type:jwt-bearer
  • grant_type: the grant type you are requesting for access token
  • scope: space delimited OAuth scope(s) that granted to your application

IMPORTANT: client assertion is one-time-only, hence you need to generate a new client assertion each time you call the /token endpoint. All you need to do is replace the jti claim with a new random UUID then generate a new client assertion.

Example of Uber OAuth request and response

Client Credentials Flow

request

curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -d 'scope=${space_delimited_scopes}' \
     -d 'grant_type=client_credentials' \
     -d 'client_assertion=${jwt_assertion_you_generated}' \
     -d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer'

response

{
    "access_token": "${access_token}",
    "token_type": "Bearer",
    "expires_in": "${expiry_in_epoch}",
    "scope": "${scopes_you_requested}"
}
Authorization Code Flow

Step 1 - /authorize request and response

# 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>

Step 2 - /token request and response

# request
curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -d 'scope=${space_delimited_scopes}' \
     -d 'grant_type=authorization_code' \
     -d 'redirect_uri=<REDIRECT_URI>' \
     -d 'code=<AUTHORIZATION_CODE_FROM_ABOVE_STEP>' \
     -d 'client_assertion=${jwt_assertion_you_generated}' \
     -d '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": "${expiry_in_epoch}",
    "scope": "${scopes_you_requested}"
}
Refresh Token Flow

Prerequisite

To integrate with the refresh token flow, you will need the refresh token obtained from the Authorization Code Flow

request

curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -d 'grant_type=refresh_token' \
     -d 'refresh_token=${refresh_token_value}' \
     -d 'client_assertion=${jwt_assertion_you_generated}' \
     -d '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": "${expiry_in_epoch}",
    "scope": "${scopes_you_requested}"
}
Token Exchange Flow

request

curl -X POST "https://auth.uber.com/oauth/v2/token" \
     -d 'scope=${space_delimited_scopes}' \
     -d 'subject_token=<ID_TOKEN>' \
     -d 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
     -d 'requested_token_type=urn:ietf:params:oauth:token-type:jwt' \
     -d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
     -d '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 Codes

Open to see the error codes
Parameter 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

Signed JWT Generation Code Snippets

Golang

import (
	"crypto/rsa"
	"github.com/golang-jwt/jwt"
	"github.com/google/uuid"
)

func GenerateJWT(clientID string, privateKey *rsa.PrivateKey, keyID string) (string, error) {
	claims := jwt.MapClaims{
		"iss": clientID,
		"sub": clientID,
		"aud": "auth.uber.com",
		"jti": uuid.New().String(),
		"exp": time.Now().Add(1 * time.Hour).Unix(),
	}

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

	return token.SignedString(privateKey)
}

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

Java

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
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, RSAPrivateKey privateKey, String keyId) {
        Instant now = Instant.now();
        return JWT.create()
                .withKeyId(keyId)
                .withIssuer(clientId)
                .withSubject(clientId)
                .withAudience("auth.uber.com")
                .withJWTId(UUID.randomUUID().toString())
                .withExpiresAt(Date.from(now.plusSeconds(3600)))
                .sign(Algorithm.RSA256(null, privateKey));
    }

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

Python

import jwt
import uuid
from datetime import datetime, timedelta

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

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

Uber

Developers
© 2023 Uber Technologies Inc.