Message Boards

REST API (JAX-RS) and authentication via Portal Session

thumbnail
André Bräkling, modified 3 Years ago.

REST API (JAX-RS) and authentication via Portal Session

Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
Hi everybody.

We are using Liferay DXP 7.1 and created a module which provides a REST API with JAX-RS. These endpoints are currently used by two application types:

  • A native, third-party mobile app (iOS and Android), authenticating via OAuth 2
  • Liferay portlets, which dynamically load and update front-end data (AJAX), authenticating via the user's existing portal session
On our live system, both authentication methods work as expected: The third-party app is working as expected via OAuth2, and our portal users can use the different portlets just by logging in before.

So, basically, nothing to complain about. But: on two of four local development systems, it is not possible to use the existing portal session, i.e., if we load one of these portlets, they don't receive any data, the AJAX request is rejected with a 401 (Unauthorized). Via Postman we explored that OAuth2 and even BasicAuth (https://username:password@localhost:8080/...etc...) are working anyway. Further research showed up, that the 401 is not caused by Liferay (so the access to our endpoint is allowed), but that not the current user is set but the Liferay default user, so that our own security checks reply with 401.

Until now, we were not able to figure out what's the difference between our development systems, the general setup (IDE, Liferay version, workspace, ...) should be absolutely the same. Obviously, we are missing something or overlook a change we did, and you can imagine that we afraid to accidentally cause this issue on our live system as well (or even worse: that our misunderstanding how the authentication should work may lead to security issues).

Thus, our question is: Does somebody know what may cause this issue? Why is the existing user session used on two of our systems, while two other systems automatically choose the Liferay default user? Can you give us a hint were to continue our research? Of course, I add all the information we already collected below.

Any help or hint is appreciated. Thanks in advance!
André


1. The JAX-RS Application
@Component(immediate = true,
        property = {"javax.portlet.resource-bundle=content.Language",
                JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/community-rest-service",
                JaxrsWhiteboardConstants.JAX_RS_NAME + "=community.rest", "oauth2.scopechecker.type=annotations"},
        service = Application.class)
public class CommunityRestServiceApplication extends Application {
    
    @Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED,
            unbind = "-")
    protected void setModuleServiceLifecycle(ModuleServiceLifecycle moduleServiceLifecycle) {
        
        // Empty body, module lifecycle is set by the annotation.
        // This is a workaround to load this module after the portal has initialized.
    }
    
    @Override
    public Set<object> getSingletons() {
        
        Set<object> singletons = new HashSet&lt;&gt;();
        
        singletons.add(new JacksonJaxbJsonProvider());
        
        singletons.add(new PartialJsonObjectPatchReader());
        
        singletons.add(new BlogsEntryAPIServiceImpl());
        singletons.add(new EventAPIServiceImpl());
        singletons.add(new FiscalperiodAPIServiceImpl());
        singletons.add(new MemberAPIServiceImpl());
        singletons.add(new MembershipTypeAPIServiceImpl());
        singletons.add(new MyAccountAPIServiceImpl());
        singletons.add(new ResourceAPIServiceImpl());
        singletons.add(new UserAPIServiceImpl());
        
        return singletons;
    }
    
}<strong>2. Endpoints Interface Example</strong><br><br><pre><code>@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Response getBlogsEntries(@Context HttpServletRequest httpServletRequest, @Context SecurityContext securityContext,
        @QueryParam("groupId") long groupId, @DefaultValue("1") @QueryParam("page") int page,
        @DefaultValue("10") @QueryParam("count") int count);</code></pre><strong>3. Getting user details while executing</strong><br>Of course, we tried to debug our existing solution. By this, as already explained, we realized, that the connection is not denied by Liferay itself. Within our endpoint, we added some more security checks to make sure this is not just a valid user session, but the user is allowed to view the requested content as well.<br>On our working systems, we can get the the correct user ID within our endpoints:<br><br><pre><code>long requestingUserId = Long.[i]parseLong[/i](securityContext.getUserPrincipal().getName());</code></pre>On the broken systems, this always leads to the "Liferay default user", who has no permissions granted on any site or content. So, our permission checks lead to the 401.<br><br><strong>4. Service Access Policies</strong><br>Because of what I described before, we checked our Service Access Policies. But also there we could not identify anything odd.<br>SYSTEM_DEFAULT and SYSTEM_USER_PASSWORD show the default setup:<br><br><img src="/documents/portlet_file_entry/14/image--tempRandomSuffix--gEClRCqJ.png/ce53165d-0e10-c2ae-6bc7-7cc61eb211e4"><img src="/documents/portlet_file_entry/14/image--tempRandomSuffix--noRmeSLe.png/6318ddbc-5abb-2d1d-7c82-424191c87d91"><br>My understanding is, that if our service would be captured by SYSTEM_DEFAULT and not by SYSTEM_USER_PASSWORD, this may cause the describe behavior, but as you can see, this is not the case.<br>The only Service Access Policies with "Default" set to true are ASSET_ENTRY_DEFAULT, CALENDAR_DEFAULT&nbsp;and SYSTEM_DEFAULT, so this is as expected.<br><br><strong>5. Another JAX-RS service</strong><br>Within another module, we define a second JAX-RS service. <strong>I don't think this causes the issue</strong> (because it is also deployed on the working systems), but just to be sure I don't miss anything, I add some information about this one as well.<br><br>This service is intended to authenticate via Basic Auth, because it delivers a iCalendar file (*.ics), and calendar software like Outlook, Apple Calendar, etc. should be able to subscribe to this calendar. The current setup is not yet working as expected (this is not topic of this thread, for this I'll create a separate one ;-)), but the Component annotation look like this:<br><pre><code>@Component(immediate = true,
        property = {"javax.portlet.resource-bundle=content.Language",
                JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE + "=/upcoming-events-rest-service",
                JaxrsWhiteboardConstants.JAX_RS_NAME + "=upcoming-events.rest",
                "auth.verifier.auth.verifier.BasicAuthHeaderAuthVerifier.basic_auth=true"
        },
        service = Application.class)</code></pre></object></object>
thumbnail
David H Nebinger, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Liferay Legend Posts: 14919 Join Date: 9/2/06 Recent Posts
Are you using sticky sessions at the LB layer?
thumbnail
André Bräkling, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
David H Nebinger:

Are you using sticky sessions at the LB layer?
No, we are not running a clustered environment. And, until know, the issue occurs on two local development setups only (running as docker containers, but also here: same for all four development setups), not on our live server instance.
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session (Answer)

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
Just guessing here:
Have you tried to delete osgi/state? I had some pretty weird effects, especially on development systems, and they vanished after deleting osgi/state.
We fetch the user always this way:
@Component(immediate = true,
property = {
        JaxrsWhiteboardConstants.JAX_RS_EXTENSION + "=true",
        JaxrsWhiteboardConstants.JAX_RS_NAME + "=DCCS.UserContextProvider",
        JaxrsWhiteboardConstants.JAX_RS_APPLICATION_SELECT + "=(osgi.jaxrs.name=DCCS.*)"
    }
)
@Provider
public class UserContextProvider implements ContextProvider<user> {

  @Reference(target = ModuleServiceLifecycle.PORTAL_INITIALIZED)
  ModuleServiceLifecycle _portalInitialized;

  private static final Log LOGGER = LogFactoryUtil.getLog(UserContextProvider.class);
  @Reference private Portal portal;

  @Override
  public User createContext(Message message) {
    try {
      return portal.getUser((HttpServletRequest) message.getContextualProperty("HTTP.REQUEST"));
    } catch (PortalException portalException) {
      if (LOGGER.isWarnEnabled()) {
        LOGGER.warn("Unable to get user", portalException);
      }

      return null;
    }
  }
}
</user>

It attaches itself to all  of our modules since we pretty much need the user in all modules. The developers can then just add @Context User user to the functions and it gets automatically injected. Works real well for us.
Maybe you can try that? Or simpler, just use portal.getUser((HttpServletRequest) since you already inject the request.
thumbnail
André Bräkling, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
Christoph Rabel:

Just guessing here:
Have you tried to delete osgi/state? I had some pretty weird effects, especially on development systems, and they vanished after deleting osgi/state.
Thanks, I tried this, but sadly it did not help. I'll also have a look at you UserContextProvider  today, even if only to be sure that not our way to fetch the user causes the issue. After testing, I'll post a feedback, of course!
thumbnail
André Bräkling, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
We fetch the user always this way:
[...]
It attaches itself to all  of our modules since we pretty much need the user in all modules. The developers can then just add @Context User user to the functions and it gets automatically injected. Works real well for us.
Maybe you can try that? Or simpler, just use portal.getUser((HttpServletRequest) since you already inject the request.
Awesome, looks like this really solves the issue. We will do some further testing on the second affected system, afterwards I'll come back with a final feedback. Thanks a lot until here!
thumbnail
André Bräkling, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Junior Member Posts: 30 Join Date: 7/8/13 Recent Posts
André Bräkling:

Awesome, looks like this really solves the issue. We will do some further testing on the second affected system, afterwards I'll come back with a final feedback. Thanks a lot until here!
Just want to confirm: Using our own UserContextProvider completely solves the issue. Thanks a lot!
But to be honest: I don't really get why getting the User ID from the security context does not (always) work. Further information on this would be appreciated ;-)
thumbnail
Tomáš Polešovský, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Liferay Master Posts: 676 Join Date: 2/13/09 Recent Posts
Hi guys,
Just a friendly warning. Please make sure you are not vulnerable to CSRF.  The way you implemented it you depend on a portal session, unless you have other mitigation in place this is not that good as it looks.See how it's solved in portal: https://github.com/liferay/liferay-portal/blob/b3173da81b62430f7150b695d5e944b224279580/modules/apps/portal-security/portal-security-auth-verifier/src/main/java/com/liferay/portal/security/auth/verifier/internal/portal/session/PortalSessionAuthVerifier.java#L68-L88Thanks and stay secure! ;)
Mirko Romstadt, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

New Member Posts: 17 Join Date: 7/23/14 Recent Posts
Hi Tomáš,
Thanks for your reply! Could you elaborate how we should do that? Our current implementation looks like that:


@Component(immediate = true,
        property = { JaxrsWhiteboardConstants.JAX_RS_EXTENSION + "=true",
                JaxrsWhiteboardConstants.JAX_RS_NAME + "=kex.user.context.provider",
                JaxrsWhiteboardConstants.JAX_RS_APPLICATION_SELECT + "=(osgi.jaxrs.name=*)" })
@Provider
public class UserContextProvider implements ContextProvider<user> {
    
    @Reference
    private Portal portal;
    
    @Override
    public User createContext(Message message) {
        
        try {
            return portal.getUser((HttpServletRequest) message.getContextualProperty("HTTP.REQUEST"));
        } catch (PortalException portalException) {
            throw new ServerErrorException(500, portalException);
        }
    }
}
</user>


This is pretty close to what we found in the Liferay source code: https://github.com/liferay/liferay-portal/blob/bb772bfe35c1ea3624d71970178d1483b48e1fc5/modules/apps/portal-vulcan/portal-vulcan-impl/src/main/java/com/liferay/portal/vulcan/internal/jaxrs/context/provider/UserContextProvider.java

The code you posted describes an implementation of AuthVerifier while we are implementing a ContextProvider. Is it also necessary to check for CSFR vulnerability in our UserContextProvider implementation or should we even implement an own AuthVerifier?

Thanks for your help!

Greetings,
Mirko
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
No, you don't need any checks in the UserContextProvider. It just injects the user and it's better to keep the responsibilities of modules as clean and small as possible (IMHO).
The idea behind CSFR is to prepare a specific, malicious url and then trick an admin (or sufficiently privileged user) to click on that url. Since the admin is authenticated, the user is correct and the call is successful. To prevent such an attack, a second secret token has to added by the calling javascript application into the request.
Liferay already provides a javascript variable "Liferay.authToken" and uses it when you use the Liferay.Service(...) method.
You can use it too. You can either add a parameter p_auth with  the token as value to your call or set a header "X-CSRF-Token" (that's what we usually do since it can be nicely configured to be added in each request by the library we use, Axios, I think). Anyway, both methods should work. When you do that, that second secret is added to the request.
Then you can either directly call AuthTokenUtil.checkCSRFToken(...) or add an annotation and Liferay adds the verifier to your module (something with AuthVerifier). I am not sure why, but we use the first method in our modules.
--
Another note: Another way would be to use OAuth and secure your application that way. But that's only interesting if your application is not in Liferay, but some external page. On a Liferay page, I would use the process above.
Mahmoud Mahmoud, modified 2 Years ago.

RE: REST API (JAX-RS) and authentication via Portal Session

New Member Posts: 2 Join Date: 10/7/21 Recent Posts

Excuisme can you tell me How you authenticate your jax-rs by session. Because now I am facing problem where I have react js portlet and I have jax-rs service in same liferay host. And I want to call this jax-rs from react js but I don't know how to authenticate this rather using oAuth 2 which I don't want to use it because it redirect user to login screen.