{{announcement.body}}
{{announcement.title}}

Building a Secure REST API with OpenID Connect

DZone 's Guide to

Building a Secure REST API with OpenID Connect

In this article, we’ll take a look at building a secured REST API by integrating with Okta as the identity provider via OpenID Connect (OIDC).

· Microservices Zone ·
Free Resource

Introduction

In this article, we’ll take a look at building a secured REST API by integrating with Okta as the identity provider via OpenID Connect (OIDC). This article is based on the DZone article Building a Java REST API with Quarkus, which explains how to create a Java REST API with Quarkus and Okta. We will be implementing a similar scenario here by using Ballerinalang, and show how it’s simpler and more straightforward to implement compared to our Java counterpart. 

Prerequisites

  • Ballerina Installation(>= v1.2.6)
    • Verify the installation by typing “ballerina -v” in the command line. This should output the currently installed Ballerina version. 

  • Okta Developer Account: An Okta developer account can be created by navigating to https://developer.okta.com/
  • CURL or another suitable HTTP client for your respective environment.

Hello World Ballerina Service

Let’s start off by creating a simple hello world service application as our base scenario. Add the following code to a file named hello.bal.

hello world

Listing 1: Hello World Service

The above service can be run by using the following command:

Shell
 




x


1
$ ballerina run hello.bal
2
 
          
3
[ballerina/http] started HTTP/WS listener 0.0.0.0:8080



The final source code of our hello world service can be found here

Let’s invoke the service by sending a request. 

Shell
 




xxxxxxxxxx
1


1
$ curl http://localhost:8080/secured/hello
2
Hello Anonymous, authScheme: N/A



Here, the service is invoked through HTTP without any form of user authentication. 

A Secured Greeting

Let’s update our hello world service in order to authenticate users who invoke it using a JWT.

JWT

Listing 2: Secured Hello World Service with JWT

We have done several new things to our earlier service implementation. First, our HTTP listener that was bound to the service construct HelloService has been changed from new http:Listener(8080) to httpsListener. Earlier we created an anonymous HTTP listener object in place, and now, we have created a separate HTTPS listener object and referred to it from the service. This approach is required when we want to provide additional configurations to the listener compared to the inline creation. A Ballerina service is not limited to having only one listener, but it can have multiple compatible listeners attached to a single service at a time. The listener object simply needs to be given as a comma-separated list.

HTTP listener objects provide the functionality to associate a set of authentication and authorization providers. In our case, we have created a single JWT inbound auth provider and registered as an auth handler in the HTTPS listener configuration. In Ballerina, it is mandated that a secure transport should be used in an authentication scenario such as in our case. This is where a bearer token is sent through the headers and would be susceptible to a man-in-the-middle attack if a secured protocol is not used. 

Figure 1 shows a summary of the relationship between the Ballerina services, listeners, and authentication providers. 

ballerina-service

Figure 1: Ballerina Service-Listener-Authentication Provider Relationship

Let’s try invoking the updated service above. 

Shell
 




xxxxxxxxxx
1


1
$ curl -k https://localhost:8443/secured/hello
2
Authentication failure.



As we see above, the invocation to the updated service resource fails with an authentication failure. The service returns an HTTP 401 Unauthorized message. This can be checked by passing in the -v switch to the CURL command above. 

The truststore.p12 and the keystore.p12 files are keystore files used in the HTTPS communication. For the purpose of this demo, I’ve copied them from Ballerina default keystore files, which can be found at ${BALLERINA_HOME}/bre/security/. ${BALLERINA_HOME} can be found by executing ballerina home in the command line. For JWT signature verification, the public keys are provided using the JSON Web Key Set (JWKS) format. This is shown at line 9 in Listing 2. 

In order to access the service, we have to send a JWT with the service request to authenticate the user. Let’s see how this can be generated using OIDC with Okta. 

OIDC Application Integration With Okta

In this section, we will use our Okta developer account to create a new OIDC application, and then generate a JWT in order to invoke our secure service. These are the steps you need to follow:

  • Navigate to your domain by clicking on the top-right menu and selecting Your Org
  • Click on Applications and then Add Application
  • Select the application type Web 
  • Provide a name, e.g., Ballerina Demo
  • Update the Login redirect URIs with “https://oidcdebugger.com/debug
  • Under Grant type allowed set Implicit (Hybrid)
  • Leave the rest with the default values
  • Click Done
  • Note down the Client ID

We will also add some custom scopes to our authorization server in order to use them in our services. 

  • Click the top menu API -> Authorization Servers
  • Select default and click Scopes
  • Add the new scopes greet, products_access, products_add, and products_delete

The updated scopes will look similar to Figure 2. 

okta

Figure 2: Okta Authorization Server Scopes

Now, we can execute an OIDC request in order to create an access token to authenticate users for our service. Let’s navigate to https://oidcdebugger.com/

implicit flow

Figure 3: OIDC Request Generation with https://oidcdebugger.com/

As shown in Figure 3, fill in the <your-okta-domain> and <your-client-id> fields with your respective values. Note that we have provided the scopes openid, email, profile, and greet, which is required for our service resource. After the fields are filled, click Send Request and you will be presented with a screen similar to Figure 4. 

successful flow

Figure 4: OIDC Response with the JWT Access Token

Copy the access token that is generated, and set it as a shell variable. 

Shell
 




xxxxxxxxxx
1


 
1
$ TOKEN=eyJraWQiOiI3...



Now that we have a JWT token to authenticate the user from Okta, we will be able to use this with our service to do the authentication.

Shell
 




xxxxxxxxxx
1


 
1
$ curl -k -H "Authorization: Bearer $TOKEN" https://localhost:8443/secured/hello
2
Hello lafernando@gmail.com, authScheme: jwt groups: Everyone



As we can see above, now the service invocation succeeds, since we provided a valid JWT token. Also, in our service implementation, we have stated that we require the greet scope in order to invoke the hello resource in the service.  We can create another access token using https://oidcdebugger.com/ without this scope and see that we cannot invoke the service resource anymore. 

Data Service With Access Control

Now that we know the generation functionality of using OIDC in creating the JWT for our services, let’s create another general scenario for using access control in our service. We will be creating a simple data service that represents a product catalog, where we will control the reading and writing operations of it based on the authenticated user’s privileges. For this, we will be using the scopes products_add, products_access, and products_delete

Listing 3 shows the implementation of this data service.

product catalogue

Listing 3: Product Catalog Data Service

Here, we can see we have simply mapped the service resources with the service paths and respective HTTP methods in order to define the functionality. Each resource requires a specific scope in order to invoke the resource. In this manner, we can have a fine-grained access control mechanism when defining the operations in our system. 

The service above can be tested by creating tokens with different scope combinations to verify the access control features. 

Sample Run

The full source code of our product catalog data service can be found here

Shell
 




xxxxxxxxxx
1
27


1
$ ballerina run product-catalog-service.bal
2
[ballerina/http] started HTTPS/WSS listener 0.0.0.0:8443
3
 
          
4
$ curl -d '{"id":"id1","name":"Pixel 4 XL","price":899.0}' -k -H "Authorization: Bearer $TK_GREET" https://localhost:8443/ProductCatalog/product
5
Authorization failure.
6
 
          
7
$ curl -d '{"id":"id1","name":"Pixel 4 XL","price":899.0}' -k -H "Authorization: Bearer $TK_PRODUCT_ACCESS" https://localhost:8443/ProductCatalog/product
8
Authentication failure.
9
 
          
10
$ curl -d '{"id":"id1","name":"Pixel 4 XL","price":899.0}' -k -H "Authorization: Bearer $TK_PRODUCTS_ADD" https://localhost:8443/ProductCatalog/product
11
 
          
12
$ curl -d '{"id":"id2","name":"Pixel 3","price":599.0}' -k -H "Authorization: Bearer $TK_PRODUCTS_ADD" https://localhost:8443/ProductCatalog/product
13
 
          
14
$ curl -k -H "Authorization: Bearer $TK_GREET" https://localhost:8443/ProductCatalog/product
15
Authorization failure.
16
 
          
17
$ curl -k -H "Authorization: Bearer $TK_PRODUCTS_ACCESS" https://localhost:8443/ProductCatalog/product
18
[{"id":"id1", "name":"Pixel 4 XL", "price":899}, {"id":"id2", "name":"Pixel 3", "price":599}]
19
 
          
20
$ curl -X DELETE -k -H "Authorization: Bearer $TK_PRODUCTS_ACCESS" https://localhost:8443/ProductCatalog/product/id1
21
Authorization failure.
22
 
          
23
$ curl -X DELETE -k -H "Authorization: Bearer $TK_PRODUCTS_DELETE" https://localhost:8443/ProductCatalog/product/id1
24
 
          
25
$ curl -k -H "Authorization: Bearer $TK_PRODUCTS_ACCESS" https://localhost:8443/ProductCatalog/product
26
[{"id":"id2", "name":"Pixel 3", "price":599}]



Summary

In this article, we have looked into how we can secure a REST service written in Ballerina using Okta as the identity provider. We have used OIDC when generating a JWT access token to access our service resources. 

For more information on writing microservices in Ballerina, check out the following resources: 

Topics:
ballerina, microservice, okta, open id connect, rest api, security, tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}