Obtaining OAuth2 Access Tokens from ZTS
Access Tokens are used to authorize access to service provider resources. The Access Token contains the set of roles (identified in the token as scopes) a client belongs to for a specified domain. So, when a client wants to access a resource, this client must obtain the appropriate Access Token from ZTS and use the token in the header of the subsequent HTTP client request. If enabled, the service will also return an ID Token if requested. ID Token identifies the authenticated principal for a given Athenz Service Identity.
Support for accessing OAuth2 access/id tokens is based on Client Credentials authentication workflow as defined in RFC6749: The OAuth 2.0 Authorization Framework.
Access/ID Token Request¶
Request¶
To request an access token from ZTS Server, the client will send a POST
request with application/x-www-form-urlencoded
content-type to /oauth2/token
endpoint. The request body must contain the following parameters:
grant_type : Value MUST be set to "client_credentials"
scope : list of scopes/roles requested in the access token. The caller
can either specify to include all roles the principal has access
to in a specific domain (e.g. <domain-name>:domain) or ask for
specific roles only (e.g. <domain-name>:role.<role1>). Scopes
are separated by spaces.
To request an ID token, the scope must include 'openid' and audience
service name (e.g. <domain-name>:service.<service-name>). The domain
name in id token request match the domain name in the access token
scope.
expires_in : requested expiry time for access token in seconds
For example, when a principal requests an access token only for accessing
demo
domain and wants to include all roles it has access to in that
domain, the request would be:
POST /zts/v1/oauth2/token HTTP/1.1
Host: <zts-address>
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=demo%3Adomain
If the principal requests an access token only for accessing
demo
domain and wants to include readers
and writers
roles it has access
to in that domain, the request would be:
POST /zts/v1/oauth2/token HTTP/1.1
Host: <zts-address>
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=demo%3Arole.readers+sherpa%3Arole.writers
If the principal requests an access token along with an id token for accessing
demo
domain for backend
service and wants to include readers
and writers
roles it has access to in that domain, the request would be:
POST /zts/v1/oauth2/token HTTP/1.1
Host: <zts-address>
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=openid+demo%3Aservice.backend+demo%3Arole.readers+demo%3Arole.writers
Response¶
If the access token request is valid and authorized, the ZTS server issues an access token (and id token if requested). The response contains the following json document:
{
"access_token":"<generated-token>",
"id_token":"<generated-token>",
"token_type":"Bearer",
"expires_in":3600
}
Access Token format¶
An access token is a JSON web token (JWT) encoded in Base64 URL-encoded format that contains a header, payload, and signature. An application server can authorize the principal to access specific resources based on the scopes (roles) defined in the access token.
Access Token Header¶
The Access token header contains the Athenz ZTS Server Private key id and the algorithm used to sign the payload.
{
"alg": "ES256",
"kid": "<zts server private key id>"
}
Access Token Payload¶
The Access Token Payload contains the following claims:
ver : version of the access token
iss : token issuer
aud : the audience (the Athenz Domain name) that the access token is intended for
uid : unique identifier for the principal (same as client Id)
sub : subject of the access token (same as client Id)
iat : token issue time in seconds (Unix time)
exp : token expiry time in seconds (Unix time)
scp : array of scopes are granted to this access token. This is the list of roles that principal can assume in the audience domain
client_id : client ID (Athenz Principal) of the client that requested the access token
Here is an example of an access token that alpha.api
retrieved to access
some resource in beta
domain. The principal alpha.api
is authorized
to assume readers
and writers
roles in the beta
domain.
{
"ver": 1,
"iss": "athenz",
"aud": "beta",
"client_id": "alpha.api",
"uid": "alpha.api",
"sub": "alpha.api",
"iat": 1554491974,
"exp": 1554495574,
"scp": [
"readers",
"writers"
]
}
Access Token Validation¶
You can use any standards based JWT library to validate the access token generated by ZTS Server. You can also implement your own verifier by following the RFC7519: Section 7.2: Validating a JWT.
ID Token format¶
An ID token is a JSON web token (JWT) encoded in Base64 URL-encoded format that contains a header, payload, and signature. The ID token, if enabled, can be issued for clients to authenticate themselves along with their access token to the requested Athenz service. ZTS Server never issues ID Tokens on its own. They're always returned along with access tokens.
ID Token Header¶
The ID token header contains the Athenz ZTS Server Private key id and the algorithm used to sign the payload.
{
"alg": "ES256",
"kid": "<zts server private key id>"
}
ID Token Payload¶
The ID Token Payload contains the following claims:
ver : version of the id token
iss : token issuer
aud : the audience (the Athenz Service name) that the id token is intended for
sub : subject of the id token - athenz pricnipal name requesting the token
iat : token issue time in seconds (Unix time)
exp : token expiry time in seconds (Unix time)
auth_time: authentication time in seconds (Unix time)
Here is an example of an id token that alpha.api
retrieved for its
authorization request to beta.backend
service.
{
"ver": 1,
"iss": "athenz",
"aud": "beta.backend",
"sub": "alpha.api",
"iat": 1554491974,
"auth_time": 1554491974,
"exp": 1554495574
}
ID Token Validation¶
You can use any standards based JWT library to validate the id token generated by ZTS Server. You can also implement your own verifier by following the RFC7519: Section 7.2: Validating a JWT.
Fetching Access Tokens with Java Client Library¶
First you need to update your Java project pom.xml
file to indicate
the dependency on the Athenz zts java client library. Checkout the
Maven Central ZTS Java Client Package
page to make sure you're using the latest release version (You must use
1.8.37 or newer version of the client library).
ZTS Client Object¶
ZTS Client Library provides several constructors. The recommended approach is to use an SSL context that includes service's Athenz issued CA certificate.
ZTSClient object must be closed to release any allocated resources. ZTSClient class implements Closeable interface. However, to correctly handle auto-refresh of access tokens, the client used to fetch the token cannot be closed since the background tasks need to use the same client to refresh the access token. Our general recommendation is that you create a single ZTSClient object and use that for all requests (it is thread safe) and then close the client when your application is shutting down.
** Important **
During the shutdown of the application, ZTSClient.cancelPrefetch()
must be called to stop the timer thread that automatically fetches
and refreshes any cached tokens in the ZTS Client.
Athenz Issued CA certificate¶
You will need to provide the ZTSClient with the ZTS Server's URL and an SSLContext. The following examples will show how to generate an SSLContext
/**
* Constructs a new ZTSClient object with the given SSLContext object
* and ZTS Server Url. Default read and connect timeout values are
* 30000ms (30sec). The application can change these values by using the
* athenz.zts.client.read_timeout and athenz.zts.client.connect_timeout
* system properties. The values specified for timeouts must be in milliseconds.
* @param ztsUrl ZTS Server's URL
* @param sslContext SSLContext that includes service's private key and x.509 certificate
* for authenticating requests
*/
public ZTSClient(String ztsUrl, SSLContext sslContext);
First update your pom.xml
to include dependency on athenz-cert-refresher
package
which provides support to create a SSLContext object based on our private key
and certificate:
<dependency>
<groupId>com.yahoo.athenz</groupId>
<artifactId>athenz-cert-refresher</artifactId>
<version>VERSION-NUMBER</version>
</dependency>
The SSLContext contains a KeyRefresher with the following arguments: - The service private key - The service public key - The path of the truststore containing the CA certificate - The password for the truststore containing the CA certificate
For example:
// Create our SSL Context object based on our private key and
// certificate and jdk truststore
KeyRefresher keyRefresher = Utils.generateKeyRefresher(trustStorePath, trustStorePassword,
certPath, keyPath);
SSLContext sslContext = Utils.buildSSLContext(keyRefresher.getKeyManagerProxy(),
keyRefresher.getTrustManagerProxy());
keyRefresher.startup();
ZTSClient ztsClient = new ZTSClient(ztsUrl, sslContext);
Obtaining an Access Token¶
To obtain an Access Token, the application would use the following method
from the ZTSClient
class:
/**
* For the specified requester(user/service) return the corresponding Access Token that
* includes the list of roles that the principal has access to in the specified domain
* @param domainName name of the domain
* @param roleNames (optional) only interested in roles with these names
* @param expiryTime (optional) specifies that the returned Access must be
* at least valid for specified number of seconds. Pass 0 to use
* server default timeout.
* @return ZTS generated Access Token Response object. ZTSClientException will be thrown in case of failure
*/
public AccessTokenResponse getAccessToken(String domainName, List<String> roleNames, long expiryTime);
In the simplest case, the method only requires the caller to specify the
domain that the application will be accessing. Thus, it needs an Access
Token for that domain. For example, if the alpha.storage
service
identifier is trying to access a resource from a domain beta
, then
the API call to retrieve the access token valid for 4 hours would be the following:
AccessTokenResponse tokenResponse = null;
try {
tokenResponse = ztsClient.getAccessToken("beta", null, 14400);
} catch (ZTSClientException ex) {
// log error using ex.getCode() and ex.getMessage()
}
final String accessToken = tokenResponse.getAccess_token();
Then the client will include the retrieved Access Token value as one of its headers when submitting its request to the provider service.
Token Caching¶
The ZTS Client Library automatically caches any tokens returned by the ZTS Server, so any subsequent requests for an Access Token for the same domain are fulfilled from the local cache as opposed to connecting to the ZTS Server every time. This provides better performance by reusing the same Access Token because they’re valid for two hours by default. The client library will only return cached Access Tokens if they’re valid for at least ¼ of the requested timeout in minutes (default 30 minutes).
Least Privilege Access¶
By default, the method getAccess
returns all the roles that the
given service identity can access within a domain. The method, however,
has the roleNames
parameter that allow applications to request access tokens
for specific roles within a domain. Thus, the server need only check and
return an access token for the specified roles. For example, the service
has access to several roles within the alpha
domain but is only interested
in two roles readers
and searchers
. With this use case, the api call
would be:
AccessTokenResponse tokenResponse = null;
List<String> roles = new ArrayList<>();
roles.add("readers);
roles.add("searchers");
try {
tokenResponse = ztsClient.getAccessToken("alpha", roles, 14400);
} catch (ZTSClientException ex) {
// log error using ex.getCode() and ex.getMessage()
}
final String accessToken = tokenResponse.getAccess_token();
Command Line Utility to Fetch Access Tokens¶
Athenz team also provides a command line utility called zts-accesstoken
that can be used to fetch access/id tokens from the ZTS Server for a given domain
and/or specific roles.
$ zts-accesstoken -domain alpha.prod -service api -svc-key-file beta.api.key.pem -svc-cert-file beta.api.cert.pem -zts https://<zts-address>/zts/v1
Check out the zts-accesstoken user guide for full details.
Troubleshooting¶
When communicating with ZTS Server to obtain an Access Token, the ZTS Server will return the following 4xx error codes if it's unable to successfully process the request:
- 400 The domain name specified in the request to issue an access token is inconsistent with other scopes.
- 401 The request could not be successfully authenticated.
- 403 The service identity does not have access to any resources in the specified domain.
- 404 The domain specified in the request to issue an Access Token for does not exist.