Open theGOTOguy opened 3 years ago
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.
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();
@theGOTOguy / @JoeMcCaffrey - feel free to create a PR with optional functionality to enable the use of the AuthInterceptor
@theGOTOguy / @JoeMcCaffrey Could you please create PR. It will super helpful for other developer.Currently there is no auth in hapi fihr :(
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 What is passed through the proxy is configurable within oauth2-proxy.
You may also find inspiration here https://github.com/trifork/hapi-fhir-jpaserver-starter/tree/feature/smart-support
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 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.
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 I see that you're using zsh
, but my example uses bash
. Does it work if you quote the wildcard character?
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
It works correctly on my system (Ubuntu 22.04). The issue is probably in your firewall, network, or docker settings.
@LinkunGao
It works correctly on my system (Ubuntu 22.04). The issue is probably in your firewall, network, or docker settings.
I use jpa server starter, so how can i register the authorization interceptor
I try doing like this but it not working
Here is my interceptor (it for testing)
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?