Getting an IAM token for a service account
To get an IAM token for your service account using a JWT through public /oauth/token
method:
-
Create a JSON Web Token
-
Exchange it for an IAM token.
The steps below will guide you through the process.
Create a JWT
Create a JWT manually by following the instructions or use a library for your programming language:
Tip
On jwt.io
Generate the parts that make up a JWT:
-
header
: aBase64Url
encoded JWT headers. -
payload
: aBase64Url
encoded JWT Claims Set. -
signature
: a signature generated from parts of the header and payload.
To create a JWT, join all the parts using a dot (.
) as delimiter:
header.payload.signature
The steps below will guide you through generating the JWT parts:
-
Generate the header:
A service account's JWT header must contain the following fields:
-
typ
: Token type, the value is alwaysJWT
. -
alg
: The encryption algorithm. The only supported algorithm is PS256 -
kid
: The ID of the public key obtained when creating API keys. The key must belong to the service account for which you request the IAM token.
Example:
{ "typ": "JWT", "alg": "PS256", "kid": "lfkoe35hsk58aks301nl" }
Save the result as a
Base64Url
encoded string. -
-
Generate the payload:
A service account's JWT payload must contain the following fields:
-
iss
: The ID of the service account whose key the JWT is signed with. -
sub
: The ID of the service account whose key the JWT is signed with (the same asiss
). -
aud
: The link by which an IAM token will be requested:https://auth.double.cloud/oauth/token
. -
iat
: The token issue time, in Unix timestamp -
exp
: The token expiration time, in Unix timestamp format. The expiration time must not exceed the issue time by more than one hour, meaningexp - iat ≤ 3600
.
Example:
{ "iss": "acctoah238eg7im2e8qh", "sub": "acctoah238eg7im2e8qh", "aud": "https://auth.double.cloud/oauth/token", "iat": 1676042291, "exp": 1676042651 }
Save the result as a
Base64Url
encoded string. -
-
Generate the signature:
Create a signature using the private key obtained when creating API keys. For the signature, use a string consisting of the header and payload separated by a dot (
.
):header.payload
The only supported algorithm is PS256
Save the result as a
Base64Url
encoded string.If you generate a token using jwt.io
\n
in the key value must be replaced with line breaks.
Before proceeding, make sure you have pyJWT
To install the above packages, run the following in your terminal:
pip install pyJWT cryptography
Example of creating a JWT using PyJWT:
import time
import jwt
import json
sa = {}
with open('key.json') as fh:
sa = json.load(fh)
service_account_id = sa['service_account_id']
key_id = sa['id'] # ID of the Key resource belonging to the service account.
private_key = sa['private_key']
now = int(time.time())
payload = {
'aud': 'https://auth.double.cloud/oauth/token',
'iss': service_account_id,
'sub': service_account_id,
'iat': now,
'exp': now + 360}
# JWT generation.
encoded_token = jwt.encode(
payload,
private_key,
algorithm='PS256',
headers={'kid': key_id})
print(encoded_token)
Example of creating a JWT using JJWT
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import java.io.FileReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Date;
public class JwtTest {
public static void main(String[] args) throws Exception {
PemObject privateKeyPem;
try (PemReader reader = new PemReader(new FileReader("<private_key_file>"))) {
privateKeyPem = reader.readPemObject();
}
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyPem.getContent()));
String serviceAccountId = "<service account ID>";
String keyId = "<public_key_ID>";
Instant now = Instant.now();
// JWT generation.
String encodedToken = Jwts.builder()
.setHeaderParam("kid", keyId)
.setIssuer(serviceAccountId)
.setSubject(serviceAccountId)
.setAudience("https://auth.double.cloud/oauth/token")
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plusSeconds(360)))
.signWith(privateKey, SignatureAlgorithm.PS256)
.compact();
}
}
Example of creating a JWT using jose-jwt
-
.NET 4.7+:
using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using Jose; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Security; class Program { static void Main(string[] args) { var serviceAccountId = "<service_account_ID>"; var keyId = "<public_key_ID>"; var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var headers = new Dictionary<string, object>() { { "kid", keyId } }; var payload = new Dictionary<string, object>() { { "aud", "https://auth.double.cloud/oauth/token" }, { "iss", serviceAccountId }, { "sub", serviceAccountId }, { "iat", now }, { "exp", now + 3600 } }; RsaPrivateCrtKeyParameters privateKeyParams; using (var pemStream = File.OpenText("<private_key_file>")) { privateKeyParams = new PemReader(pemStream).ReadObject() as RsaPrivateCrtKeyParameters; } using (var rsa = new RSACryptoServiceProvider()) { rsa.ImportParameters(DotNetUtilities.ToRSAParameters(privateKeyParams)); string encodedToken = Jose.JWT.Encode(payload, rsa, JwsAlgorithm.PS256, headers); } } }
-
.NET 5.0+ and .NET Core 2.2+:
using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using Jose; namespace ConsoleApp { class Program { static void Main(string[] args) { var serviceAccountId = "<service_account_ID>"; var keyId = "<public_key_ID>"; var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var headers = new Dictionary<string, object>() { { "kid", keyId } }; var payload = new Dictionary<string, object>() { { "aud", "https://auth.double.cloud/oauth/token" }, { "iss", serviceAccountId }, { "sub", serviceAccountId }, { "iat", now }, { "exp", now + 3600 } }; using (var rsa = RSA.Create()) { rsa.ImportFromPem(File.ReadAllText("<private_key_file>").ToCharArray()); string encodedToken = Jose.JWT.Encode(payload, rsa, JwsAlgorithm.PS256, headers); } } } }
Example of creating a JWT using golang-jwt
import (
"crypto/rsa"
"io/ioutil"
"time"
"github.com/golang-jwt/jwt/v4"
)
const (
keyID = "<public_key_ID>"
serviceAccountID = "<service_account_ID>"
keyFile = "<private_key_file>"
)
// JWT generation.
func signedToken() string {
claims := jwt.RegisteredClaims{
Issuer: serviceAccountID,
Subject: serviceAccountID,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Audience: []string{"https://auth.double.cloud/oauth/token"},
}
token := jwt.NewWithClaims(jwt.SigningMethodPS256, claims)
token.Header["kid"] = keyID
privateKey := loadPrivateKey()
signed, err := token.SignedString(privateKey)
if err != nil {
panic(err)
}
return signed
}
func loadPrivateKey() *rsa.PrivateKey {
data, err := ioutil.ReadFile(keyFile)
if err != nil {
panic(err)
}
rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data)
if err != nil {
panic(err)
}
return rsaPrivateKey
}
Example of creating a JWT using node-jose
var jose = require('node-jose');
var fs = require('fs');
var key = fs.readFileSync(require.resolve('<private_key_file>'));
var serviceAccountId = '<service_account_ID>';
var keyId = '<public_key_ID>';
var now = Math.floor(new Date().getTime() / 1000);
var payload = { aud: "https://auth.double.cloud/oauth/token",
iss: serviceAccountId,
sub: serviceAccountId,
iat: now,
exp: now + 3600 };
jose.JWK.asKey(key, 'pem', { kid: keyId, alg: 'PS256' })
.then(function(result) {
jose.JWS.createSign({ format: 'compact' }, result)
.update(JSON.stringify(payload))
.final()
.then(function(result) {
// result is an created JWT.
});
});
Example of creating a JWT using PHP JWT Framework
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\Converter\StandardConverter;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Algorithm\PS256;
use Jose\Component\Signature\Serializer\CompactSerializer;
$service_account_id = '<service_account_ID>';
$key_id = '<public_key_ID>';
$jsonConverter = new StandardConverter();
$algorithmManager = AlgorithmManager::create([
new PS256()
]);
$jwsBuilder = new JWSBuilder($jsonConverter, $algorithmManager);
$now = time();
$claims = [
'aud' => 'https://auth.double.cloud/oauth/token',
'iss' => $service_account_id,
'sub' => $service_account_id,
'iat' => $now,
'exp' => $now + 360
];
$header = [
'alg' => 'PS256',
'typ' => 'JWT',
'kid' => $key_id
];
$key = JWKFactory::createFromKeyFile('<private_key_file>');
$payload = $jsonConverter->encode($claims);
// Signature creation.
$jws = $jwsBuilder
->create()
->withPayload($payload)
->addSignature($key, $header)
->build();
$serializer = new CompactSerializer($jsonConverter);
// JWT generation.
$token = $serializer->serialize($jws);
Example of creating a JWT using jwt-cpp
#include <chrono>
#include <fstream>
#include <iterator>
#include "jwt-cpp/jwt.h"
int main(int argc, char *argv[])
{
std::ifstream priv_key_file("<private_key_file>");
std::ifstream pub_key_file("<public_key_file>");
auto now = std::chrono::system_clock::now();
auto expires_at = now + std::chrono::hours(1);
auto serviceAccountId = "<service_account_ID>";
auto keyId = "<public_key_ID>";
std::set<std::string> audience;
audience.insert("https://auth.double.cloud/oauth/token");
auto algorithm = jwt::algorithm::ps256(
std::string(std::istreambuf_iterator<char>{pub_key_file}, {}),
std::string(std::istreambuf_iterator<char>{priv_key_file}, {}));
// JWT generation.
auto encoded_token = jwt::create()
.set_key_id(keyId)
.set_issuer(serviceAccountId)
.set_subject(serviceAccountId)
.set_audience(audience)
.set_issued_at(now)
.set_expires_at(expires_at)
.sign(algorithm);
}
Example of creating a JWT using ruby-jwt
require 'jwt'
privateKey = OpenSSL::PKey::RSA.new(File.read('<private_key_file>'))
issuedAt = Time.now.to_i
expirationTime = issuedAt + 360
serviceAccountId = "<service_account_ID>"
# ID of the Key resource belonging to the service account.
keyId = "<public_key_ID>"
headers = { kid: keyId }
payload = {
typ: 'JWT',
aud: "https://auth.double.cloud/oauth/token",
iss: serviceAccountId,
sub: serviceAccountId,
iat: issuedAt,
exp: expirationTime,
data: 'data' }
# JWT generation.
token = JWT.encode(
payload,
privateKey,
'PS256',
headers)
Exchange the JWT for an IAM token
When exchanging the JWT for an IAM token, ensure the following conditions are met:
-
The service account and key specified in the JWT exist (they haven't been deleted).
-
The key belongs to the service account.
-
The signature is valid.
Token lifespan warning
Both JWT and IAM tokens have short lifespans by design - they're valid for 12 hours.
To avoid issues with accessing our API, make sure to request new tokens beforehand.
To get an IAM token, use the https://auth.double.cloud/oauth/token
POST-method with x-www-form-urlencoded
parameters.
This method implements Using JWTs as Authorization Grants
Sample request using cURL:
curl -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<SIGNED_JWT>' \
https://auth.double.cloud/oauth/token
Where <SIGNED_JWT>
is the JWT received in the previous step.
Pass the IAM token in your API request
To get access to the DoubleCloud API, pass the IAM token in the Authorization
header as follows:
Authorization: Bearer <your_IAM_token>
For example, grpcurl
grpcurl --format text \
-H "authorization: Bearer $IAM" \
-d 'project_id: "<your_project_id>"' transfer.api.double.cloud:443 doublecloud.transfer.v1.EndpointService/List