microsoft / fhir-server

A service that implements the FHIR standard
MIT License
1.18k stars 505 forks source link

Configure Custom Identity provider #3921

Open LodewijkSioen opened 3 months ago

LodewijkSioen commented 3 months ago

I'm pretty sure I had this working at one time, but now I cannot get the custom Identity Provider to work. I have IdentityServer running in an app service. This is the token it generates:

{
  "nbf": 1718369848,
  "exp": 1718373448,
  "iss": "https://[redacted].azurewebsites.net",
  "aud": "api-m2m",
  "client_id": "3f53a72a-32ab-4e40-9ae5-08dc8c4d08cd",
  "appid": "sandbox-ls",
  "roles": "smartUser",
  "scp": "system/*.read",
  "jti": "565DA07050D5AB8058057609519D4BE5",
  "iat": 1718369848,
  "scope": "system/*.read"
}

I checked the fields a hundred times:

And I get the following answer from the FHIR service:

HTTP/1.1 403 Forbidden

{
  "resourceType": "OperationOutcome",
  "id": "28294463e393a1ffdcfc1c203d370539",
  "meta": {
    "lastUpdated": "2024-06-14T13:11:01.8220825+00:00"
  },
  "issue": [
    {
      "severity": "error",
      "code": "forbidden",
      "diagnostics": "Authorization failed."
    }
  ]
}

Which means that my settings are correct, otherwise I would have gotten a 401.

Why am I getting Forbidden? How can I troubleshoot this?

EXPEkesheth commented 3 months ago

@LodewijkSioen - Have you assigned the SMART user role ?

LodewijkSioen commented 3 months ago

As you can see in my JWT, I've put "roles": "smartUser" in there.

Mind that I'm using my own Identity Provider and EntraID as specified in the documentation. That documentation does not talk about any roles.

feordin commented 3 months ago

You are correct that the token is valid, and that is why you see a 403 instead of a 401. Perhaps you have App Insights configured for your app service? It should have more details about why the 403 was returned.

LodewijkSioen commented 3 months ago

The 403 comes from the fhir service on Azure. I don't seem to be able to configure App Insights for that.

I tried to configure it locally, but there is no appsetting for the additional Identity Provides. EDIT: what's more, there is no code in here that handles this use case...

(I need both Entra and another provider)

LodewijkSioen commented 3 months ago

So I've dug deeper and I'm pretty sure it's the _authorizationService.CheckAccess method that gives me the 403. So that means that my roles claim is not correct. @EXPEkesheth can you confirm that I correctly configure the roles claim to use it on Azure. It works locally, but perhaps the name of the role is different on Azure?

LodewijkSioen commented 3 months ago

I made a minimal demo project to show you what I've done: https://github.com/LodewijkSioen/fhir-samples/tree/main/identity-provider

EXPEkesheth commented 3 months ago

@feordin - can you please review the demo project and help ?

feordin commented 3 months ago

I will take a look at the sample and see if I can repro the issue.

feordin commented 3 months ago

I downloaded the sample (thank you for that!) and I was able to get tokens that were validated and functioning.

I did make one update to the Identity Provider to include the "roles" claim:

    public Task ValidateAsync(CustomTokenRequestValidationContext context)
    {
        if (context.Result.ValidatedRequest.RequestedScopes.Contains(Constants.Userscope))
        {
            context.Result.ValidatedRequest.ClientClaims.Add(new Claim("fhirUser", "https://example.org/Practitioner/123456798"));
        }

        context.Result.ValidatedRequest.ClientClaims.Add(new Claim("roles", "smartUser"));

With that change I get a token like this for system scope:

{
  "alg": "RS256",
  "kid": "B699F4C1E63E8F38012738C00B738BE5",
  "typ": "JWT"
}.{
  "nbf": 1719253724,
  "exp": 1719257324,
  "iss": "https://localhost:7023",
  "aud": "smart-fhir-test-audience",
  "client_id": "test",
  "azp": "smart-fhir-test-clientid",
  "roles": "smartUser",
  "scp": "system/*.read",
  "jti": "2DD4592B05BB4663EF983374144A31AD",
  "iat": 1719253724
}.[Signature]

or for user scope I get this token:

{
  "alg": "RS256",
  "kid": "B699F4C1E63E8F38012738C00B738BE5",
  "typ": "JWT"
}.{
  "nbf": 1719254168,
  "exp": 1719257768,
  "iss": "https://localhost:7023",
  "aud": "smart-fhir-test-audience",
  "client_id": "test",
  "azp": "smart-fhir-test-clientid",
  "fhirUser": "https://example.org/Practitioner/123456798",
  "roles": "smartUser",
  "scp": "user/*.read",
  "jti": "2A2A5BD7D8F83023F39AB3E580908C49",
  "iat": 1719254168
}.[Signature]

either of those tokens succeed with a 200.

To match the tokens my appsettings.json for the FHIR service has the following security configuration:

    "FhirServer": {
        "Security": {
            "Enabled": true,
            "EnableAadSmartOnFhirProxy": true,
            "Authentication": {
                "Audience": "smart-fhir-test-audience",
                "Authority": "https://localhost:7023"
            },

You can see that the Audience matches the "aud" claim in the token, and the Authority matches the "iss" claim in the token. The role matches "smartUser" which is defined in roles.jon in the FHIR service code. That will result in the proper data actions being returned by the CheckAccess method that you already mentioned.

I hope this helps!

LodewijkSioen commented 3 months ago

Thank you for looking into this. I already had it working against the OSS version of the Fhir Server, but it's against the Azure PaaS version that I cannot get it working.

LodewijkSioen commented 3 months ago

@feordin I dug deeper and I suspect that the Azure FHIR Service is configured to either:

But there is no way for me to know of investigate this. Also the fact that a specific role is needed is missing from the documentation.

EXPEkesheth commented 2 months ago

@LodewijkSioen is the issue resolved or are you looking for any further inputs?

LodewijkSioen commented 2 months ago

The issue is not resolved. If you us the instructions in my repo, then you'll see that it does not work.

Groeten, Lodewijk Sioen


From: ketki @.> Sent: Monday, July 8, 2024 1:59:23 PM To: microsoft/fhir-server @.> Cc: Lodewijk Sioen @.>; Mention @.> Subject: Re: [microsoft/fhir-server] Configure Custom Identity provider (Issue #3921)

@LodewijkSioenhttps://github.com/LodewijkSioen is the issue resolved or are you looking for any further inputs?

— Reply to this email directly, view it on GitHubhttps://github.com/microsoft/fhir-server/issues/3921#issuecomment-2214839138, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAA56XTDZUDIOUWLSF4BRUTZLLHPXAVCNFSM6AAAAABJKLPP2OVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMJUHAZTSMJTHA. You are receiving this because you were mentioned.Message ID: @.***>

feordin commented 2 months ago

@LodewijkSioen I have tried the scenario again for the Azure PaaS version. the configuration is a bit different, and it does not use the roles.json file. When using Azure Entra ID as the identity propvider we would read role infomration via Azure RBAC. For 3rd party IDPs we limit the user to only the "smart" role, and role membership is assumed. We validate the token using the appId and issuer to ensure those values match the entries in the portal. Then we check the fhirUserClaim to ensure it matches a resource in the FHIR server and determine the compartment of resources that should be made available to be read.

I published the sample identity provider code in fhir-samples repo you mentioned to an app service. Then I configured my PaaS instance with the following values:

image

I made a change to the identity provider so that it would populate the fhirUserClaim token with a valid url of a practitioner in the FHIR server:

    if (context.Result.ValidatedRequest.RequestedScopes.Contains(Constants.Userscope))
    {
        context.Result.ValidatedRequest.ClientClaims.Add(new Claim("fhirUser", "https://jaerwin-identitytest.fhir.azurehealthcareapis.com/Practitioner/123456798"));
    }

It then generated a token like this: { "alg": "RS256", "kid": "E95C1005887923B229629BDB6D5A0557", "typ": "JWT" }.{ "nbf": 1721770730, "exp": 1721774330, "iss": "https://identityserver20240722190056.azurewebsites.net", "aud": "smart-fhir-test-audience", "client_id": "test", "azp": "smart-fhir-test-clientid", "fhirUser": "https://jaerwin-identitytest.fhir.azurehealthcareapis.com/Practitioner/123456798", "roles": "smartUser", "scp": "user/*.read", "jti": "F2F7D6AA44F05FF070D3FF5A6B2FF818", "iat": 1721770730 }.[Signature]

With that token I was able to successfully read resources within the practitioner compartment.

LodewijkSioen commented 2 months ago

Ah yes, so for the user scope, you need the full url including the domain in the fhirUser claim. This is annoying since we don't want to expose the internal azure url, but I can work around that. I'de rather not leak the internal URL to a client, but at least it's hidden in inside an encoded token.

A more pressing issue is that I cannot get the system scopes to work (eg system/*.read). Are these not supported on external ID providers?

EXPEkesheth commented 1 month ago

@LodewijkSioen system scopes are not supported on external ID providers.

LodewijkSioen commented 1 month ago

That's too bad because the system scopes are what we wanted to use in our system :(