hapi-fhir-jpaserver-starter icon indicating copy to clipboard operation
hapi-fhir-jpaserver-starter copied to clipboard

Add Optional Configuration for OAuth Authentication

Open theGOTOguy opened this issue 3 years ago • 14 comments

In short, I'd like to use the HAPI FHIR JPA server as a FHIR store, secured by OAuth similarly to this apparently defunct project.

Is there a reason that this isn't already included among the many options available in the JPA Server starter? Otherwise, would there be any interest if I were to create a PR allowing the user to optionally specify a generic OAuth authority?

theGOTOguy avatar Jan 13 '21 03:01 theGOTOguy

For anyone who follows, I've taken a different approach. If you're looking at this issue because you wish you had OAuth in the HAPI FHIR JPA Server without having to fork it and add your own, read on.

I understand that the right way to add OAuth would be to add it as a first-class part of the JPA server. This has proven to be a bit weird because there is some overlap in the capabilities of Spring Boot's OAuth2 functionality and HAPI FHIR's AuthorizationInterceptor. In practice, I've found it difficult to reconcile these two methods of authorization in a way that both works and makes sense as a PR to the generic starter JPA server. On the other hand, I don't want to fork the JPA server because I don't want to burden our team with maintaining yet another codebase.

Instead, I decided to stick to off-the-shelf Docker images and used the oauth2-proxy reverse proxy to add generic OAuth functionality to an underlying HAPI FHIR JPA server.

It's just four commands to set one up for development and testing purposes. For example:

First, set up a HAPI FHIR JPA server running at localhost:8080,

docker pull hapiproject/hapi:latest
docker run -p 8080:8080 \
           -e hapi.fhir.server_address=http://localhost:4180 \
           hapiproject/hapi:latest

Then set up an OAuth2 proxy wrapping the JPA server. This configuration uses MockLab's OAuth Server for Unit Testing which accepts any e-mail and password as valid, so obviously don't use it for anything but testing.

docker pull quay.io/oauth2-proxy/oauth2-proxy:latest
docker run -p 4180:4180 \
           -e OAUTH2_PROXY_COOKIE_SECRET=secretsecretsecr \
           -e OAUTH2_PROXY_CLIENT_ID=clientid \
           -e OAUTH2_PROXY_CLIENT_SECRET=secret \
           -e OAUTH2_PROXY_EMAIL_DOMAINS=* \
           -e OAUTH2_PROXY_UPSTREAMS=http://localhost:8080 \
           -e OAUTH2_PROXY_LOGIN_URL=https://oauth.mocklab.io/oauth/authorize \
           -e OAUTH2_PROXY_OIDC_JWKS_URL=https://oauth.mocklab.io/.well-known/jwks.json \
           -e OAUTH2_PROXY_REDEEM_URL=https://oauth.mocklab.io/oauth/token \
           -e OAUTH2_PROXY_COOKIE_SECURE=false \
           -e OAUTH2_PROXY_PROFILE_URL=https://oauth.mocklab.io/userinfo \
           -e OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 \
           -e OAUTH2_PROXY_PROVIDER=oidc \
           -e OAUTH2_PROXY_OIDC_ISSUER_URL=https://oauth.mocklab.io \
           -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \
           -e OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true \
           --network="host" \
           quay.io/oauth2-proxy/oauth2-proxy:latest

Now you can log into your HAPI FHIR JPA server using your web browser at localhost:4180!

Oauth2-proxy is very well documented and you can be quite granular in your permissions if you need to be with appropriate configuration.

theGOTOguy avatar Jan 27 '21 03:01 theGOTOguy

You will need to roll your own auth handling with AuthInterceptor:

I wrote my own service to connect to the OAuth2 server to verify a jwt and retrieve the roles.

Then use the RuleBuilder below to grant access based on roles

public class AuthInterceptor extends AuthorizationInterceptor {

  @Autowired
  JwtService jwtService;

  public List<IAuthRule> buildRuleList(RequestDetails details) {

    try {

      String token = details.getHeader(Constants.HEADER_AUTHORIZATION);
      if (token == null) {
        throw new AuthenticationException("Not authorized (no authorization header found in request)");
      }
      if (!token.startsWith(Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER)) {
        throw new AuthenticationException("Not authorized (authorization header does not contain a bearer token)");
      }

      JwtVerification jwtVerification = jwtService.verifyJwt(token);

      if(jwtVerification.getJwtBody().getRoles().contains("fhir-admin"))
        return new RuleBuilder().allowAll().build();

another:

return new RuleBuilder()
          .allow().read().allResources().withAnyId().forTenantIds(details.getTenantId())
          .andThen().allow().create().allResources().withAnyId().forTenantIds(details.getTenantId())
          .build();

JoeMcCaffrey avatar Feb 23 '21 19:02 JoeMcCaffrey

@theGOTOguy / @JoeMcCaffrey - feel free to create a PR with optional functionality to enable the use of the AuthInterceptor

jkiddo avatar Jun 26 '21 19:06 jkiddo

@theGOTOguy / @JoeMcCaffrey Could you please create PR. It will super helpful for other developer.Currently there is no auth in hapi fihr :(

aakashkag avatar May 09 '22 04:05 aakashkag

For anyone who follows, I've taken a different approach. If you're looking at this issue because you wish you had OAuth in the HAPI FHIR JPA Server without having to fork it and add your own, read on.

I understand that the right way to add OAuth would be to add it as a first-class part of the JPA server. This has proven to be a bit weird because there is some overlap in the capabilities of Spring Boot's OAuth2 functionality and HAPI FHIR's AuthorizationInterceptor. In practice, I've found it difficult to reconcile these two methods of authorization in a way that both works and makes sense as a PR to the generic starter JPA server. On the other hand, I don't want to fork the JPA server because I don't want to burden our team with maintaining yet another codebase.

Instead, I decided to stick to off-the-shelf Docker images and used the oauth2-proxy reverse proxy to add generic OAuth functionality to an underlying HAPI FHIR JPA server.

It's just four commands to set one up for development and testing purposes. For example:

First, set up a HAPI FHIR JPA server running at localhost:8080,

docker pull hapiproject/hapi:latest
docker run -p 8080:8080 \
           -e hapi.fhir.server_address=http://localhost:4180 \
           hapiproject/hapi:latest

Then set up an OAuth2 proxy wrapping the JPA server. This configuration uses MockLab's OAuth Server for Unit Testing which accepts any e-mail and password as valid, so obviously don't use it for anything but testing.

docker pull quay.io/oauth2-proxy/oauth2-proxy:latest
docker run -p 4180:4180 \
           -e OAUTH2_PROXY_COOKIE_SECRET=secretsecretsecr \
           -e OAUTH2_PROXY_CLIENT_ID=clientid \
           -e OAUTH2_PROXY_CLIENT_SECRET=secret \
           -e OAUTH2_PROXY_EMAIL_DOMAINS=* \
           -e OAUTH2_PROXY_UPSTREAMS=http://localhost:8080 \
           -e OAUTH2_PROXY_LOGIN_URL=https://oauth.mocklab.io/oauth/authorize \
           -e OAUTH2_PROXY_OIDC_JWKS_URL=https://oauth.mocklab.io/.well-known/jwks.json \
           -e OAUTH2_PROXY_REDEEM_URL=https://oauth.mocklab.io/oauth/token \
           -e OAUTH2_PROXY_COOKIE_SECURE=false \
           -e OAUTH2_PROXY_PROFILE_URL=https://oauth.mocklab.io/userinfo \
           -e OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 \
           -e OAUTH2_PROXY_PROVIDER=oidc \
           -e OAUTH2_PROXY_OIDC_ISSUER_URL=https://oauth.mocklab.io \
           -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \
           -e OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true \
           --network="host" \
           quay.io/oauth2-proxy/oauth2-proxy:latest

Now you can log into your HAPI FHIR JPA server using your web browser at localhost:4180!

Oauth2-proxy is very well documented and you can be quite granular in your permissions if you need to be with appropriate configuration.

How is the OAuth2 JWT passed from oauth-proxy to the JPA server?

schepuri2012 avatar Jul 19 '23 16:07 schepuri2012

@schepuri2012 What is passed through the proxy is configurable within oauth2-proxy.

theGOTOguy avatar Jul 20 '23 02:07 theGOTOguy

You may also find inspiration here https://github.com/trifork/hapi-fhir-jpaserver-starter/tree/feature/smart-support

jkiddo avatar Jul 20 '23 06:07 jkiddo

You will need to roll your own auth handling with AuthInterceptor:

I wrote my own service to connect to the OAuth2 server to verify a jwt and retrieve the roles.

Then use the RuleBuilder below to grant access based on roles

public class AuthInterceptor extends AuthorizationInterceptor {

  @Autowired
  JwtService jwtService;

  public List<IAuthRule> buildRuleList(RequestDetails details) {

    try {

      String token = details.getHeader(Constants.HEADER_AUTHORIZATION);
      if (token == null) {
        throw new AuthenticationException("Not authorized (no authorization header found in request)");
      }
      if (!token.startsWith(Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER)) {
        throw new AuthenticationException("Not authorized (authorization header does not contain a bearer token)");
      }

      JwtVerification jwtVerification = jwtService.verifyJwt(token);

      if(jwtVerification.getJwtBody().getRoles().contains("fhir-admin"))
        return new RuleBuilder().allowAll().build();

another:

return new RuleBuilder()
          .allow().read().allResources().withAnyId().forTenantIds(details.getTenantId())
          .andThen().allow().create().allResources().withAnyId().forTenantIds(details.getTenantId())
          .build();

Hi @JoeMcCaffrey, Can you please tell me how you registered the BearerTokenAuthInterceptor to the client in the FHIR context? I see the Built-In Client Interceptor documentation, but not sure where to configure this in the jpaserver. Appreciate your help. Thanks

schepuri2012 avatar Aug 07 '23 16:08 schepuri2012

@schepuri2012 What is passed through the proxy is configurable within oauth2-proxy.

Thanks, I got the OAuth2 proxy working. It works great for the web client. However, I still need to modify the JPA restful server to authenticate the rest API endpoints using the JWT bearer tokens.

schepuri2012 avatar Aug 07 '23 16:08 schepuri2012

For anyone who follows, I've taken a different approach. If you're looking at this issue because you wish you had OAuth in the HAPI FHIR JPA Server without having to fork it and add your own, read on.

I understand that the right way to add OAuth would be to add it as a first-class part of the JPA server. This has proven to be a bit weird because there is some overlap in the capabilities of Spring Boot's OAuth2 functionality and HAPI FHIR's AuthorizationInterceptor. In practice, I've found it difficult to reconcile these two methods of authorization in a way that both works and makes sense as a PR to the generic starter JPA server. On the other hand, I don't want to fork the JPA server because I don't want to burden our team with maintaining yet another codebase.

Instead, I decided to stick to off-the-shelf Docker images and used the oauth2-proxy reverse proxy to add generic OAuth functionality to an underlying HAPI FHIR JPA server.

It's just four commands to set one up for development and testing purposes. For example:

First, set up a HAPI FHIR JPA server running at localhost:8080,

docker pull hapiproject/hapi:latest
docker run -p 8080:8080 \
           -e hapi.fhir.server_address=http://localhost:4180 \
           hapiproject/hapi:latest

Then set up an OAuth2 proxy wrapping the JPA server. This configuration uses MockLab's OAuth Server for Unit Testing which accepts any e-mail and password as valid, so obviously don't use it for anything but testing.

docker pull quay.io/oauth2-proxy/oauth2-proxy:latest
docker run -p 4180:4180 \
           -e OAUTH2_PROXY_COOKIE_SECRET=secretsecretsecr \
           -e OAUTH2_PROXY_CLIENT_ID=clientid \
           -e OAUTH2_PROXY_CLIENT_SECRET=secret \
           -e OAUTH2_PROXY_EMAIL_DOMAINS=* \
           -e OAUTH2_PROXY_UPSTREAMS=http://localhost:8080 \
           -e OAUTH2_PROXY_LOGIN_URL=https://oauth.mocklab.io/oauth/authorize \
           -e OAUTH2_PROXY_OIDC_JWKS_URL=https://oauth.mocklab.io/.well-known/jwks.json \
           -e OAUTH2_PROXY_REDEEM_URL=https://oauth.mocklab.io/oauth/token \
           -e OAUTH2_PROXY_COOKIE_SECURE=false \
           -e OAUTH2_PROXY_PROFILE_URL=https://oauth.mocklab.io/userinfo \
           -e OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 \
           -e OAUTH2_PROXY_PROVIDER=oidc \
           -e OAUTH2_PROXY_OIDC_ISSUER_URL=https://oauth.mocklab.io \
           -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \
           -e OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true \
           --network="host" \
           quay.io/oauth2-proxy/oauth2-proxy:latest

Now you can log into your HAPI FHIR JPA server using your web browser at localhost:4180!

Oauth2-proxy is very well documented and you can be quite granular in your permissions if you need to be with appropriate configuration.

Hi @theGOTOguy, I also want to use this way to proxy my fhir server locally. I create a github/google oauth2 app, and set the Homepage URL: http://localhost:4180/ and Authorization callback URL: http://localhost:4180/oauth2/callback, then copy and paste the client_id and client_secret from oauth2 app to

 -e OAUTH2_PROXY_CLIENT_ID=clientid \
-e OAUTH2_PROXY_CLIENT_SECRET=secret \

but when I run the docker command to start the proxy, I always get this error:

zsh: no matches found: OAUTH2_PROXY_EMAIL_DOMAINS=*

but, if I change the * to my gmail, the error will disappear, but the proxy still not work. E.g, when I view http://localhost:4180, I cannot access the webpage with This site can’t be reachedlocalhost refused to connect.

Can you help me with this? Thanks!

LinkunGao avatar Aug 16 '23 01:08 LinkunGao

@LinkunGao I see that you're using zsh, but my example uses bash. Does it work if you quote the wildcard character?

theGOTOguy avatar Aug 16 '23 03:08 theGOTOguy

Hi @theGOTOguy Thanks so much for your help!

When I switch to bash, there is no issue with OAUTH2_PROXY_EMAIL_DOMAINS=*.

However, the http://localhost:4180/ is still not work. I am sure the fhir server on http://localhost:8080/ is working. And I set the oauth2 app via Github. Is there anything wrong with my script?

docker run -p 4180:4180 \
           -e OAUTH2_PROXY_COOKIE_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 \
           -e OAUTH2_PROXY_CLIENT_ID=from_github_oauth2_app_client_id \
           -e OAUTH2_PROXY_CLIENT_SECRET=from_github_oauth2_app_client_secret \
           -e OAUTH2_PROXY_UPSTREAMS=http://localhost:8080 \
           -e OAUTH2_PROXY_EMAIL_DOMAINS=* \
           -e OAUTH2_PROXY_REDIRECT_URL=http://localhost:4180/oauth2/callback \
           -e OAUTH2_PROXY_COOKIE_SECURE=false \
           -e OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 \
           -e OAUTH2_PROXY_PROVIDER=github \
           -e OAUTH2_PROXY_SKIP_OIDC_DISCOVERY=true \
           -e OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true \
           --network="host" \
           quay.io/oauth2-proxy/oauth2-proxy:latest

LinkunGao avatar Aug 16 '23 23:08 LinkunGao

@LinkunGao

It works correctly on my system (Ubuntu 22.04). The issue is probably in your firewall, network, or docker settings.

Screenshot from 2023-08-17 01-17-16

theGOTOguy avatar Aug 17 '23 05:08 theGOTOguy

@LinkunGao

It works correctly on my system (Ubuntu 22.04). The issue is probably in your firewall, network, or docker settings.

Screenshot from 2023-08-17 01-17-16

I use jpa server starter, so how can i register the authorization interceptor I try doing like this but it not working image

Here is my interceptor (it for testing)

image

TranLong183 avatar Oct 18 '23 06:10 TranLong183