Hapi Fhir JPA with oauth2

3,133 views
Skip to first unread message

nrusingha.be...@gmail.com

unread,
Dec 2, 2016, 7:35:18 AM12/2/16
to HAPI FHIR
Hi to all,
I am new to Hapi-Fire, can any one help me in adding oauth2 in Hapi-fhir jpa server.
Kindly reply 
Thanks with regards ......

James Agnew

unread,
Dec 2, 2016, 10:47:40 AM12/2/16
to nrusingha.be...@gmail.com, HAPI FHIR
Hi There,

A useful starting point is probably the server interceptor framework: http://hapifhir.io/doc_rest_server_interceptor.html

You could certainly write an interceptor which inspects incoming requests for a bearer token, validates it, and then allows/rejects requests.

The authorization interceptor might be a good base for what you're doing: http://hapifhir.io/doc_rest_server_security.html#Authorization_Interceptor

There isn't any one-stop solution for this though, you'll definitely need to code a solution for your particular situation. OAuth2/OpenID Connect defines a way for an application to authenticate and authorize a user, but it doesn't provide a way for a resource provider (e.g. a FHIR server) to actually use and verify that authorization. That part is up to you.

Cheers,
James

--
You received this message because you are subscribed to the Google Groups "HAPI FHIR" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hapi-fhir+unsubscribe@googlegroups.com.
To post to this group, send email to hapi...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hapi-fhir/62732501-bf4f-48ae-b235-92dc99139a02%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

nrusingha.be...@gmail.com

unread,
Dec 2, 2016, 11:58:46 PM12/2/16
to HAPI FHIR

Thanks James for your reply.
Its quite helpful for me. Can you suggest any git samples for reference

Lars Kristian Roland

unread,
Dec 3, 2016, 5:08:41 AM12/3/16
to HAPI FHIR
If you're looking for an example on how to pick out the BEARER access-token and make an access rule, you can have a look here: (A JWT-based OAuth-example, where the JWT is signed with the issuer's certificate. The example shows how to check the certificate.) 


You probably want to look at the scopes though and build the rules differently according to the scopes (I'd suggest formatting scopes in a SMART-on-FHIR fashion). You would extract the scope from the JWT and build different rules depending on the scope.

You still need a server to authenticate the user and issue the JWT-based OAuth access token. Such as IdentityServer3 or MitreID or other. Then you need your FHIR-server to trust the certificate that the authentication/authorization server uses to sign the JWT.

Best regards,
Lars
Message has been deleted
Message has been deleted

nrusingha.be...@gmail.com

unread,
Dec 5, 2016, 5:31:48 AM12/5/16
to HAPI FHIR

Thanks Roland,
now I put the Authorization code and I think Its working.
but can you suggest me the proper way of setting headers as its showing error:

2016-12-05 14:09:49.728 [qtp1040385133-17] WARN  ca.uhn.fhir.to.BaseController [BaseController.java:396] Failed to load conformance statement
ca.uhn.fhir.rest.server.exceptions.AuthenticationException: HTTP 401 Unauthorized: Missing Authorization header value
at sun.reflect.GeneratedConstructorAccessor112.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException.newInstance(BaseServerResponseException.java:307)
at ca.uhn.fhir.rest.client.BaseClient.invokeClient(BaseClient.java:290)
at ca.uhn.fhir.rest.client.BaseClient.invokeClient(BaseClient.java:183)
at ca.uhn.fhir.rest.client.GenericClient.conformance(GenericClient.java:105)
at ca.uhn.fhir.to.BaseController.loadAndAddConfDstu2(BaseController.java:394)
at ca.uhn.fhir.to.BaseController.loadAndAddConf(BaseController.java:321)
at ca.uhn.fhir.to.BaseController.addCommonParams(BaseController.java:97)
at ca.uhn.fhir.to.Controller.actionHome(Controller.java:230)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:845)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1689)
at org.ebaysf.web.cors.CORSFilter.handleNonCORS(CORSFilter.java:437)
at org.ebaysf.web.cors.CORSFilter.doFilter(CORSFilter.java:172)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1668)
at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:225)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1676)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:581)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:226)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1180)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:511)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1112)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:213)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:119)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:134)
at org.eclipse.jetty.server.Server.handle(Server.java:524)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:319)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:253)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:273)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:95)
at org.eclipse.jetty.io.SelectChannelEndPoint$2.run(SelectChannelEndPoint.java:93)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.executeProduceConsume(ExecuteProduceConsume.java:303)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.produceConsume(ExecuteProduceConsume.java:148)
at org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume.run(ExecuteProduceConsume.java:136)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:671)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:589)
at java.lang.Thread.run(Unknown Source)


James Agnew

unread,
Dec 5, 2016, 1:23:17 PM12/5/16
to nrusingha.be...@gmail.com, HAPI FHIR
Hi Nrushinga,

The BearerTokenAuthInterceptor could be a way of doing this on your client: http://hapifhir.io/doc_rest_client_interceptor.html#Security_HTTP_Bearer_Token_Authorization

That said, you still need an actual bearer token to provide it. That part is a bit beyond the scope of HAPI. You could use something like MitreID Connect to generate these, but there are lots of other ways.

Cheers,
James



sent from my phone.

--
You received this message because you are subscribed to the Google Groups "HAPI FHIR" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hapi-fhir+unsubscribe@googlegroups.com.
To post to this group, send email to hapi...@googlegroups.com.

James Agnew

unread,
Dec 6, 2016, 6:31:38 AM12/6/16
to nrusingha.be...@gmail.com, HAPI FHIR
Unfortunately the web testing UI does not support OAuth2 at this point. I'm hoping we'll add that support at some point, but it hasn't happened yet..

On Mon, Dec 5, 2016 at 3:29 AM, <nrusingha.be...@gmail.com> wrote:
Thanks Ronald
In this sample I have to set the header. Can you suggest.me the proper way of setting the header.
Please check out the snaps I have attached.


--
You received this message because you are subscribed to the Google Groups "HAPI FHIR" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hapi-fhir+unsubscribe@googlegroups.com.
To post to this group, send email to hapi...@googlegroups.com.

nrusingha.be...@gmail.com

unread,
Dec 8, 2016, 4:30:17 AM12/8/16
to HAPI FHIR
Hey James,
you can see the errors in my previous post, it is showing Authorization failed as header is missing.
The header must contain Authorization: bearer <token>

Here I am confused how to generate the token and where it will be added to the header.
The question may be silly but as I am new to Oauth2 Hapi-fhir integration , please give steps to implement it.

Regards
Nrusingh

viswanathan R

unread,
Jan 3, 2018, 1:20:40 AM1/3/18
to HAPI FHIR
Hi  Lars Kristian Roland,

the bitbucket asks the  credentials could you please make it as public.

Lars Kristian Roland

unread,
Jan 3, 2018, 3:23:21 AM1/3/18
to viswanathan R, HAPI FHIR
The example code is now a bit out-of-date, so I made it private. I'm note sure if the code runs on the most recent version of HAPI, but I've included it below. 


Lars

--
You received this message because you are subscribed to a topic in the Google Groups "HAPI FHIR" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/hapi-fhir/0ewrCc4v_Sk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to hapi-fhir+unsubscribe@googlegroups.com.

To post to this group, send email to hapi...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Lars Kristian Roland

Lars Kristian Roland

unread,
Jan 3, 2018, 3:25:42 AM1/3/18
to viswanathan R, HAPI FHIR
Apologies. The email was sent a bit quickly. Again, not sure if the code runs any more... 

package ca.uhn.fhir.jpa.demo;

import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;

import org.jose4j.jwk.HttpsJwks;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver;

import org.json.JSONObject;

import java.util.Base64;
import java.util.List;

/**
 * Created by LarsKristian on 12.09.2016.
 */

public class OAuthAuthorizationInterceptor extends AuthorizationInterceptor {

    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(OAuthAuthorizationInterceptor.class);

    @Override
    public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {

        // Check if request has authorization header and Bearer token
        if (theRequestDetails.getHeader("Authorization") == null) {
            // Throw an HTTP 401
            throw new AuthenticationException("Missing Authorization header value");
        } else if (!theRequestDetails.getHeader("Authorization").toUpperCase().startsWith("BEARER ")) {
            logger.error("Bearer not found (do not log in production!) = " + theRequestDetails.getHeader("Authorization"));
            throw new AuthenticationException("Missing Bearer token in Authorization header (must start with 'Bearer')");
        }

        String authHeader = theRequestDetails.getHeader("Authorization");
        String encodedAccessToken = authHeader.split(" ")[1];

        logger.info("Authorization header (do not log in production!) = " + authHeader);
        logger.info("encodedAccessToken (do not log in production!) = "+encodedAccessToken);

        JSONObject decodedAccessTokenBody = null;
        boolean isJWTValid = false;
        boolean userIsAdmin = false;
        IdDt userIdPatientId = null;

        try {
            decodedAccessTokenBody = getDecodedJSONObject(encodedAccessToken.split("\\.")[1]);
            isJWTValid = checkNHNAuthorization(encodedAccessToken, decodedAccessTokenBody);
        } catch (Exception e) {
            throw new AuthenticationException("Error parsing Bearer token (is it a valid JWT?)");
        }

        if (isJWTValid) {
            // TODO: Only give access to ONE patient!
            userIsAdmin = true;
            // This user has access only to Patient/1 resources
            //userIdPatientId = new IdDt("Patient", 1L);
        } else {
            // Throw an HTTP 401
            throw new AuthenticationException("Bearer token not accepted");
        }

        // If the user is a specific patient, we create the following rule chain:
        // Allow the user to read anything in their own patient compartment
        // Allow the user to write anything in their own patient compartment
        // If a client request doesn't pass either of the above, deny it
        if (userIdPatientId != null) {
            return new RuleBuilder()
                    .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen()
                    .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen()
                    .denyAll()
                    .build();
        }

        // If the user is an admin, allow everything
        if (userIsAdmin) {
            return new RuleBuilder()
                    .allowAll()
                    .build();
        }

        // By default, deny everything. This should never get hit, but it's
        // good to be defensive
        return new RuleBuilder()
                .denyAll()
                .build();
    }

    private boolean checkNHNAuthorization(String encodedAccessToken, JSONObject decodedAccessTokenBody) {
        // Check Access Token
        HttpsJwks httpsJkws = new HttpsJwks("https://adfs.fia.nhn.no/adfs/discovery/keys");
        HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws);
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setAllowedClockSkewInSeconds(180) // allow some leeway in validating time based claims to account for clock skew
                //.setRequireSubject() // the JWT must have a subject claim
                .setExpectedIssuer("http://adfs.fia.nhn.no/adfs/services/trust") // whom the JWT needs to have been issued by
                .setExpectedAudience("http://apps.ehelselab.com/velferd/api/") // to whom the JWT is intended for
                .setVerificationKeyResolver(httpsJwksKeyResolver)
                .build();
        try
        {
            //  Validate the JWT and process it to the Claims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(encodedAccessToken);
            logger.info("JWT validation succeeded! " + jwtClaims);
            return true;
        }
        catch (InvalidJwtException e)
        {
            logger.info("Invalid JWT! ", e);
            return false;
        }
    }


    private JSONObject getDecodedJSONObject(String encodedString){
        byte[] decoded = Base64.getDecoder().decode(encodedString);
        return new JSONObject(new String(decoded));
    }
}


kevin.m...@airelogic.com

unread,
Jan 3, 2018, 3:28:51 AM1/3/18
to HAPI FHIR
Not clear if you have an OAuth2 server? 

As mentioned above, we used the Mitre (https://mitreid.org/) based server HSPC uses (https://bitbucket.org/hspconsortium/reference-auth).

NOTE: It's not a HAPI JPA Server but config should be very similar. HSPC uses JPA Server and you'll have to browse their bitbucket for examples.

On OAuth2. I found the documentation quite hard to navigate but this new book was outsatnding https://www.manning.com/books/oauth-2-in-action  
It goes over the nuts and bolts of OAuth2, gradually adding complexity in each chapter.

Scott Kirby

unread,
May 23, 2019, 10:35:20 AM5/23/19
to HAPI FHIR

Hi Kevin - I’m a bit late to this conversation, but I’m working on prototyping a FHIR server with HAPI, and have just begun working with the HSPC reference auth server as an OpenID/OAuth2 solution.  While I have followed the documentation to configure and deploy the various components of the HSPC infrastructure (HSPC reference API, etc.) to do end-to-end app testing (including auth flow), my primary interest is in using the HSPC reference auth server along side a custom FHIR server built with HAPI.  If I’m understanding your post here  correctly, this is a similar approach to the one you’ve taken with NHS Care Connect.  Additionally, looking at some of the code in the CCRI, it appears as though the server itself simply extends the HAPI InterceptorAdaptor to inspect bearer tokens (which are assumed to be JWT) and verify authorization conditions based on the token contents, which is more or less in line with what I was imagining and what I’ve heard James speak about in the past.

All that said, I suppose I’m just hoping to verify that you’ve continued to have success using the HSPC reference auth server outside of their overall ‘sandbox’ architecture, and that’s it has been a viable option for integration with your own HAPI-based FHIR server implementation.  

sarankuma...@gmail.com

unread,
Apr 16, 2020, 8:03:54 AM4/16/20
to HAPI FHIR
Hi Lars,

I am working HAPI FHIR server setup. Don't know how to add the OAuth to it.

can you help me on this?

please share me the sample code also.

Thanks,
sarankumar
Reply all
Reply to author
Forward
0 new messages