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:

  1. Create a JSON Web Token (JWT).

  2. 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 you can view the list of libraries and try generating a token manually.

Generate the parts that make up a JWT:

  • header: a Base64Url encoded JWT headers.

  • payload: a Base64Url 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:

  1. Generate the header:

    A service account's JWT header must contain the following fields:

    • typ: Token type, the value is always JWT.

    • 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.

  2. 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 as iss).

    • 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 format.

    • exp: The token expiration time, in Unix timestamp format. The expiration time must not exceed the issue time by more than one hour, meaning exp - 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.

  3. 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 , note that \n in the key value must be replaced with line breaks.

Before proceeding, make sure you have pyJWT and cryptography installed on your system.

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 to send a request via a Transfer API endpoint to doublecloud.transfer.v1.EndpointService/List method authorized with your IAM token:

grpcurl --format text \
   -H "authorization: Bearer $IAM" \
   -d 'project_id: "<your_project_id>"' transfer.api.double.cloud:443 doublecloud.transfer.v1.EndpointService/List