4

I'm trying to implement the EPIC FHIR SMART Backend Services (Backend OAuth 2.0) on go programming language.

I've created my dev account, uploaded the public key there, and selecting the backend system as the application audience.

I'm pretty sure my jwt token is correct. I've inspected it on jwt.io, the signature is correct. However, I always get this error:

{ "error": "invalid_client", "error_description": null }

I've tried other possible solutions as well such as:

  • ensuring the expiration date within the jet claim is below 5 minutes
  • placing the payload in the body with the correct content type, which is application/x-www-form-urlencoded
  • ensuring to use the sandbox client_id
  • using the correct jwt sign in method (RS384)

What should I do to resolve this issue?

Btw, I also saw several discussions on the google groups saying that it's worth to wait for one or two days after the dev account is created.

Below is my code. Appreciate the help!

var (
    oauth2TokenUrl  = "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token"
    sandboxClientID = "..."
    privateKey      = "..."
)

// load private key
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
So(err, ShouldBeNil)

// construct jwt claims
now := time.Now()
claims := jwt.MapClaims{
    "iss": sandboxClientID,
    "sub": sandboxClientID,
    "aud": oauth2TokenUrl,
    "jti": uuid.New().String(),             // fill with reference id
    "exp": now.Add(1 * time.Minute).Unix(), // cannot be more than 5 minutes!
}
log.Info("  => claims:", utility.ToJsonString(claims))

// generate signed token using private key with RS384 algorithm
alg := jwt.SigningMethodRS384
signedToken, err := jwt.NewWithClaims(alg, claims).SignedString(signKey)
So(err, ShouldBeNil)
log.Info("  => signed token", signedToken)

// prepare api call payload
payload := map[string]string{
    "grant_type":            "client_credentials",
    "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
    "client_assertion":      signedToken,
}

// dispatch the api call
req := resty.New().
    R().
    EnableTrace().
    SetFormData(payload)
res, err := req.Post(oauth2TokenUrl)
So(err, ShouldBeNil)
log.Info("  => response status:", res.StatusCode())
log.Info("  => response header:", res.Header())
log.Info("  => response body:", string(res.Body()))

// parse response
resBody := make(map[string]interface{})
err = json.Unmarshal(res.Body(), &resBody)
So(err, ShouldBeNil)
novalagung
  • 10,905
  • 4
  • 58
  • 82

3 Answers3

10

Fantastic, I got it working now.

The solution is simply waiting! it was confusing because I can't find any explanation about this on the doc, and also the error message is not quite friendly.

in summary, after creating dev app and the public key is uploaded there, we have to wait for a few hours/days, and then the credentials will eventually be usable.

The waiting part is applied to both open epic and app orchard dev accounts.

novalagung
  • 10,905
  • 4
  • 58
  • 82
  • Is there anything else this could possibly be? We've had our app on the store for a while and I'm still running into this same error. – mochsner Sep 15 '21 at 00:57
  • which is the audience you selected? if it's a backend system then you need to upload the public key there – novalagung Sep 15 '21 at 03:20
  • This is true when testing the sandbox also? – kevlar Jan 05 '23 at 22:30
  • 1
    @kevlar yes, I experienced the issue on the sandbox env as well. it'll automatically resolved after few hours/days – novalagung Jan 06 '23 at 14:30
  • @novalagung Thanks. It's funny, I had the same issue but now when I run the exact same code on two machines, one of them works and the other fails. No clue why. One is a windows machine and another is a Mac. Still, the same jwt token is being produced and same network request is happening, but one succeeds and the other returns this error. – kevlar Jan 08 '23 at 20:46
4

It seems that Epic has some kind of synchronising mechanism which runs once a day. So waiting after account create is the only solution. Please also note that, in app settings after Endpoint URI change you also have to wait some time.

Error { "error": "invalid_client", "error_description": null } also shows up when redirect_uri param is set to something like localhost:3000.

Jaro
  • 860
  • 9
  • 21
1

I encountered this problem too. In my case, I was using "Patients" as the "Application Audience" selected for the Epic SMART on FHIR app. I was able to successfully obtain an authorization code on the test server, but when I attempted to exchange it for an access token I received "invalid_client" error message.

The mistake I made is that the redirect_uri in the HTTP POST must be an absolute URL and must match a redirect URI you have specified for your app. If the redirect URI is invalid, the resulting error message will say "invalid client" (which is misleading).

Here is a sample of the Python code I was using...

    data = {
        'grant_type': 'authorization_code',
        'code': request.GET.get('code'),
        'redirect_uri': 'http://127.0.0.1:8000/ehr_connection_complete/', # THIS MUST BE AN ABSOLUTE URL
        'client_id': '11111111-2222-3333-4444-555555555555',
    }
    response = post(url, data)

It felt odd to me that an error with the redirect_uri parameter generates an error message about invalid_client, but it's true with Epic's test FHIR server.

I hope this information helps others.

Dylan Klomparens
  • 2,853
  • 7
  • 35
  • 52