Client Asymmetric Key Authentication
¶ Overview
In this doc we will cover how to interact with Uber OAuth server to obtain access token and how to generate client assertion JWT using asymmetric key authentication method(based on OIDC Core 1.0 specification)
¶ Prerequisite
We assume you have already done the following when you are reading this page
- registered an account and created Uber developer application at https://developer.uber.com
- requested OAuth scopes for your application(if not please contact your Uber Partner Engineer or Account Executive)
- downloaded asymmetric key file from your app’s Setup tab
¶ 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" \
-d 'scope=${space_delimited_scopes}' \
-d 'grant_type=client_credentials' \
-d 'client_assertion=${client_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}"
}
Explanation of the request parameters
scope
: it’s a list of OAuth scopes granted to your app delimited by spaceclient_assertion
: it’s a JWT to authenticate your application, please follow the tutorial below to generate it
How to generate client assertion
header:
alg
: RS256, Stringtyp
: JWT, Stringkid
: key id from key file, String
payload:
iss
: The client ID of the developer application, Stringsub
: The client ID of the developer application, Stringaud
: auth.uber.com, Stringjti
: A unique identifier for the JWT, String (can be generated from here)exp
: The expiration time of the JWT, Int (can be generated from here, make sure use your local time)
get public key from the downloaded key file and convert it to PEM format:
echo "${public_key_from_key_file}" | sed 's/\\n/\n/g'
get private key from the downloaded key file and convert it to PEM format:
echo "${private_key_from_key_file}" | sed 's/\\n/\n/g'
We will use jwt.io to generate client assertion JWT, you are welcome to use any library or tool
IMPORTANT: client assertion is one-time-only, you need to generate a new client assertion each time you call /token endpoint. All you need to do is replace the jti claim with a new random UUID then generate a new client assertion.
¶ 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/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
expand to see code snippets
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
expand to see code snippets
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
# ...
¶ 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" \
-d 'scope=${space_delimited_scopes}' \
-d 'grant_type=client_credentials' \
-d 'client_assertion=${client_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
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}
secondly call /token to exchange for access token
# 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=${client_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
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" \
-d 'grant_type=refresh_token' \
-d 'refresh_token=${refresh_token_value}' \
-d 'client_assertion=${client_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
expand to see example
# 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 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 |