OpenEMR 7.0.3 OAuth2 JWT – Getting 400 invalid_client Even After Proper Setup

oauth2, jwt, api, authentication, openemr-7.0.3

We’re trying to integrate OpenEMR (v7.0.3 on Google Cloud) using OAuth2 client_credentials flow with private_key_jwt authentication. All steps were followed exactly, but we still receive a 400 Bad Request with "invalid_client" error when exchanging the JWT for a token.

1. Created JWKS and Hosted It Publicly

  • Generated RSA key pair
  • Hosted JWKS (https: //mkjwk. org/) with kid, n, e, and alg=RS256

2. Configured Public Key in OpenEMR (Google Cloud VM)

  • Added the PEM public key to the trusted keys

3. Registered a Client in OpenEMR Admin Interface

  • Confidential client with private_key_jwt
  • Set JWKS URL and public key
  • Enabled client

4. Generated JWT Assertion (Programmatically)

  • Header included correct kid and alg
  • Payload had iss, sub, aud, iat, exp
  • Signed with matching RSA private key
  • JWT validated correctly on jwt.io and compiled in https://token.dev/

5. Made Token Request via Postman & Self-Hosted Hoppscotch

  • POST to /oauth2/default/token
  • Body:
grant_type=client_credentials  
client_id=<our-client-id>  
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer  
client_assertion=<generated JWT>  

6. Result:
Always getting:

{
  "error": "invalid_client",
  "error_description": "Client authentication failed"
}


(Postman)

:white_check_mark: Checked:

  • kid matches
  • JWT signed correctly
  • iat/exp fresh and accurate
  • JWKS URL is accessible
  • aud is the token endpoint
  • Public key same across JWKS and OpenEMR trusted keys

:question: Need Help With:

  • Is there a known bug in v7.0.3 with private_key_jwt?
  • Does OpenEMR require anything non-standard in JWTs?
  • Any logs we can check or extra debug steps?
  • Are there specific aud or formatting issues that could trigger this?

We’re stuck at this final step and would greatly appreciate any guidance.

Thanks in advance!

Check out this helpful topic
Use FHIR in open EMR V7 - OpenEMR Development - OpenEMR Community

We’ve carefully followed those steps multiple times, but the issue persists. Previously, we encountered a 500 error, but after applying your suggestions, it has changed to 400/401 errors. We’d greatly appreciate it if you could kindly share the steps once again for our reference.

Allow me to give you a brief overview — we’ve deployed both versions 7.0.3 and 0.2 of OpenEMR on Google Cloud, along with Hoppscotch, to enable API integration entirely within the cloud environment. We’ve followed your steps closely to reach this stage, but we still need your support to move forward. Your assistance would be greatly appreciated

Did you actually enable the client from the OpenEMR gui page?

Turn on the API debug logs and you’ll get more information. Most likely your registration URL doesn’t match your token if you’ve enabled the client from the admin GUI interface.

What does your registration request look like?

Registration Request

Full JWT Claims
{
“iss”: “2u1abKEP0poBIJStm2Lt7Q1y9kkKLgRf83rJFx8pYTs”,
“sub”: “2u1abKEP0poBIJStm2Lt7Q1y9kkKLgRf83rJFx8pYTs”,
“aud”: “http://34.93.67.195/openemr/oauth2/default/token”,
“exp”: 1751221352,
“iat”: 1751221052,
“jti”: “xyz-123-unique”
}

JWKS URL
jwks url : http://34.93.67.195/jwks/jwks.json

Open EMR Client Registration

sorry for our late reply

Kind of difficult to diagnose an http status without the request! What does your client look like etc.?
I’ve been developing an example client and testing app for openemr but haven’t had time to finish the private client for jwt.
If there was an issue with source I’d have expected we’d see others complain.

Okay I verified our authorization server works for the client_secret_post.
I will be modifying our controllers to accept/add token_endpoint_auth_method = private_key_jwt to expand our feature for future uses.

I’ll post my client apps once I clean them up and update readme’s. I went ahead and finished the app for client_secret_post including client register feature. My register client app will create everything needed for a client secret post auth grant type including pem’s so endpoints like /Patient will succeed.

I also need to point out that when we(admin’s) respond to folks asks on the forum it is bad form to ignore us for days or even after we respond within an hour like I did today.
I advise that if this continues I personally will ignore all future forum post from you guys because I find it disrespectful to me. Sorry guys but that’s how I feel and we are very busy.

I will check this and update you by the end of today and I am really sorry and disappointed on the way how you feel about our late response. Me and my team we are 1st year college students and we are doing this as our side passion project. This week has been a very busy also for us in terms of college and hence we could not reply to you. I am very grateful for the support you are giving us and I really want this to be continued in the future. This will not happen at all in the future which we commit to you.

Okay thanks guys. I get college and all that involves but when @adunsulag and I try to help you folks on the forum concerning our API’s we set aside time for the forum to catch up and are diverting time on say ONC or modules.
So when we get delay responses to our queries we may not have the time. So nothing gets done.
Anyway I beat this horse dead and I am extremely happy you guys are here and hope we can help you in your engineering goals.

A note. I’m still testing this.

Thanks a ton Jerry, Anyway I am Sriyam (kind of the leader of team) and I am working with 2 of my college friends, Sayan and Ankit…I am grateful you guys are testing this and I am eager to know your results, I will be checking my system and I will let you know by the end of today for sure…

1 Like

If your URL path to fetch your jwks.json isn’t a valid https with a valid SSL certificate then will fail.
But you can pass the jwks in your client by populating the jwks in client and leaving the jwks_uri empty.
I’m working on providing better response messages.

1 Like

I clearly understand what you are implying but in our case this is not happening I guess.

This is the statues of our json url

Just to be more clear, we are giving the public json key set along with the url

I gave you 2 clues from my end, I hope it helps…I am also working on providing better clues.

Your keys are fine but again you can not use http. Must be https with a valid ssl certificate at least for your registered client jwks_uri.

Your best bet is to populate clients jwks and leave jwks_uri empty.
Not sure you can do that with our client create so either fix the jwks_uri to access the file using valid ssl path or create and register your client from your app.

Sometime today I’ll put up my tools to help do this.

1 Like

Thanks, that clears it up. We will go ahead and embed the JWKS directly in the client and leave the jwks uri empty for now.

Hi Jerry,
This is Sayan from the Medsync team. As you suggested, we first tried embedding the JWKS directly in the client config (leaving JWKS URI blank), but we still encountered errors. So we moved forward with setting up HTTPS and a proper domain.

Here’s a quick summary of our setup:

We configured HTTPS using DuckDNS and Certbot on our GCP-hosted OpenEMR (v7.0.2) instance (domain: https://openemr7.duckdns.org).

We generated the RSA key pair, hosted the public JWKS at https://openemr7.duckdns.org/jwks.json, and registered the API client with private_key_jwt method in OpenEMR.

The JWT is signed properly using the private key with correct iss/sub/aud claims.

When we send a POST to the token endpoint using Postman or curl with the generated client_assertion, we receive 400 or 404 errors.

Would you be able to guide us with the correct steps at this point (basically in the Postman portion) ?

Thanks again for your time and help!

How did you create your client?
Is client enabled in API Clients?

I found an issue if you create your client using the api endpoint grant type not saved when using API client registration endpoint · Issue #8547 · openemr/openemr · GitHub

You’ll need to patch you code if you want to auto create clients or use my tools I will upload to you in about an hour.

Thank you for pointing this out. Yes, we created the client using the API endpoint, and the grant type issue makes sense now. We did not realise the /registration endpoint defaults to authorization_code despite posting client_credentials . This explains the persistent authentication failures.

We will manually create the client using the OpenEMR GUI Client Registrations page to ensure the correct grant type is set. Meanwhile, we will review Issue #8547 for a backend patch in our deployment to avoid similar problems in automation.

Appreciate your quick and precise help, Jerry. Let us know once your tools are available to simplify this further. We will look into it tomorrow and update you further.