Do you use or administer OpenEMR? Take the General Satisfaction Survey to help improve the product

How to configure OpenEMR 6.x with Keycloak (or any OAuth2 server)

in apache log, I have : [Tue Jan 19 15:02:55.885453 2021] [php7:notice] [pid 79] [client 172.20.0.1:52780] [2021-01-19T15:02:55.885407+00:00] OpenEMR.DEBUG: AuthorizationController->oauthAuthorizeToken() OAuthServerExceptio
n occurred {“message”:“Bad request”,“stack”:"#0 /var/www/localhost/htdocs/openemr/oauth2/authorize.php(73): OpenEMR\\RestControllers\\AuthorizationController->oauthAuthorizeToken()\n#1 {main}
"} []

Looking at the source code, I think this block is problematic in my test:

if (($this->grantType === ‘authorization_code’) && empty($_SESSION[‘csrf’])) {
// the saved session was not populated as expected
throw new OAuthServerException(‘Bad request’, 0, ‘invalid_request’, 400);
}

I wonder how to manage the notion of session in my example …

The state parameter is important, it must be at the time of authorization and then in the token request!

Here I am, thank you for the tip to activate the logs, it’s a good way to understand where the problem comes from.

Regards,

Yes the state param is used to mitigate Man In The Middle Attacks to the client. Glad that you are making headway. The next patch will include even more detailed logs for the API requests as some of the OAUTH2 Exception logs are not as detailed as we wanted them to be.

Yes that would be a good thing. Do you know if in the roadmap it is expected to be fully compliant with the HL7 smart app launch specifications and also the backend service part?

Regards,

I’m on the same boat as you trying to wrap my head around how this new API flow works and getting nowhere fast.

What method are you trying to use to authenticate? I’m trying to use the password grant unsafe method but have had no luck with that either :-/

How would one go about allowing an existing user access to the API similar to how it worked in 5.0.2?

yep,

There is definitely a learning curve. Recommend checking out the following simple oauth client to get things working:
GitHub - jumbojett/OpenID-Connect-PHP: Minimalist OpenID Connect client

Then steps are for the code request (rec using the online demo first to see how things work, since that should work on first try):

  1. Install the above client locally
  2. Use this client.php script: https://gist.github.com/bradymiller/1d3f06b3c8a2c420204f5ed28d2bd3d6
  3. Create a client via postman/curl (again, do the online demo first just to see it working https://eight.openemr.io/openemr/oauth2/default/registration):
    openemr/API_README.md at master · openemr/openemr · GitHub (ensure use the correct redirect_uris; if have client.php running locally, then would be something like https://localhost/oauth/client.php)
  4. Then place the client_id and the client_secret in the client.php script (and run it from browser). also ensure set $test to ‘non-local’ to use the online demo (note there is a local setting there that can use when then wish to try to get working on your instance).

If using the password grant method (this should only be for development purposes and not production), then do as above (step 3) in getting the client_id from online demo and then should work on first try if you hit the online demo (https://eight.openemr.io/openemr/oauth2/default/token) with (populate below request with the client_id):
openemr/API_README.md at master · openemr/openemr · GitHub

Thanks for the info @brady.miller so this is where it gets confusing I have to create a client to get a client_id then how would go about logging in again with username/password without the client_id?

Does one have to do this whole registration process all over again?

I know there’s a refresh token grant but that also requires you to know the client_id so this is where my confusion/disconnect comes from.

Also, what are these 2 screens meant to be used for?

https://localhost//interface/smart/register-app.php - I know this is meant to register a client

https://localhost/oauth2/default/provider/login - not sure what this one does tbh

Thanks for any info/clarification :pray:

hi @benmarte ,
Your app/client would store the client id (and secret when applicable) and use that when making requests.

https://localhost//interface/smart/register-app.php is used in following workflow:
openemr/API_README.md at master · openemr/openemr · GitHub

https://localhost/oauth2/default/provider/login is used in the workflow discussed above (code request with those 4 steps)

I think the docs might be missing the Authorize section example based on what was posted here: V6 Authorization and API changes afoot

  1. You do a registration request to: https://localhost/oauth2/default/registration
  2. Next step is to authorize to: https://localhost/openemrv6/oauth2/default/authorize

The API docs says to use this url: https://localhost/oauth2/default and no example.

So I registered the admin account as an API client that worked fine.
I then proceed to authorize the client via a GET/POST to: https://localhost:443/oauth2/default/authorize using these params:

{
    "client_id": "UIy5NV9K53pYus35IbXGjVuYzQOUXIDQzmF3YocU7Zs",
    "response_type": "code",
    "client_id": "LnjqojEEjFYe5j2Jp9m9UnmuxOnMg4VodEJj3yE8_OA",
    "state": "a85b870548dd8880ddb7c3192439f468fe63396f",
    "scope": "openid api:oemr api:fhir api:port api:pofh",
    "redirect_uri": "https://localhost:443"
}

And this is the result:

<br />
<b>Notice</b>:  Key file "file:///var/www/localhost/htdocs/openemr/sites/default/documents/certificates/oaprivate.key" permissions are not correct, recommend changing to 600 or 660 instead of 700 in <b>/var/www/localhost/htdocs/openemr/vendor/league/oauth2-server/src/CryptKey.php</b> on line <b>63</b><br />
{
    "error": "unsupported_grant_type",
    "error_description": "The authorization grant type is not supported by the authorization server.",
    "hint": "Check that all required parameters have been provided",
    "message": "The authorization grant type is not supported by the authorization server."
}

Changing permissions on that file does nothing, this is where I’m stuck at the moment so I’m not sure if it’s something I’m doing wrong or missing info in the API docs.

I’ve changed response_type to every response type supported based on: https://localhost/oauth2/default/.well-known/openid-configuration and get the same error.

Using the endpoint in the API docs https://localhost:443/oauth2/default doesn’t return anything but a 200 ok with an empty body.

I’m just looking to be able to use the API with the user accounts created in OEMR like it was prior to V6 since I’m just using OEMR as the backend of my app.

Thanks.

A bit of progress so far here’s what I’ve done:

  • Enabled REST API in Admin > Connectors
  • Added Site Address: https://localhost:443
  • Enabled Client SSL under Admin > Security (not sure if necessary)
  • Enabled OAuth2 Password Grant to: On for Both Roles
  • Registered my admin account as an API Client via postman on: https://localhost:443/oauth2/default/registration
    with the following:
{
    "application_type": "private",
   "redirect_uris": ["https://localhost:443"],
    "initiate_login_uri": "https://localhost:443",
   "client_name": "admin-account",
   "token_endpoint_auth_method": "client_secret_post",
   "username": "admin",
   "password": "pass",
   "scope": "openid api:oemr api:fhir api:port api:pofh"
}
  • Enabled Client under Administration > System > API Clients
  • Proceeded to refresh/authorize the client on: https://localhost:443/oauth2/default/token with the following urlencoded data:
client_id:xtk0UuP446AneUfZs5nchUn_iCQX-yV92Sh_GI8aDMQ
grant_type:password
user_role:users
scope:openid api:oemr api:fhir api:port api:pofh
username:admin
password:pass
  • Used the access token from the previous request as the Bearer token for my next request to: https://localhost:443/apis/default/api/facility which then returns:
{
    "error": "access_denied",
    "error_description": "The resource owner or authorization server denied the request.",
    "hint": "Error while decoding from JSON",
    "message": "The resource owner or authorization server denied the request."
}

That’s as far as I’ve gotten.

Hi,

Regarding the Authorization Code Grant, that is done via a oauth2 standard flow. That’s why the instructions are minimal (ie. use a oauth2 client, which I detailed an example of using one in the 4 steps several posts above) since there is a standardized process/flow for this (can learn about via google).

Regarding your password grant, just need to include the needed scopes in your scope on the client registration and password grant request. For example, the user/facility.read scope will get your call to https://localhost:443/apis/default/api/facility to work.

I was under the assumption that using api:oemr could grant access to all scopes under it the same for api:fhir etc. is that not the case?

I can register fine with all scopes but when I try to refresh the token with all scopes I get an invalid scope error.

<br />
<b>Notice</b>:  Key file "file:///var/www/localhost/htdocs/openemr/sites/default/documents/certificates/oaprivate.key" permissions are not correct, recommend changing to 600 or 660 instead of 700 in <b>/var/www/localhost/htdocs/openemr/vendor/league/oauth2-server/src/CryptKey.php</b> on line <b>63</b><br />
{
    "error": "invalid_scope",
    "error_description": "The requested scope is invalid, unknown, or malformed",
    "hint": "Check the `user/Coverage.read` scope",
    "message": "The requested scope is invalid, unknown, or malformed"
}

I’ve tried escaping / and periods but no matter what I always get this error when trying to refresh the token.

Thanks for your help and patience :pray:

Hi,

From experience, the scope of the resource must be clearly specified at the time of registering and authorize. In your example, add user/Coverage.read to your scope.

I hope this will help you,

Regards,

Yeah I removed it from the scope and it worked maybe it was no included in the register phase :thinking: thanks.

It definitely was in the registration scope, weird anyways progress now to see if I can get this working, thanks again.

Hi there,

I can surely help with what all you need.

Reach me at nicole(at)cisinlabs(com)

With regards,
Nciole

Hi @benmarte ,
The user/Coverage.read scope is only in master (ie. dev version) since it was just recently added. If you are using 6.0.0, then it will fail if try to use that scope since it does not exist there. If doing shotgun approach on scopes (ie. including them all) while getting them to work and testing them out, then would use the listing of scopes in the API_README.md file included in the instance you are running from to avoid trying to use scopes that do not yet exists there.

1 Like

Ok, so I decided to write down a blog post for any other poor soul trying to use the new Open EMR Auth flows, specifically the Password Grant flow.

Check it out here and let me know what can be improved or is wrong so I can fix it, thank you everyone for your help :pray:

https://benmarte.com/blog/openemr-api-v6/

1 Like

@benmarte ,

Very cool stuff! Only 2 things I noted:

  1. https is required for oauth/api to work (when set the Site Address in globals)
  2. The Activating your API Client is not needed in this setting (it will work without doing this; this is only pertinent for use of SMART app use)
1 Like