Search code examples
azureazure-active-directoryfhir-server-for-azure

firely - how to connect to Azue Fhir service using a C# console app


Update: I managed to solve this using the replies posted. I have appended the actual steps I used below but marked the reply that led to this as the answer.

I have a .Net 8 console app using C# and am trying to connect to an Azure Fhir service (basic add patient, get patient scenario).

The issue is that I can't find any up-to-date/accurate information on how to do so that isn't using Postman other similar approaches.

I am using the Fire.ly HL7.Fhir.R4 (5.5.1) nuget package, and have configured an App Registration in Entra, then using ClientSecretCredential to get the token as follows:

var clientSecretCredential = new ClientSecretCredential(tenantId: tenantId, clientId: applicationClientId, clientSecret: secretValue);
AccessToken accessToken = await clientSecretCredential.GetTokenAsync(new TokenRequestContext(scopes: new[] { tokenEndpointAuthority }));

I get a response back, then connect using the following:-

    var mh = new HttpClientEventHandler();
mh.OnBeforeRequest += (object sender, BeforeHttpRequestEventArgs e) =>
{
    e.RawRequest.Headers.Add("Authorization", $"Bearer {accessToken.Token}" );
};

FhirClient client = new FhirClient( endpoint: endpointAuthority, 
                                    messageHandler: mh, 
                                    settings: new FhirClientSettings { PreferredFormat = ResourceFormat.Json });

I appear to get a valid response, then construct a patient object, however, when I call the client create I get access forbidden.

client.Create<Patient>(patient)  // FhirOperationException (Forbidden) Authorization Failed.

Does anyone have a sample of C# code that gets the token and if possible, some up-to-date instructions setting up the app-registration in entra - in case I have this incorrect.

Also, when I check the access token expiry date, it appears to be set to the datetime that the request was made - is this correct?

My end process: -

  1. Create an App Registration in Entra
  2. In Entra go to Enterprise Applications and ensure the Service Principal is created (usually has the same name as the app reg)
  3. Go to the fhir service and under IAM, add a new role assignment

In step 3 what was happening was happening for me is that when clicking on 'select members', it was only displaying my current users but not any security principals. I found that I needed to paste in the name of the principal fully before it was displayed and could be selected.

Once this was done, I was able to connect my client using by using both ADAL and MSAL to connect.


Solution

  • The error usually occurs if the service principal does not have required RBAC roles under FHIR resource to perform the operation.

    I registered one Entra ID application named FHIRapp and created one client secret in it like this:

    enter image description here

    When I tried to connect Azure FHIR service without adding RBAC role to service principal, I got same error as below:

    enter image description here

    To resolve the error, make sure to assign proper RBAC role like FHIR Data Contributor to the service principal, under your Azure FHIR service like this:

    enter image description here

    When I ran the below code again now, I got the response with created patient details successfully as below:

    using Azure.Identity;
    using Azure.Core;
    using Hl7.Fhir.Model;
    using Hl7.Fhir.Rest;
    
    class Program
    {
        static async System.Threading.Tasks.Task Main(string[] args)
        {
            string endpointAuthority = "https://demofhirxxxx.azurehealthcareapis.com/";
            string tenantId = "tenantId";
            string applicationClientId = "appId";
            string secretValue = "secret";
            string tokenEndpointAuthority = "https://demofhirxxxx.azurehealthcareapis.com/.default";
    
            var clientSecretCredential = new ClientSecretCredential(tenantId: tenantId, clientId: applicationClientId, clientSecret: secretValue);
    
            try
            {
                AccessToken accessToken = await clientSecretCredential.GetTokenAsync(new TokenRequestContext(scopes: new[] { tokenEndpointAuthority }));
    
                var mh = new HttpClientEventHandler();
                mh.OnBeforeRequest += (object sender, BeforeHttpRequestEventArgs e) =>
                {
                    e.RawRequest.Headers.Add("Authorization", $"Bearer {accessToken.Token}");
                };
    
                FhirClient client = new FhirClient(endpoint: endpointAuthority,
                                                   messageHandler: mh,
                                                   settings: new FhirClientSettings { PreferredFormat = ResourceFormat.Json });
    
                var patient = new Patient()
                {
                    Name = new List<HumanName>()
                    {
                        new HumanName()
                        {
                            Given = new List<string>{"Sridevi"},
                            Family = "Machavarapu"
                        }
                    },
                    Gender = AdministrativeGender.Female,
                    BirthDate = "2000-01-27",
                    Identifier = new List<Identifier>()
                    {
                        new Identifier()
                        {
                            Value = "123456799"
                        }
                    }
                };
    
                // Create the patient
                var createdPatient = client.Create<Patient>(patient);
    
                Console.WriteLine($"Patient created with ID: {createdPatient.Id} \n " +
        $"Name: {createdPatient.Name[0].Given.FirstOrDefault()} {createdPatient.Name[0].Family} " +
        $"\n Gender: {createdPatient.Gender} \n BirthDay: {createdPatient.BirthDate}");
    
            }
            catch (AuthenticationFailedException ex)
            {
                Console.WriteLine($"Authentication failed: {ex.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
    

    Response:

    enter image description here

    References:

    Configure Azure role-based access control (Azure RBAC) for Azure API for FHIR | Microsoft

    Make Your First FHIR Client - Firely by Marten Smits