Skip to main content

Validate the access token

When aanother care organization queries your FHIR API (or other data API), the request comes in at your resource endpoint,carries an access token shouldyour beNuts availablenode issued. Your API (or the API gateway in front of it) must check that token on every request, then decide whether to allow the HTTPquery.

Authorization

The header.Nuts Younode can validatechecks the token and tells you who is asking. Whether that party may see the data is decided by calling:your application.

Before you start

    The node has the use case's policy definition (see chapter "Configure the node for a use case"). The policy says which credentials another party must present to get a token for a scope. Without it, the node doesn't issue tokens for that scope. Your care organization is registered on the Discovery Service, so others can find your authorization server (see "Register on the Discovery Service").

    What an incoming request looks like

    For a DPoP token, the request has two headers:

    GET https://fhir.example.com/r4/Patient?identifier=http://fhir.nl/fhir/NamingSystem/bsn|123456789
    Authorization: DPoP eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...
    DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0...
    

    For a Bearer token, there's only Authorization: Bearer <token>.

    1. Introspect the token

    POST <internal Nuts interface>/internal/auth/v2/accesstoken/introspect
    Content-Type: application/x-www-form-urlencoded
    
    token=ciOiJSUzI1NiIsInR5cCI6IkpeyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...
    

    Note:Result thefor content-type of this call is application/x-www-form-urlencoded.

    And the result:360-graden-clientbeeld:

    {
      "active": true,
      "iss": "https://nuts.example.com/oauth2/other_subject_identifier"careorg-a-acc",
      "client_id": "https://example.com/nuts.other.example/oauth2/my_subject_identifier"careorg-b",
      "scope": "coffee"360-graden",
      "exp": 1793110000,
      "cnf": { "jkt": "jlkhnp87453slfhansdhf" },
      "organization_ura": "87654321",
      "organization_name": "CareZorgorganisatie Bears"B",
      "organization_city": "Utrecht",
      "employee_name": "J. de Vries",
      "employee_role": "Verpleegkundige",
      "employee_identifier": "123456"
    }
    
      If active is false, reject the request with 401 Unauthorized. The token is expired, invalid or unknown. Check that scope is the scope of the use case the request belongs to. The other fields come from the credentials the requester presented. They're named by the field IDs in the policy definition, so they depend on the use case. cnf.jkt is only there for DPoP tokens. You need it in the next step.

      LikeNeed the presented credentials themselves, e.g. for your audit log? Use POST /internal/auth/v2/accesstoken/introspect_extended: same result, plus the Verifiable Presentations.

      2. Check the DPoP header (DPoP tokens only)

      A DPoP token is only valid together with a DPoP header that the requester signed for this request:

      POST <internal Nuts interface>/internal/auth/v2/dpop/validate
      Content-Type: application/json
      
      {
        "dpop_proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0...",
        "method": "GET",
        "url": "https://fhir.example.com/r4/Patient",
        "thumbprint": "jlkhnp87453slfhansdhf",
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..."
      }
      
      Field Value dpop_proof The DPoP header of the incoming request method The HTTP method of the incoming request url The URL of the incoming request, as the requester called it. Query parameters are ignored. thumbprint cnf.jkt from the introspection result token The access token from the Authorization header

      The node answers with 200 OK and whether the DPoP header is valid:

      {
        "valid": false,
        "reason": "DPoP proof is expired"
      }
      
        valid: true: the DPoP header belongs to this token and this request. valid: false: reject the request with 401 Unauthorized. reason says why.

        Use the URL as the requester called it, not as your API sees it after a reverse proxy rewrote it. Otherwise the check fails.

        3. Decide whether to allow the query

        The Nuts node doesn't decide whether the requester may see this data. Your application does, using the introspection result and the rules of the use case.

        For 360-graden-clientbeeld, the rules are in volume 2a of the IG. In short:

          The request must fit the use case's FHIR CapabilityStatement. The requesting organization (organization_ura) must be identified, and the user (employee_identifier, employee_role) must be present for the audit log. There must be a legal basis: consent registered in Mitz, the requester is in the patient's CareTeam, or another legal basis. The request must be about one patient: a BSN in /Patient/_search, otherwise a reference to the Patient.

          You don't have to build this from scratch:

            Nuts Knooppunt has a policy decision point (PDP). Your reverse proxy or API gateway sends it the introspection result and the request details, and gets back allow or deny. It checks things like the FHIR CapabilityStatement, required search parameters and Mitz consent. It has built-in policies per scope, e.g. for BgZ, eOverdracht and PZP. See using the PDP in the Knooppunt docs. The 360 IG has an OPA policy that implements the 360 rules. It isn't used in production yet, but can be a starting point.

            Log every request with the discoveryrequester's service,organization anyand constraintuser, inas the presentation definition of the policy file is also added as key/value pair to the introspection result. This is the mechanism to use attestationscase from presented credentials for authorization purposes.requires.

            TheAPI tokenreference: introspectionAuth result is the last thing the Nuts node can do for you. From this point you have to apply the authorization policy...(v2)