Write shit down, so you don't forget it.
Back to blog

How to use Open EMR v6 API Password Grant Flow

Pre-Requisites

  • Open EMR v6 installed
  • Postman or an alternative tool/app to test API calls

After I upgraded my Open EMR instance without reading the changelog (never do this) and breaking my site (this is why) due to massive changes to the way their API works I had to then spend 4 days wrapping my brain around how the new API oauth2 flows work with Open EMR v6. Luckily my app is a POC and nothing to be concerned about it being broken so I had time to work on it, the following is what I learned in my 4 days trying to get Open EMR v6 API working with my React app using the Password Grant Flow.

Enabling the API

The first thing we need to do is enable the API, to do this we go to Administration > Globals on the main menu. We then proceed to click on the Connectors link on the sidebar menu. On this screen, we need to enable a few things and add our site address in order to get the API up and running.

  • Edit your Site Address this is the URL of your Open EMR instance in my case since I was testing locally I set it to: http://localhost but in a production environment this would be your actual URL which needs to be served via https so make sure you have SSL enabled on your site.
  • Enable OpenEMR Standard REST API checkmark enabled
  • Enable OpenEMR Standard FHIR REST API checkmark enabled
  • Enable OpenEMR Patient Portal REST API (EXPERIMENTAL) checkmark enabled
  • Enable OpenEMR Patient Portal FHIR REST API (EXPERIMENTAL) checkmark enabled
  • Enable OAuth2 Password Grant (Not considered secure) On for Both Roles

Registering an API client

In order to use the API we need to register an API Client for our app that will be accessing Open EMRs data, theres a few ways to accomplish this, you can use the curl registration example they have on the API Readme if thats your thing, me I do all my API testing with Postman and will be using that for this tutorial.

So fire up postman and we are going to use the curl registration example code in postman to register our client. Based on the registration example we see in order to register an API Client we need to make a POST to the API endpoint: http://localhost/oauth2/default/registration and send it some JSON data.

In Postman make sure you set your Body to raw and paste the following JSON object:

{
    "application_type": "private",
    "redirect_uris": [
        "http://localhost:3000"
    ],
    "post_logout_redirect_uris": [
        "http://localhost:3000"
    ],
    "initiate_login_uri": "https://localhost:3000",
    "client_name": "external-web-app",
    "token_endpoint_auth_method": "client_secret_post",
    "username": "admin",
    "password": "pass",
    "scope": "openid api:oemr api:fhir api:port api:pofh user/allergy.read user/allergy.write user/appointment.read user/appointment.write user/dental_issue.read user/dental_issue.write user/document.read user/document.write user/drug.read user/encounter.read user/encounter.write user/facility.read user/facility.write user/immunization.read user/insurance.read user/insurance.write user/insurance_company.read user/insurance_company.write user/insurance_type.read user/list.read user/medical_problem.read user/medical_problem.write user/medication.read user/medication.write user/message.write user/patient.read user/patient.write user/practitioner.read user/practitioner.write user/prescription.read user/procedure.read user/soap_note.read user/soap_note.write user/surgery.read user/surgery.write user/vital.read user/vital.write user/AllergyIntolerance.read user/CareTeam.read user/Condition.read user/Encounter.read user/Immunization.read user/Location.read user/Medication.read user/MedicationRequest.read user/Observation.read user/Organization.read user/Organization.write user/Patient.read user/Patient.write user/Practitioner.read user/Practitioner.write user/PractitionerRole.read user/Procedure.read patient/encounter.read patient/patient.read patient/Encounter.read patient/Patient.read"
}

In this example I added all the API scopes since I wanted to test and make sure everything works but in the real world you want to only assign the required scopes to your API Client for security purposes. Make sure you specify a client_name for your API Client, for easier identification I use the name of the app I am connecting to Open EMR. Also of note make sure you use the proper username, password for the respective account you want to register.

Click send and wait for the API response.

You should get a response similar to this:

{
  "client_id":"Wy4c-6ZXlUE1YB7yoW1YcL1IZ1MQWRwFH3Q49itRtnw",
  "client_secret":"BPdPWQZbLMj_qyjmpLHUY6mWPvj-YsgG_wklT9bO1m2xP7VbkMa5edUzuwZrmDmBrmDtG93bvjFtsZKTtBeLRQ",
  "registration_access_token":"pu6OnFowFkDAdw9oNyFyDW2_ACsHmJlbHldRetpeUcQ",
  "registration_client_uri":"http:\/\/localhost\/oauth2\/default\/client\/FxEp5ADJTFYMHC9lsfG4UQ",
  "client_id_issued_at":1611675409,
  "client_secret_expires_at":0,
  "client_role":"user",
  "application_type":"private",
  "client_name":"external-web-app",
  "redirect_uris":["https:\/\/localhost:3000"],
  "post_logout_redirect_uris":["https:\/\/localhost:3000"],
  "token_endpoint_auth_method":"client_secret_post",
  "initiate_login_uri":"https:\/\/localhost:3000",
  "scope":"openid api:oemr api:fhir api:port api:pofh user\/allergy.read user\/allergy.write user\/appointment.read
  user\/appointment.write user\/dental_issue.read user\/dental_issue.write user\/document.read user\/document.write
  user\/drug.read user\/encounter.read user\/encounter.write user\/facility.read user\/facility.write
  user\/immunization.read user\/insurance.read user\/insurance.write user\/insurance_company.read
  user\/insurance_company.write user\/insurance_type.read user\/list.read user\/medical_problem.read
  user\/medical_problem.write user\/medication.read user\/medication.write user\/message.write user\/patient.read
  user\/patient.write user\/practitioner.read user\/practitioner.write user\/prescription.read user\/procedure.read
  user\/soap_note.read user\/soap_note.write user\/surgery.read user\/surgery.write user\/vital.read user\/vital.write
  user\/AllergyIntolerance.read user\/CareTeam.read user\/Condition.read user\/Encounter.read user\/Immunization.read
  user\/Location.read user\/Medication.read user\/MedicationRequest.read user\/Observation.read user\/Organization.read
  user\/Organization.write user\/Patient.read user\/Patient.write user\/Practitioner.read user\/Practitioner.write
  user\/PractitionerRole.read user\/Procedure.read patient\/encounter.read patient\/patient.read patient\/Encounter.read
  patient\/Patient.read"
}

Activating your API Client

Head back to your Open EMR instance and click on Administrations > System > API Clients you will see the API Client you registered which should be in a disabled state, every API Client is disabled by default so we need to enable it.

In order to do so click on Edit and click on Enable Client

Your API Client is now ready to access Open EMRs API.

Authenticating with Password Grant flow

Since my React app does not use a database I use Open EMR as it’s database/backend server I require the use of Password Grant flow which is similar to how we accessed the API prior to v6. Now that we enabled our API Client we can proceed to authenticate with it.

In postman open a new tab we are going to do a POST to the API endpoint: http://localhost/oauth2/default/token in the Body tab select x-www-form-urlencoded and we are going to need to add a few key/value pairs with the data the endpoint is expecting.

  • client_id insert the Client ID you got from your registration (it is also accessible via the Client Registrations screen in Open EMR)
  • grant_type = “password”
  • user_role = “users”
  • scope = Open EMR scope list you need
  • username = username of registered Open EMR user
  • password = password of the registered Open EMR user

Click send and wait for the API response.

You should get a response similar to this:

{
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJHTXlkQ1ZGNzZMUC0zT1JXYUk1ekthSUUwQzFzZW51YTAxTHVOc0tZWlZFIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0XC9vYXV0aDJcL2RlZmF1bHQiLCJpYXQiOjE2MTE2NzU1MDksImV4cCI6MTYxMTY3OTEwOSwic3ViIjoiOTI4NGI4NDMtM2RhZi00Nzg0LTk2NGEtNmZiYjA0MzUzZjk3IiwiYXBpOm9lbXIiOnRydWUsImFwaTpmaGlyIjp0cnVlLCJhcGk6cG9ydCI6dHJ1ZSwiYXBpOnBvZmgiOnRydWV9.kie0UsDzkUsqFlWYyf-U9-nVA32uLY96xxDBXyIGN6iuuR4ndGy1lqfgOuwhcaVZcoTeLxEK9_lm1xksz5ZdpMegTzNdXZcaV2F7ymETWC1z0_KE-ogPetxSBNiSIOiJ-QTzDlKDJmGO8Y9j6A16bkSfR80r2qwupTP8z5oR7fzRQ1lVrntFqZNkPlJOSM8XwrlifEUcj41-IpBIxBzwd6XX7ygdtS32LnQH0Kj5Lsav7Y4VNcXSon1Gyy61xGgG024TIs3qkrIoRe0PoomZWdI7UAxwbzLngKcP81U0g9u0-7oPzKhYPpYCR6Wxauct70AVKdAXKv3yVqKsUlf4lA",
    "scope": "openid user/allergy.read user/allergy.write user/appointment.read user/appointment.write user/dental_issue.read user/dental_issue.write user/document.read user/document.write user/drug.read user/encounter.read user/encounter.write user/facility.read user/facility.write user/immunization.read user/insurance.read user/insurance.write user/insurance_company.read user/insurance_company.write user/insurance_type.read user/list.read user/medical_problem.read user/medical_problem.write user/medication.read user/medication.write user/message.write user/patient.read user/patient.write user/practitioner.read user/practitioner.write user/prescription.read user/procedure.read user/soap_note.read user/soap_note.write user/surgery.read user/surgery.write user/vital.read user/vital.write user/AllergyIntolerance.read user/CareTeam.read user/Condition.read user/Encounter.read user/Immunization.read user/Location.read user/Medication.read user/MedicationRequest.read user/Observation.read user/Organization.read user/Organization.write user/Patient.read user/Patient.write user/Practitioner.read user/Practitioner.write user/PractitionerRole.read user/Procedure.read patient/encounter.read patient/patient.read patient/Encounter.read patient/Patient.read",
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJHTXlkQ1ZGNzZMUC0zT1JXYUk1ekthSUUwQzFzZW51YTAxTHVOc0tZWlZFIiwianRpIjoiZDY3NzQ0YzM0NDRiODYyOThiZDNhYWUyY2E5YzJlYTAyMjkwODBlMGRhMWMwNzczMzVkNzRhNmY4NDhjY2JmOGQ3YjBiNzliNTgxZjhhODciLCJpYXQiOjE2MTE2NzU1MDksIm5iZiI6MTYxMTY3NTUwOSwiZXhwIjoxNjExNjc5MTA5LCJzdWIiOiI5Mjg0Yjg0My0zZGFmLTQ3ODQtOTY0YS02ZmJiMDQzNTNmOTciLCJzY29wZXMiOlsib3BlbmlkIiwiYXBpOm9lbXIiLCJhcGk6ZmhpciIsImFwaTpwb3J0IiwiYXBpOnBvZmgiLCJ1c2VyXC9hbGxlcmd5LnJlYWQiLCJ1c2VyXC9hbGxlcmd5LndyaXRlIiwidXNlclwvYXBwb2ludG1lbnQucmVhZCIsInVzZXJcL2FwcG9pbnRtZW50LndyaXRlIiwidXNlclwvZGVudGFsX2lzc3VlLnJlYWQiLCJ1c2VyXC9kZW50YWxfaXNzdWUud3JpdGUiLCJ1c2VyXC9kb2N1bWVudC5yZWFkIiwidXNlclwvZG9jdW1lbnQud3JpdGUiLCJ1c2VyXC9kcnVnLnJlYWQiLCJ1c2VyXC9lbmNvdW50ZXIucmVhZCIsInVzZXJcL2VuY291bnRlci53cml0ZSIsInVzZXJcL2ZhY2lsaXR5LnJlYWQiLCJ1c2VyXC9mYWNpbGl0eS53cml0ZSIsInVzZXJcL2ltbXVuaXphdGlvbi5yZWFkIiwidXNlclwvaW5zdXJhbmNlLnJlYWQiLCJ1c2VyXC9pbnN1cmFuY2Uud3JpdGUiLCJ1c2VyXC9pbnN1cmFuY2VfY29tcGFueS5yZWFkIiwidXNlclwvaW5zdXJhbmNlX2NvbXBhbnkud3JpdGUiLCJ1c2VyXC9pbnN1cmFuY2VfdHlwZS5yZWFkIiwidXNlclwvbGlzdC5yZWFkIiwidXNlclwvbWVkaWNhbF9wcm9ibGVtLnJlYWQiLCJ1c2VyXC9tZWRpY2FsX3Byb2JsZW0ud3JpdGUiLCJ1c2VyXC9tZWRpY2F0aW9uLnJlYWQiLCJ1c2VyXC9tZWRpY2F0aW9uLndyaXRlIiwidXNlclwvbWVzc2FnZS53cml0ZSIsInVzZXJcL3BhdGllbnQucmVhZCIsInVzZXJcL3BhdGllbnQud3JpdGUiLCJ1c2VyXC9wcmFjdGl0aW9uZXIucmVhZCIsInVzZXJcL3ByYWN0aXRpb25lci53cml0ZSIsInVzZXJcL3ByZXNjcmlwdGlvbi5yZWFkIiwidXNlclwvcHJvY2VkdXJlLnJlYWQiLCJ1c2VyXC9zb2FwX25vdGUucmVhZCIsInVzZXJcL3NvYXBfbm90ZS53cml0ZSIsInVzZXJcL3N1cmdlcnkucmVhZCIsInVzZXJcL3N1cmdlcnkud3JpdGUiLCJ1c2VyXC92aXRhbC5yZWFkIiwidXNlclwvdml0YWwud3JpdGUiLCJ1c2VyXC9BbGxlcmd5SW50b2xlcmFuY2UucmVhZCIsInVzZXJcL0NhcmVUZWFtLnJlYWQiLCJ1c2VyXC9Db25kaXRpb24ucmVhZCIsInVzZXJcL0VuY291bnRlci5yZWFkIiwidXNlclwvSW1tdW5pemF0aW9uLnJlYWQiLCJ1c2VyXC9Mb2NhdGlvbi5yZWFkIiwidXNlclwvTWVkaWNhdGlvbi5yZWFkIiwidXNlclwvTWVkaWNhdGlvblJlcXVlc3QucmVhZCIsInVzZXJcL09ic2VydmF0aW9uLnJlYWQiLCJ1c2VyXC9Pcmdhbml6YXRpb24ucmVhZCIsInVzZXJcL09yZ2FuaXphdGlvbi53cml0ZSIsInVzZXJcL1BhdGllbnQucmVhZCIsInVzZXJcL1BhdGllbnQud3JpdGUiLCJ1c2VyXC9QcmFjdGl0aW9uZXIucmVhZCIsInVzZXJcL1ByYWN0aXRpb25lci53cml0ZSIsInVzZXJcL1ByYWN0aXRpb25lclJvbGUucmVhZCIsInVzZXJcL1Byb2NlZHVyZS5yZWFkIiwicGF0aWVudFwvZW5jb3VudGVyLnJlYWQiLCJwYXRpZW50XC9wYXRpZW50LnJlYWQiLCJwYXRpZW50XC9FbmNvdW50ZXIucmVhZCIsInBhdGllbnRcL1BhdGllbnQucmVhZCIsInNpdGU6ZGVmYXVsdCJdfQ.DD-ddIubs6CLzFU6n3hJ37tEvZ4YkaMLP8dof97xSyo-NKI3Ar3MbhlxsLbKL1eWCLh9AJxx2Bdj69Nf1WHut48dQd-471wVJR5hs0BIp7JD4fz2LgqmNfIQVKmvk4uUmeozkKfzeBGgLEleGV_Ezgs79ABRN0yAfq5qYISjrmOgWI3XW_StrexrEfutkvCg9LarliHnsWUN5uQdhGzlJCIoi_FoR3rpFCl3r64iSc2MxCOTz3sgRDj1Aq00DfWSjP7EwR3m9Ky1kXN-lJpYvjprfy9lHpmnIMldG2unyWSSe9qf9P_NgFrcIFkp-rgK2BRTlj-WlmVw9Lq9Ea0M_A",
    "refresh_token": "def50200862c8846430bf966f95595cb9af0e22582c14527da4f2e62ca966c4337f6625e3f900bdc37bd4a5dd30ae27602a05910d73b025b74ce4cb2355e45d27dae36d606892fd7a237c2b134357563d05e97cef77a0379ec5cfe9872e10df2c8673a6962cd2bba0feb02795d686b774d77b1634a70e0b44a945df1313f46b4dbb23ae08db2b88f10816dc1ca978dae18cbe497bb92f66ab179b88850a4c20b3242b3ec25fe674869a2d341ce851f0eb89aeba26fc4d69c5f0dd657a95aaac25c22fef0527bcb908b150710490f95e0081543dd5ad036f5065bcc39546595ea79018b2a77d084466bd955e9536f8e02ed47a06e006dd5c8d831eedbcc18773f8ee190abf1d226842c755dd3b1e276d14f01f74b2e46998c8b6b764b6ab19f9b30e77d5806574346582177e6bb5d1a71aa7ef5d9e939ea7f8426b3218d92df39bcac764e9259063b340123635947678a2d9af5cd4ab91ecc5b76644574717c0cf8b540a9df0d6092292cc4294a6df9725e50049cd202fe3f4de88b0fd4c1119b22234c691cb3bbc4d3793e88ee29c00744b2e22e38f53e034a20ff53bc982d7a5c1b836f1108e846fcda2b088b40f652993ff2da23eafccfb3973e312419a9622a20138a27963689f42450aa6260ebe84a892cf31fb15841873914af57fd1e2557926450e4483335c31891c706f6fcf91668acc981ab68fa213d812e8bb1601851c04a222c083b133a2fcaae1bae26c6f49f20c074a6142e2ea04cf83fd6ade52a43273f51c7a6a5adde6c7144becb46b6ed3b0030b085d105dae19d94033f1f938dbee5c90b604793f1a91ae6c499da2d1904858741a7664588d6ecb86d67364694e6f90ac34511b9a326f8db672622882e7c07e45826a6eeb7be1d54a064bb03455d554205667295304bed6fdf76b8430cd1ba77a6b26bbacdebdba922075270771ec34183d388cbfee8a9af72f2e7937ecfc93f7cdfb20a02e087ca673e1ee515eaef27db82b9addfbbd98384ee8ad5f485cf10773adbb893db51cde17ade2f38531cfa1d809a988a6682eb2f56ab9c00351c0628dd61784ee9ce3556a6d4ffe4e0dfe53841f3bf8b9d0208d6471a671e661dea12459caecd8ccd30962fc4da2e9d96e69433f019943b78d769ad9fb829931413a17db2eccccbd5df8175776def80181d2f5e89a22a7186267a3359eeafc9f41988da02a39c057f15e9d25a1b1f1af2b2e9a8feca48d3c0ffe7de1e9aeabb27d8f7565dcaa7bfbce482f15d926438183b6661feaca344856e149aaa3962a3a2f683cd7fc03ac165a0b5f7ca07ac90dbfb463a7026d1cfdb1f4abe9874196a45c27ee3bc74b743e423a3c143518d00be34633e6816c76fc3cdb888048f6e25ce3324c6fbd80870a884ad1de365a3895347a2aedb509ab77c28fbccce91861ddeaa48b460545ed74565a94ceefe6ea3768231b801be53c4bd609afddd988b955fe806bf036baed718f3f31bd6966e66883896c409adfd61fd075dc1ff085bda3463e6fdd865878e82cd3ba5677460d43b6f3cf933dc142db4739e8e3b4426f206daa7d244c4f686dd2e533b1a54d5d028e321e922b274da70afe1e34faf3e95806eff2ff0a5dc990d0b9d4f6895f8d8d5296fe91fe527a5af375f8e9b777e4639962eefe8178a986177fdfffd2440e4952a030f6676ccd1df98c404bf4eed2bdc8dc194cf144437d0845c03fd4d67f0de96f2b359eed6715a72c85224230555a32a7a0d07f78f82612a5384e0052a090c604c5f108e664b2d44f01d3ecb38a84e591670bef9afe3e4ba54bf2166201cc75fb9521b6354730d7eb7cff0727c3b14d89cf0a2894a4b312f3d2f7c47f77aefedfdc9950f4e311b486cfdb2acd383a039c7d11b5c8216f0a08bc937c0c2fa68cf9c0e4fb3c1c8e388ff7999b9fad1f6ceb27f46cfe0c77af410a3e2f38c97aa440d8f625a8638ae100d2fca76c2355d385ef2568c138c3bde5340053f261bb9835a8b7f513d7e3371ce63c32db86d2889c7b4325ff31566ff4d68b20c2da1719734a9d684c34b67048870b770335dbd1d8396ca73ec70b3d65f30d6dedf3fd23af78cae46af54924cb2ad01acdec99a22442c9c625fd0bea93d5c85ff7afd231fe1fe0391c95ee684fd8c0bf86cd56f1040ea01600b1826ebfa3a9d7c846528027e0227665e78c897017ecd60c1d053d575bae1f782edac69269eb1f65aaa38c5afdcfb8e858d4e6792890bacc9d0da5fbf406e2b224641122824b6dc00703f987d0ccf062b06a9b5bb895a7ec77995a543a32cd7a6255b1536c29b6664c2d3b2f0bdd6ad85c4fac02d451b64fbcca18d3a03098a2d2359d096a6487032b5bf477e5d09f44ab964d1a2422acf6ae76719c00361a3dfdf9ad392b1dbe699736bde1a06104aadbd5f73eca386d7883cd0b1d37a267da0f490dfcee78401cecb59e84574f56a54dd57911b9d52f9301b37646284339533a8549ccc8ab10988f37055e3e0b035815a11b6dfac76f9b97d73660909c0c141db30279a04be4d12542f93c7ceb63b0e3977120f5431bfb93f37455183f6f568f6527ccf424a302468687cbc9b676854cfa469d6d54bb27eb28ff87eb04e369c7d2f3928600ce504a2c451d8277457e5e6e57e2dcbef07aad87c92d1a098b60a75b347b3de1bfdf2b68551b24fad5712d407ce3780b4d0426ebad20acaedbcd3303ed8"
}

You know have the access_token to be able to make API requests to Open EMR.

Making API Requests

In order to make requests to Open EMRs API we need to provide the endpoints the authorization bearer token (access_token we got in the previous step). Back in postman let’s open a new tab to make our first API request.

We are going to make an API GET request to the patient endpoint: http://localhost/apis/default/api/patient to retrieve a list of all patients in our Open EMR database, before we do this we need to provide psotman the access_token as a Bearer Token. Click on the Authorization tab in postman under type make sure to select Bearer Token then paste your access_token and click Send.

You will get the list of patients in your Open EMR database 🙌 🎉