1
votes

I am using the REST API and I am implementing '02 JWT Access Token' from Docusign's Postman Collection in C#. I have the RSA keypair generated, the header and body is ready (Header.Body) in Base64 format.

long d1 = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long d2 = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 1800;

string header = "{  \"alg\": \"RS256\",  \"typ\": \"JWT\"}";
string body = "{  \"iss\": \"" + integrationKey + "\",  \"sub\": \"" + apiUsername + "\",  \"aud\": \"" + environment + "\",  \"iat\": " + d1.ToString() + ",  \"exp\": " + d2.ToString() + ",  \"scope\": \"signature impersonation\"}";
string base64 = Base64Encode(header) + "." + Base64Encode(body);

The Base64 encoder:

public string Base64Encode(string plainText)
{
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
    return System.Convert.ToBase64String(plainTextBytes);
}

I have used the following code to sign and it seems to work:

private string Sign(string message, string privateKey)
{
    try
    {
        byte[] r = Encoding.UTF8.GetBytes(message);

        StringReader strReader = new StringReader(privateKey);
        PemReader pemReader = new PemReader(strReader);
        AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
        RsaKeyParameters privateRSAKey = (RsaKeyParameters)keyPair.Private;


        ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
        sig.Init(true, privateRSAKey);
        sig.BlockUpdate(r, 0, r.Length);
        byte[] signedBytes = sig.GenerateSignature();

        return Convert.ToBase64String(signedBytes);

    }
    catch (Exception ex)
    {
        throw ex;
    }
}

I then submit. The assertion (token in the code below) is in the format 'Header.Body.Signature':

using (var request = new HttpRequestMessage(new HttpMethod("POST"), "https://" + environment + "/oauth/token"))
        {
            var contentList = new List<string>();
            contentList.Add($"assertion={token}");
            contentList.Add($"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer");

            request.Content = new StringContent(string.Join("&", contentList));
            request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
            request.Headers.Authorization = new AuthenticationHeaderValue("Basic", encodedKeys);

            HttpResponseMessage response = null;

            try
            {
                using (HttpClient client = new HttpClient())
                {
                    response = await client.SendAsync(request);
                }

                if (response != null)
                    return await response.Content.ReadAsStringAsync();
                else
                    throw new Exception();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error: " + ex.Message);
                throw ex;
            }
        }

I am getting an error when I submit, {"error":"Invalid_request"}.

More info: https://developers.docusign.com/platform/auth/jwt/jwt-get-token/

3

3 Answers

1
votes

It seems the JWT assertion is made from scratch, this might cause many problems.DocuSign recommends using a library to make the JWT assertion. This link shows this process with our SDK:  https://github.com/docusign/code-examples-csharp/blob/06c46b235c094be1346e4ddca692aa9b72a82456/launcher-csharp/Common/JWTAuth.cs

For this case there's a method you might need to try: public static (string, string, string) AuthenticateWithJWT(). This is going to use .NET Core. Or you can use a method on our SDK: RequestJWTUserToken. One of these might solve it, consider that the DocuSign Code Example Launchers should be installed and set up for using this method.

0
votes

The DocuSign OAuth JWT grant uses JWT tokens with the RS256 algorithm.

RS256 refers to the RSA Signature algorithm with SHA-256 keys.

How to create an RS256 JWT shows you how to do it. It's complicated. I recommend that you use a library function!

For most every DocuSign API method, use one of our SDKs' RequestJWTUserToken methods.

0
votes

The issue was with Base64 encode. It should be Base64Url encode, which has a slightly different format. Thankfully, its just a matter of adding '.Replace("=", "").Replace("/", "_").Replace("+", "-");'

public string Base64Encode(string plainText)
    {
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
        return System.Convert.ToBase64String(plainTextBytes).Replace("=", "").Replace("/", "_").Replace("+", "-");
    }


    private string Sign(string message, string privateKey)
    {
        try
        {
            byte[] r = Encoding.UTF8.GetBytes(message);

            StringReader strReader = new StringReader(privateKey);
            PemReader pemReader = new PemReader(strReader);
            AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
            RsaKeyParameters privateRSAKey = (RsaKeyParameters)keyPair.Private;

            ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
            sig.Init(true, privateRSAKey);
            sig.BlockUpdate(r, 0, r.Length);

            return Convert.ToBase64String(sig.GenerateSignature()).Replace("=", "").Replace("/", "_").Replace("+", "-"); 
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }