Rainer
Rainer

Reputation: 11

Created OAuth bearer token generated with jwt token is invalid for REST calls

I am new to DocuSign and the REST-API. I created a developer account and added a new "Apps and Keys" entry for my application (Authentication = Implicit Grant and RSA key pairs). The keys were stored in two seperate files.

I activated the Keys using the following URL:

http://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=_the_integration_key&redirect_uri=http://localhost

I am trying to write a JAVA application without spring security framework (or any other framework).

To read the key files I used and modified (a little bit) the functions from the DocuSign examples.

private static RSAPublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {
    File pemFile = new File(filepath);
    if (!pemFile.isFile() || !pemFile.exists()) {
        throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
    }
    PemReader reader = new PemReader(new FileReader(pemFile));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPublicKey publicKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
            publicKey = (RSAPublicKey) kf.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the public key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the public key");
        }

        return publicKey;
    } finally {
        reader.close();
    }
}

private static RSAPrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException {
    File pemFile = new File(filepath);
    if (!pemFile.isFile() || !pemFile.exists()) {
        throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
    }
    PemReader reader = new PemReader(new FileReader(pemFile));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPrivateKey privateKey = null;
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory kf = KeyFactory.getInstance(algorithm, "BC");
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
            privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        } catch (NoSuchProviderException e) {
            System.out.println("Could not reconstruct the private key, invalid provider.");
        }

        return privateKey;
    } finally {
        reader.close();
    }
}

private static RSAPrivateKey readPrivateKeyFromByteArray(byte[] privateKeyBytes, String algorithm) throws IOException {
    PemReader reader = new PemReader(new StringReader(new String(privateKeyBytes)));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPrivateKey privateKey = null;
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory kf = KeyFactory.getInstance(algorithm, "BC");
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
            privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        } catch (NoSuchProviderException e) {
            System.out.println("Could not reconstruct the private key, invalid provider.");
        }

        return privateKey;
    } finally {
        reader.close();
    }
}

To get the JWT token I used the following function:

public static String generateJWTAssertion(String publicKeyFilename, String privateKeyFilename, String oAuthBasePath, String clientId, String userId, long expiresIn) throws JWTCreationException, IOException {
    String token = null;
    if (expiresIn <= 0L) {
        throw new IllegalArgumentException("expiresIn should be a non-negative value");
    }
    if (publicKeyFilename == null || "".equals(publicKeyFilename) || privateKeyFilename == null || "".equals(privateKeyFilename) || oAuthBasePath == null || "".equals(oAuthBasePath) || clientId == null || "".equals(clientId)) {
        throw new IllegalArgumentException("One of the arguments is null or empty");
    }

    try {
        RSAPublicKey publicKey = readPublicKeyFromFile(publicKeyFilename, "RSA");
        RSAPrivateKey privateKey = readPrivateKeyFromFile(privateKeyFilename, "RSA");
        Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
        long now = System.currentTimeMillis();
        token = JWT.create()
                .withIssuer(clientId)  // integration key
                .withSubject(userId)   // null
                .withAudience(oAuthBasePath) // account-d.docusign.com
                .withNotBefore(new Date(now))
                .withExpiresAt(new Date(now + expiresIn * 1000))
                .withClaim("scope", "signature impersonation")
                .sign(algorithm);
    } catch (JWTCreationException e){
        throw e;
    } catch (IOException e) {
        throw e;
    }

    return token;
}

I checked the generated token on https://jwt.io/ and the content looks fine.

To get the bearer token I use the following code:

 public Boolean getBearer(long expiresIn) throws IOException {
    String jwtToken = JwtUtils.generateJWTAssertion(
            RESOURCES_DIR + "public.key",
            RESOURCES_DIR + "private.key",
            oAuthBasePath,
            integrationKey,
            null,
            expiresIn
    );

    OkHttpClient client = new OkHttpClient().newBuilder()
            .build();
    MediaType mediaType = MediaType.parse("text/plain");
    MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
            .addFormDataPart("assertion", jwtToken)
            .build();
    Request request = new Request.Builder()
            .url("https://" + oAuthBasePath + "/oauth/token") // https://account-d.docusign.com/oauth/token
            .method("POST", body)
            .build();
    Response response = client.newCall(request).execute();
    int responseCode = response.code();
    String responseText = response.body().string();
    Gson gson = new Gson();
    OAuthResponse oAuthResponse = gson.fromJson(responseText, OAuthResponse.class);

    if (responseCode >= 200 && responseCode <= 299) {
        bearerToken = oAuthResponse.getAccess_token();
        return true;
    }

    System.out.println("Errorcode: " + oAuthResponse.getError());
    System.out.println("Error: " + oAuthResponse.getError_description());
    return false;
}

I get the bearer token and want to use it for the following REST calls.

For example:

public void getUsers () throws IOException {
    OkHttpClient client = new OkHttpClient().newBuilder()
            .build();
    Request request = new Request.Builder()
            .url(getRestBaseUrl() +"/users") // https://demo.docusign.net/restapi/v2.1/accounts/_API_account_id/users
            .method("GET", null)
            .addHeader("Accept", "application/json")
            .addHeader("Authorization", "Bearer " + bearerToken)
            .build();
    Response response = client.newCall(request).execute();
    String responseText = response.body().string();
    System.out.println(responseText);
}

But instead of a JSON structure with the users of my developer account, I got the following response:

{"errorCode":"AUTHORIZATION_INVALID_TOKEN","message":"The access token provided is expired, revoked or malformed. Authentication for System Application failed."}

When I use the API explorer and the bearer token, I can use it for authentication (it is shown as valid), but the REST call for "users" get the same error response.

So I used the API explorer for login and the REST call works.

I used the bearer token from the API explorer and used it (as fixed entered string value) as bearer token. And the JAVA REST calls works.

So, there must be an error in generating / requesting the JWT token or bearer token.

Any idea what's wrong?

Regards,

Rainer

Upvotes: 1

Views: 413

Answers (1)

Rainer
Rainer

Reputation: 11

I found the reason.

The API username was missing.

    String jwtToken = JwtUtils.generateJWTAssertion(
        RESOURCES_DIR + "public.key",
        RESOURCES_DIR + "private.key",
        oAuthBasePath,
        integrationKey,
        "_here_the_API_username",
        expiresIn
);

After adding the username I could use the API.

Upvotes: 0

Related Questions