back to all blogsSee all blog posts

Securing Open Liberty apps and microservices with MicroProfile JWT and Social Media login

image of author
Bruce Tiffany on Aug 29, 2019
Post available in languages:

Open Liberty now includes more security features to enable you to secure your apps with external security providers. Let’s take a quick look at why and how to use some of these features.

Delegating authentication

Delegating user authentication to an external security provider can offer several advantages: it ensures that the secured app never sees the user’s password; it relieves developers and administrators of the effort of managing user accounts; it can provide a single sign-on experience to users that enables them to log in once for all secured apps that they use.

Open Liberty provides the Social Media Login (socialLogin-1.0) feature which developers can use to delegate authentication to external providers. App users can log in using their existing social media credentials from providers such as Facebook, Twitter, GitHub, and others. App developers can also customize configurations for other social media providers that are not included by default. App developers just need to register the app with one or more of these providers and add a few lines to server.xml to secure the app. The Social Media Login feature is an OpenID Connect client (largely based on the openidConnectClient-1.0 feature which can alternatively be used), and is certified by the OpenID Foundation as a Basic RP (Relying Party; an app that uses an external identity provider for authentication).

Open Liberty also supplies the samlWeb-2.0 feature for authenticating with a SAML security provider, and the spnego-1.0 feature for authenticating with Microsoft Active Directory.

Consuming JSON Web Tokens (JWT)

Developers can use the MicroProfile JWT (mpjwt-1.1) feature to secure microservices. JSON web tokens (JWTs) are identity credentials with a standardized format that can be exchanged between processes. They’re secured against tampering by use of digital signatures. These tokens can be submitted to secured microservices to propagate the identity of a logged-in user in an efficient, self-contained way.

Developers can write apps that consume JWTs from an external provider, or they can build and consume their own JWTs using an API with the JWT (jwt-1.0) feature. This enables them to build JWTs for use with microservices.

Configuring Open Liberty as a provider

It is also possible to configure Open Liberty as an OpenID Connect provider. The OpenID Connect Provider (openidConnectServer-1.0) feature can be configured to manage user authentication and to issue JWTs for use by OpenID Connect clients and microservices. The OpenID Connect Provider can work directly with a user registry such as LDAP or act as an identity broker and delegate to another security provider.

Example: Using OpenID Connect and JWTs to authenticate users and secure microservices:

The following sample code and configurations show two web applications to demonstrate authenticating a user, building a JWT and accessing a secured microservice using that JWT. The pieces of this are:

  • A servlet app secured with OpenID Connect is hosted on an Open Liberty server called oidc_client. The server is configured with the Social Media Login feature (socialLogin-1.0), with Google as the designated security provider.

  • A JAX-RS microservice that is secured with MicroProfile JWT is hosted on a server called microservice. The server is configured with the MicroProfile JWT feature (mpJwt-1.1).

An application is first registered with Google, which then supplies the client ID and client secret used in the server.xml (for registration instructions, see Google’s developer docs).

The public certificate from Google is added to the client, and certificates exchanged between client and microservice (for more information, see the WebSphere Liberty Knowledge Center).

When a user attempts to access the servlet, their browser is redirected to Google for them to enter their ID and password. Upon success, the browser gets redirected back to the client with an authorization code. The client then contacts the provider (Google) on a back channel, obtains an access token, then uses it to create an authenticated user on the client, which then gets access to the servlet.

The servlet running on oidc_client then creates a JWT and makes a REST call to the microservice running on microservice. The JWT gets sent along in an HTTP header. The microservice server is configured to trust JWTs issued by oidc_client, so it grants access to the JAX-RS service.

Requesting servlet:

@WebServlet("/")
@ServletSecurity(value = @HttpConstraint(rolesAllowed= {"users"}))
public class MyServlet extends HttpServlet {

    public MyServlet() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter pw = response.getWriter();
        Principal principal = request.getUserPrincipal();
        String jwt = null;
        if (principal != null && principal.getName() != null) {
            try {
                jwt = buildJwt(principal.getName());  // build our own JWT to propagate downstream
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        javax.ws.rs.client.ClientBuilder cb = ClientBuilder.newBuilder();
        pw.append("Calling the jaxrs service and sending jwt...\n");
        javax.ws.rs.client.Client c = cb.build();
        String res = null;
        try {
            res = c.target("https://localhost:39443/jaxrshello")
            .path("/hello").request().header("Authorization", "Bearer " + jwt)
            .get(String.class);
        } catch (Exception e) {
            res = "[Error]:" + e.toString();
        } finally {
            c.close();
        }
        pw.append("The jaxrs service response is: "+ res);
        pw.append("\n");
        pw.flush();

    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    // use jwt-1.0 feature to build jwt
    private String buildJwt(String userName) throws Exception {
        JwtBuilder builder = JwtBuilder.create("myBuilder")
                .jwtId(true)
                .claim(Claims.SUBJECT, userName)
                .claim("upn", userName)
                .claim("groups","users");

        return builder.buildJwt().compact();
    }
}

The JAX-RS microservice:

@ApplicationPath("/")
public class JaxrsHelloApp extends Application {}
@RolesAllowed("users")  // <=== A JWT group can be specified here, or a JEE security role.
@Path("/hello")
public class HelloService {
    @Context
    HttpServletRequest request;

    @GET
    public String hello() {
      DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
      Date date = new Date();
      String principalName = request.getUserPrincipal() == null ?  "null" : request.getUserPrincipal().getName();
      return "Jax-Rs app is accessed.  The current time is: "+ dateFormat.format(date)
         + " and the authenticated user is: "+ principalName;
    }
}

The oidc_client server configuration:

<server description="oidc_client">
    <featureManager>
        <feature>jaxrs-2.1</feature>
        <feature>localConnector-1.0</feature>
        <feature>appSecurity-2.0</feature>
        <feature>socialLogin-1.0</feature>
        <feature>jwt-1.0</feature>
    </featureManager>

    <httpEndpoint host="*" httpPort="19080" httpsPort="19443" id="defaultHttpEndpoint"/>

    <keyStore id="defaultKeyStore" password="keyspass"/>

    <!-- add your client ID and secret from Google -->
    <googleLogin clientId="your_client_id_from_Google_goes_here"
        clientSecret="your_client_secret_from_Google_goes_here"/>

    <jwtBuilder expiresInSeconds="600" id="myBuilder" issuer="https://example.com" keyAlias="default"/>

    <webApplication id="myservlet" location="myservlet.war" name="myservlet" type="war">
        <application-bnd>
            <security-role name="users">
                <special-subject type="ALL_AUTHENTICATED_USERS"/>
            </security-role>
        </application-bnd>
    </webApplication>

    <applicationManager autoExpand="true"/>
    <applicationMonitor updateTrigger="mbean"/>
</server>

The microservice server configuration:

<server description="microservice">

    <featureManager>
        <feature>transportSecurity-1.0</feature>
        <feature>jaxrs-2.1</feature>
        <feature>localConnector-1.0</feature>
        <feature>mpjwt-1.1</feature>
    </featureManager>

    <!-- configure mpjwt feature to trust jwts from oidc client -->
    <mpJwt id="mympjwt" issuer="https://example.com"
        jwksUri="https://localhost:19443/jwt/ibm/api/myBuilder/jwk"/>

    <keyStore id="defaultKeyStore" password="keyspass"/>

    <httpEndpoint httpPort="39080" httpsPort="39443" id="defaultHttpEndpoint"/>

    <applicationMonitor updateTrigger="mbean"/>
    <applicationManager autoExpand="true"/>

    <webApplication id="jaxrshello" location="jaxrshello.war" name="jaxrshello"/>
</server>

If you want to examine the tokens from the provider, or perhaps send the provider’s JWT downstream instead of building your own, the tokens can be accessed using the com.ibm.websphere.security.social.UserProfileManager and com.ibm.websphere.security.social.UserProfile APIs documented in the UserProfileManager Javadoc.

This concludes our brief tour of OpenID Connect and JWT in Open Liberty.