From the Trenches: Who are you?

In Developer Support, a fairly common set of problems we see, especially for developers moving from the developer account into production, revolve around the implementation or lack of implementation of the Oauth/UserInfo call. The UserInfo call takes a user’s authentication token and asks DocuSign, “Who are you? What accounts do you belong to? What environments do those accounts run on?”  In your developer account, there is only one environment and most user IDs will only map to one account. Developers using developer accounts rarely make an API call on the wrong account for a user; and there is only one base URI, https://demo.docusign.net/restapi, so developers almost never point to the wrong environment or base URI for their API calls. This convenience is lost in production. Users frequently belong to several accounts, and each of those accounts may live in a different environment. In production DocuSign currently has the environments www, na2, na3, na4, ca, eu, and au, and we add environments as we grow. 

Once you have received a user access token for the eSignature API, you make a UserInfo call to learn which accounts the user is associated with and what environment those accounts run on. If you make raw REST calls, then the endpoint for production is:

GET https://account.docusign.com/oauth/userinfo

If you are coding with one of our SDKs, just call the method and include the access token:

C#:

OAuth.UserInfo userInfo = apiClient.GetUserInfo(token.access_token);

Java:

OAuth.UserInfo userInfo = apiClient.GetUserInfo(token.getAccessToken());

The JSON response gives you a great deal of data about your user, including the accounts and the base_uri, which tells you where to make API calls for the account, and also information about organizations the user belongs to if a DocuSign organization has been created.

JSON:

{
    "sub": "4799e5e9-xxxx-xxxx-xxxx-cf4713bbcacc",
    "name": "First Last",
    "given_name": "First",
    "family_name": "Last",
    "created": "2015-08-13T22:03:03.45",
    "email": "first.last@example.com",
    "accounts": [
        {
            "account_id": "a4ec37d6-xxxx-xxxx-xxxx-143885c333aa",
            "is_default": false,
            "account_name": "Example Europe Ltd",
            "base_uri": "https://eu.docusign.net"
        },
        {
            "account_id": "a4ec37d6-xxxx-xxxx-xxxx-143885c220e1",
            "is_default": true,
            "account_name": "Example Corporation",
            "base_uri": "https://na3.docusign.net",
            "organization": {
                "organization_id": "9c5fb8e1-xxxx-xxxx-xxxx-054ff8a249bf",
                "links": [
                    {
                        "rel": "self",
                        "href": "https://account.domain.example.com/organizations/9c5fb8e1-xxxx-xxxx-xxxx-054ff8a249bf"
                    }
                ]
            }
        }
    ]
}

As a developer, what you do with this data depends on the type of application you are building and what kinds of DocuSign users you are supporting. If you are coding a service application using JSON Web Token (JWT) Grant for your authentication, then you are probably dealing with one or very few users. You would primarily use the UserInfo response to verify that the user you authenticated is indeed a member of the target account, and it would tell you what base_uri to use when you make your API calls. Once you have verified a user, you can store this information in your application; there’s no need to repeat the call.

This C# code snippet demonstrates how to get a JWT user token; make the UserInfo call; get the account and base_uri to use in your API calls; set the basePath on your apiClient; and call GetEnvelope.

//Get the access token
OAuth.OAuthToken tokenInfo = apiClient.RequestJWTUserToken(integratorKey, userId, oauthBasePath, privateKeyBytes, expiresInHours, scopes);
 
//Make the UserInfo call
OAuth.UserInfo userInfo = apiClient.GetUserInfo(tokenInfo.access_token);
 
//Find the default account and the base_uri from the userInfo object
foreach (var item in userInfo.Accounts)
{
    if (item.IsDefault == "true")
    {
        accountId = item.AccountId;
        //Update the apiClient with the correct basePath 
        apiClient.SetBasePath(item.BaseUri + "/restapi");
        break;
    }
}
//Instantiate the envelopesApi using your apiClient configuration
EnvelopesApi envelopesApi = new EnvelopesApi(apiClient);
//Use envelopesApi to call DocuSign - this shows GetEnvelope
Envelope env = envelopesApi.GetEnvelope(accountId, envelopeId);

In the more complex multi-user scenario, you may be an ISV building an application intended for several unknown customers. You are using Authorization Code Grant to prompt these users for their DocuSign credentials. In this use case, the UserInfo call is even more crucial. If a user has membership in multiple accounts, you can present them an account picker in the UI. Then, once the user has selected an account, you can pull the base_uri to make API calls to the correct DocuSign environment. If your intent is to let the user choose their DocuSign accounts more than once, then you would need to refresh the UserInfo call before they select their accounts.

Many of DocuSign’s code examples, including the one above, use the shortcut of skipping asking the user and selecting for them by identifying the default account. That is viable but introduces the risk of assuming what the user wants and sending envelopes from the wrong account. When going this route, you should at least display the account name and account number so the user is aware where envelopes will be sent from.

If you fail to make the UserInfo call and identify the the account and server to hit with your API call, you will see some of the following errors:

  • USER_DOES_NOT_EXIST_IN_SYSTEM: The UserID did not identify a User in the system (because they don't exist in the server you hit)
  • USER_LACKS_MEMBERSHIP: User does not have a valid membership on this account (Because the user exists but isn't associated with that account on the server you hit)
  • USER_DOES_NOT_BELONG_TO_SPECIFIED_ACCOUNT: The specified User is not a member of the specified Account. (Ditto previous)
  • PARTNER_AUTHENTICATION_FAILED: "The specified Integrator Key was not found or is disabled. Invalid account specified for user." (The account is not on the server you hit).

We hear from quite a few developers who encounter these errors as they swap back and forth between testing on demo and testing in production.

Troubleshooting

There are a couple of possible gotchas, but they tend to be quite rare. In some cases the call to get the UserInfo will work on most of your users, but return a 404 - UserNotFound error or in JSON an empty array [] for a specific valid user. This is a known issue.

An even more rare problem we have seen is an old free account being returned as the default account. If you encounter this problem, open a support case with the same information referenced in the previous paragraph.

Conclusion

Use the UserInfo call to know who your users are, what accounts or organizations they are members of and where to send your API calls when you are impersonating a user.

Additional resources

Geoff Pfander
Author
Geoff Pfander
Senior Developer Support Engineer
Published