************************ Hosting your own Service ************************ This guide aims to explain how you, as a Provider, can host your own GAIA-X-Med compliant Service that will be consumable by other GAIA-X-Med Participants. Using the Demonstrator as a starting point ========================================== The `Demonstrator <https://gitlab.isp.uni-luebeck.de/gaia-x-med/demonstrator-web-service>`__ serves a reference implementation of everything that is required to host a GAIA-X-Med Service as a Provider. It includes simple implementations of the necessary software components alongside a configurable Docker compose environment. Both Open ID Connect as well as Authentication Proxy based authentication methods are supported. After first reading through this article, which hopefully helps you in making informed decisions on how to set up a fitting architecture for your Service, you can refer to the `Demonstrator Local installation guide <https://gitlab.isp.uni-luebeck.de/gaia-x-med/demonstrator-web-service/-/blob/main/INSTALL.md>`__ for a detailed setup and configuration guide. Onboarding the Service ====================== Service Offerings in GAIA-X-Med require an **identity**, just like it is the case with :doc:`Participants </getting-started/becoming-a-participant>`. Just like a Participant has to make certain claims about themselves (legal name, company address, etc.), the Service also has to describe itself using a Verifiable Credential (also known as a *Gaia-X Self-Description*), and they also require a unique and resolvable DID. You can follow the :doc:`service-onboarding-guide` for a detailed explanation on how to set up a Service Offering Credential using the Credential Manager and Credential Store. Choosing an authentication method / service type ================================================ As explained in :doc:`/documentation/key-concepts/authentication`, we support two distinct methods of offering a Service with regards to authentication: **OpenID Connect-based** and **Authentication Proxy-based**. They both have their advantages and disadvantages and are suited for different types of services. It is up to you to choose the method you deem best suitable for the kind of Service that you want to offer. OpenID Connect-based (suitable for web apps) -------------------------------------------- As the name implies, the OpenID Connect based method offers interactive authentication using the `OpenID Connect <https://openid.net/>`__ standard. For this purpose, the GAIA-X-Med ecosystem hosts an **OpenID Connect Identity Provider** that can be used by **OpenID Connect-compatible clients**. .. figure:: /../guides/hosting-service/auth-oidc.png :width: 100% :alt: OpenID Connect based authentication OpenID Connect based authentication Since OpenID Connect is primarily focused on providing an interactive, human-centric authentication method, it is most suitable if you want to offer a **web application with a frontend as a Service**, or if you have built **a web application that is already secured by an easily exchangable authentication method** such as Keycloak. It is particularly well-suited for single-page web applications. Authentication Proxy-based (suitable for APIs) ---------------------------------------------- Alternatively, we offer an authentication method that makes use of a software component called the **Authentication Proxy**. This is, in essence, a simple HTTP reverse proxy that is deployed in front of your Service and that implements the necessary GAIA-X-Med authentication protocols. Incoming HTTP requests from Consumers need to contain an authentication token that the Authentication Proxy forwards to the GAIA-X-Med Authentication Services. On successful authentication, the verified Consumer credentials are supplied to the Authentication Proxy, which in turn forwards it to the Service via HTTP request headers. If a Consumer fails to authenticate, the request is automatically denied before it reaches your Service. .. figure:: /../guides/hosting-service/auth-proxy.png :width: 100% :alt: Authentication Proxy based authentication Authentication Proxy based authentication This method is particularly suited if you want to offer an **API as a Service**, as authentication is achieved in an automated manner. It is also relatively simple to implement on both Provider and Consumer side – all a Consumer has to do is to configure a connector library with their Participant Identity File, and the library will handle the rest; while the Provider’s Service only has to be designed to parse the HTTP headers carrying the Consumer’s credentials. It can theoretically also be used for server-side rendered web applications (built with frameworks like e.g. Django or Laravel); though you would have to design some means of generating the necessary authentication token client-side and supplying it with every request. Architecture and service design considerations ============================================== Authentication -------------- Depending on which authentication method you choose, you will need to setup the corresponding components. OpenID Connect method ~~~~~~~~~~~~~~~~~~~~~ If you want to secure your service using the GAIA-X-Med OpenID Connect Identity Provider, you will need to install and configure an **OpenID Connect client**. Choosing an OpenID Connect client ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are several options available. Here are a few suggestions: - `Apache 2 <https://httpd.apache.org/>`__ + `mod_auth_openidc <https://github.com/OpenIDC/mod_auth_openidc>`__ is what the Demonstrator uses and was found by us to be the most simple and straightforward solution to setup while still being highly configurable and most compatible. Since the OIDC implementation is handled by the server module, no additional service will need to be run. The module does need to be installed manually as it is not a standard Apache module. - `Keycloak <https://www.keycloak.org/docs/22.0.1/server_admin/#_identity_broker_oidc>`__ can be configured to enable logins through an OpenID Connect Identity Provider, but its enormous feature bloat might be overkill for its usage in a simple Service. - `nginx <https://nginx.org/en/>`__ + `oauth2-proxy <https://github.com/oauth2-proxy/oauth2-proxy>`__ is a combination that we first considered to use for the Demonstrator but eventually abandoned in favor of the Apache solution. While it generally seems to work well, it is difficult to configure and rather inflexible as generic OIDC support is not particularly fleshed out, and there are multiple unaddressed open issues (at time of writing). However, it could still be a useful starting point for basic setups. Registering your OpenID Connect client with the Identity Provider ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You will need to register your client with the GAIA-X-Med OpenID Connect Identity Provider. In order to do this, you will need to provide: - an URI that the Identity Provider is allowed to redirect your users to, for example ``https://service.provider.com/redirect_uri`` - URIs for any valid post-logout redirect targets, ex. ``https://service.provider.com/public/logout.html`` Once registered, you will receive a client ID and client secret that you need to use when configuring your client. .. note:: There is no automated process for this yet, so for now, please get in touch with us directly if you want to register an OIDC client. .. _configuring-the-openid-connect-client: Configuring the OpenID Connect client ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the following settings to configure your OpenID Connect client: - **Discovery endpoint:** https://identity.gaia-med.org/idp/.well-known/openid-configuration - **PKCE method:** S256 - **OIDC scope:** ``openid`` plus any of the following: ``email profile phone address`` The Identity Provider will map a Participant’s claims from their Verifiable Credential into OAuth2 claims as follows: +----------------------------------+---------------+-------------------+ | Participant GAIA-X-Med claim | OAuth2 scope | OAuth2 claim(s) | +==================================+===============+===================+ | *Participant’s unique identifier | ``openid`` | ``sub``, ``iss`` | | (DID ID)* | | | +----------------------------------+---------------+-------------------+ | *Participant’s full credential | ``profile`` | ``credential`` | | JSON* | | (non-standard) | +----------------------------------+---------------+-------------------+ | Legal Name | ``profile`` | ``name``, | | | | ``pre | | | | ferred_username`` | +----------------------------------+---------------+-------------------+ | E-Mail address | ``email`` | ``email`` | +----------------------------------+---------------+-------------------+ | Phone number | ``phone`` | ``phone_number`` | +----------------------------------+---------------+-------------------+ | Address \* | ``address`` | ``address`` | +----------------------------------+---------------+-------------------+ \* Only contains the country subdivison code at this current time. .. note:: The OAuth2 claim mapping is subject to change. Forwarding the Consumer claims to your application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You will likely want to configure your OIDC client in a way that provides your secured application with the contents of the above claims. How this is done exactly will depend on your client. For example, if you’re using Keycloak, one way to do this is to set up a `mapper <https://www.keycloak.org/docs/latest/server_admin/#_mappers>`__ that e.g. imports the OAuth2 claims as User Attributes. .. hint:: If you want to serve an API backend that is compatible with both the OpenID Connect *and* Authentication Proxy methods, it is recommended that you follow the :ref:`specification of the Authentication Proxy <forwarding-the-consumer-claims>` in this regard to avoid having to write handlers for both methods. Authentication Proxy method ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Setting up a service to use the Authentication Proxy method mostly involves running and hosting an instance of the **Authentication Proxy**. Setting up the Authentication Proxy ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The `Authentication Proxy <https://gitlab.isp.uni-luebeck.de/gaia-x-med/provider-services/authentication-proxy>`__ is a simple Java-based HTTP proxy that intercepts requests from an external Consumer to your internal Service backend, performing the necessary authentication steps and providing your application with the Consumer’s credentials (or denying access if the Consumer fails to authenticate). You can run an instance of the Proxy via its Docker image, a compiled jar file or by using maven. See the `README <https://gitlab.isp.uni-luebeck.de/gaia-x-med/provider-services/authentication-proxy>`__ for details. Make sure to configure the service endpoint address and port in the proxy’s ``proxy.properties`` file. .. _forwarding-the-consumer-claims: Fowarding the Consumer claims to your application ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If a Consumer provides a token in their HTTP request that successfully authenticates, the Proxy will forward the request to your Service backend while enriching it with the Consumer’s credentials as additional HTTP request headers. A Participant’s credentials are mapped to HTTP request headers as follows: ========================================== ========================= Participant credential HTTP request header ========================================== ========================= *Participant’s unique identifier (DID ID)* ``x-gaia-x-sub`` *Participant’s full credential JSON* ``x-gaia-x-credential`` Legal Name ``x-gaia-x-name`` E-Mail address ``x-gaia-x-email`` Phone number ``x-gaia-x-phone_number`` Address \* ``x-gaia-x-address`` ========================================== ========================= \* Only contains the country subdivison code at this current time. .. note:: The OAuth2 claim mapping is subject to change. .. _hosting-contract-service: Contract Service ---------------- To allow a Consumer to actually consume your Service, they will need to have signed a valid **contract** with you. GAIA-X-Med contracts are stored on the Provider side, and we have developed the **Contract Service** as a means of managing them. `Follow the instructions in the Contract Service repository <https://gitlab.isp.uni-luebeck.de/gaia-x-med/provider-services/contract-service>`__ to set up an instance. A single Contract Service can be configured to handle multiple Services at once; but you can also deploy one instance per Service as well. The Contract Service handles **negotiation** as well as **storage** of Contracts. By default, it performs only basic checks on incoming Contract Offers, but you can extend it with more complex, custom logic if need be. See :doc:`/documentation/key-concepts/contract-negotiation` for more details. .. _hosting-service-backend: Service Backend --------------- The backend of a Provider’s Service has two major responsibilities to fulfill as a GAIA-X-Med Service: **parsing the incoming authentication headers** passed from the authentication layer, and verifying that the Consumer is authorized to consume the Service by **checking for an existing contract**. .. _parsing-the-authentication-headers: Parsing the authentication headers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Regardless of whether you choose the OpenID Connect or Proxy method, you can safely assume that incoming requests to your backend are properly authenticated. To tell your Consumers apart, the authentication layer will pass their credentials along the forwarded requests. How this is done exactly depends on the method you implemented. - In case of the Authentication Proxy, the credentials will be sent via HTTP request headers :ref:`(see here) <forwarding-the-consumer-claims>`. - If you use an OpenID Connect client, it will depend on the client’s configuration :ref:`(see here) <configuring-the-openid-connect-client>`. .. hint:: If you want to implement *both* authentication methods for a single Service, it is recommended that you configure the OpenID Connect client in a way that supplies your backend with the credentials in the same format the Authentication Proxy does (HTTP request headers prefixed with ``x-gaia-x-...``). This way, you can reuse the same credential handling code for both methods. Checking for a valid contract (Authorization) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After a Consumer has successfully authenticated and your service backend has received their credentials, it is required to check whether they actually signed a contract with you to use your service. To accomplish this, your service backend will need to query your :ref:`hosting-contract-service` instance. This is done via a simple GET query that contains the unique IDs of the Consumer and your service. For example, if your Contract Service is reachable at ``contract-service``, to check whether the Consumer ``did:web:identity.consumer.com:johndoe`` has signed a contract to use the service ``did:web:identity.provider.com:services:gallery``, you would call: `http://contract-service/contracts?participant=did%3Aweb%3Aidentity.consumer.com%3Ajohndoe&service=did%3Aweb%3Aidentity.provider.com%3Aservices%3Agallery <http://contract-service/contracts?participant=identity.consumer.com%3Ajohndoe&service=identity.provider.com%3Aservices%3Agallery>`__ **Note:** The colon (``:``) is URL-encoded into ``%3A``. The Contract Service would then respond with the contents of the contract, which could be: .. code:: json { "participant": "did:web:identity.consumer.com:johndoe", "service": "did:web:identity.provider.com:services:gallery", "data": { "validFrom": 1690460452, "validUntil": 1721996452 } } The Contract Service, by default, only returns a Contract if it is also currently valid according to the timestamps. The Service backend should deny access to the requested resource(s) if the Consumer is authenticated but does not have a valid contract, or if they have not signed any contract at all (in which case the Contract Service would return a HTTP 404 response). Contract creation hook ~~~~~~~~~~~~~~~~~~~~~~ When a new Contract has been signed between a Consumer and a Provider, the Contract Service possesses the ability to automatically notify the Service backend of this. This feature allows you to perform some additional onboarding steps for your new Consumer, such as the creation of a local account. The Contract Service can be configured to either use a single hook endpoint URL for all Services, but you can also code more complex logic if your Contract Service is meant to handle multiple Services as well. In any case, the Contract Service will send a POST request with the finalized Contract document as the request body. Your backend can parse the Contract and use it to perform the onboarding steps. For example, if you wish to setup a local account for the Consumer, it would be a good idea to read their unique identifier (``did:web:...``) from the contract and use it as a username. Later, when the Consumer authenticates and accesses your Service, the same unique identifier will be found in the ``x-gaia-x-sub`` request header, allowing you to match them up with their local account.