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

Serverless: The New Frontier of Web Services. Do or Don’t?

DZone 's Guide to

Serverless: The New Frontier of Web Services. Do or Don’t?

In this tutorial you created a simple serverless function using Amazon Web Services, Java, and Gradle, integrate AWS with Gradle, and secure with Okta.

· Cloud Zone ·
Free Resource

Cloud management has come a long way, beginning with physical hardware, and then moving servers to the cloud.  It seems likely that the next logical step is to run code serverless! Without needing to manage hardware or depend on infrastructure providers like Google and Amazon, serverless can run code in a managed service container, completely unaffected by server-level issues. By utilizing this new step, you won’t have to worry about operating systems, web servers, or even updates. 

Although it seems like a no-brainer, there are a number of things you both lose and gain when switching over. The benefits alone should be enough to get you interested; you do the work, create an application, and once your code is uploaded you are promised simplicity, low cost, and automatic scaling. Due to the fact that the service provider is automatically managed, scaling is much faster. In addition to this, you only pay for what you use, cutting back on the normal cost, and saving you or your company money. This situation seems ideal, but there are also drawbacks to serverless that can complicate your decision. 

Amazon AWS, with all of its benefits, is extremely complex and is not in any way a ‘simple’ platform. Designed for tech specialists, AWS gives the user enough control to fine-tune the platform to the very smallest decisions, making it confusing to navigate for a first time (or just inexperienced) user. This has led to the inclusion of AWS ‘gurus’ in startups that need the full range of AWS and the capability to use it within their brand.

Even with the initial simplicity, learning how to navigate the service provider's system can be just as costly. Without the familiarity of tech like Apache, Linux, or Nginx, the new system has a cloud infrastructure similar to Amazon or Google. Serverless functions are private, meaning that in order for them to become public they need to go through Gateway APIs, adding even more to the once ‘simple’ platform. This complexity will only increase as the server application grows, making the propriety interaction even more complex as well. By the time your platform is able to perform how you want it to, your team will have to become specialized in an entirely new system, which more often than not is just as complicated as systems they were already familiar with. You also will begin to see the limitations as you become aware of the conditions of your specific cloud provider, trapping you, and canceling out the benefits you reaped from a serverless platform in the beginning. 

With all of this in mind, there are ways that I can see serverless platforms being actually beneficial. First, units of code that are discrete, and not likely to grow but that are used unpredictably, and thus utilizing the pay-as-you-go design. Second, any code that has a variety in its use/demand, and therefore benefits from the larger scaling capacities of serverless tech. 

With the function of serverless platforms being stateless, the run times are shorter than the norm, however, if you need to write a disk, serverless cannot be utilized. (This shouldn’t pose an issue as databases, cloud logging and cloud file repositories render this issue almost obsolete). Serverless systems work best around short-running code, so the longer your code runs, the less you gain from the ‘pay-as-you-go’ model, while most providers have a max execution time between 300 and 900 seconds.

Table of Contents 


Choose Between Java Serverless Options

Broadly speaking, two main options for running a serverless code base are: 1) to use a commercial function-as-service (FaaS) provider, or 2) use an open-source serverless platform and deploy it in one of the many options for deploying containerized code. The example application you’re going to write in this tutorial uses the first option, specifically Amazon AWS Lambda. But before you get started, I’m going to quickly look at a few of the other options.

One of the major differences between FaaS providers is their Java support. Some commercial FaaS providers and the flavors of Java they currently support are:

  • Amazon AWS Lambda - Java 8 and Java 11
  • Google Cloud Functions - Java 8 in alpha and Java 11 on App Engine public beta
  • Microsoft Azure - Java 7, Java 8, and Java 11 (Java 13 in technical preview)
  • IBM Cloud Functions - Java 8

The pricing on all of these services is very similar. I looked up the various pricing pages and used their calculators before finding this helpful page: the Serverless Cost Calculator. Assuming 3 million executions per month, 512 MB of memory, and 1 second execution time, the estimated cost for the providers would be:

  • Amazon AWS Lambda - $18.74
  • Google Cloud Functions - $25.15
  • Microsoft Azure - $18.00
  • IBM Cloud Functions - $18.70

Because cloud functions often work in a whole ecosystem of online services, probably more significant than cost is going to be what other services you’d like to use and which company you trust to be tied to as your project grows.

Another option, which neatly avoids the problem of vendor-lock-in, but does so at the cost of some increased operational complexity, is to use a cloud container provider combined with one of the great open-source, serverless platforms. These tools allow you to essentially roll your own serverless service packaged in a Docker container (for which there is a cornucopia of hosting solutions).

I’ll just mention a few possible solutions here. We’ll take a look at some of these in future articles.

  • Apache OpenWhisk
  • Oracle Fn
  • Micronaut

Finally, I’ll just point out that there are hybrid/meta solutions like Serverless.com that bridge open-source and pay-for-service models with managing deployment to multiple systems.

In this project, you’re going to create a simple Amazon AWS Lambda function. You’re going to create an API Gateway for the function to make it publicly accessible via HTTP requests. Finally, you’re going to secure the function using Okta and JSON Web Token authentication.

AWS is a good introduction to these kinds of services because it highlights some of the trade-offs in anything-as-service. Often when you offload complexity to a vendor, you either lose flexibility or you simply shift complexity from a system you know to a system you don’t. In this case, AWS is incredibly powerful, and once you get to know it, it can be great. But it’s definitely an expert system in its own right and sometimes daunting if you’re not familiar with it. Fortunately, it’s very well documented and there’s a huge user base, so answers are generally easy to find.

Prerequisites:

  • Java 11: This tutorial uses Java 11. OpenJDK 11 works well. You can find instructions on the OpenJDK website. You can install OpenJDK using Homebrew. Alternatively, SDKMAN is another great option for installing and managing Java versions.
  • Okta Developer Account: You’ll be using Okta as an OAuth/OIDC provider to add JWT authentication and authorization to the application. You can go to our developer site and sign up for a free developer account.
  • HTTPie: This is a powerful command-line HTTP request utility that you’ll use to test the server. Install it according to the docs on their site.
  • Amazon Web Services account with billing activated. The costs for this account should be minimal, if not free, but AWS required you to have billing activated to use the Lambda service. Instructions on how to set up an AWS account are below.

Sign Up for AWS Account with Billing

The first thing you need to do is create an Amazon AWS account and activate billing. There are detailed instructions on the Amazon Web Services website. The steps are outlined briefly below:

  • Go to the Amazon Web Services home page
  • Click Create an AWS Account
  • Enter your account information and click Continue
  • Choose Personal or Professional and enter the necessary information (either way works)
  • Read and accept the AWS Customer Agreement
  • Click Create Account and Continue

You’ll get an email confirming your new account. Now you need to sign in to the account and add a payment method. You also need to verify your account using either a text message or an automated phone call.

Account activation usually only takes a few minutes but can take up to 24 hours. Until your account is activated, you won’t be able to use AWS services.

Create AWS Access Keys

For part of this tutorial, you will use the AWS Command Line Interface (CLI). For this to work, you need to generate access keys.

  • From the AWS dashboard, go to Your account (your name) and select My Security Credentials
  • Expand Access keys (access key ID and secret access key) tab
  • Click on Create New Access Key

Save your access key ID and access key somewhere (you’ll need them in just a moment).

Install and Configure AWS CLI

The AWS CLI is incredibly full featured. You can do nearly everything from the CLI that you can do from the dashboard. You could probably spend an entire career learning how to use all the features. You’re going to install AWS CLI version 2 (version 1 is still supported, but outdated). To install the CLI, follow the instructions for your operating system from the AWS documentation, or use the commands below.

On a Mac, you should be able to run the following commands from a shell to install the AWS CLI:

Java
 




x


 
1
$ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
2
$ sudo installer -pkg AWSCLIV2.pkg -target /



And on Linux:

Java
 




xxxxxxxxxx
1


1
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
2
unzip awscliv2.zip
3
sudo ./aws/install



Windows users should just download the graphical installer here. No command-line for you!

Once the CLI has been properly installed, you need to configure it. This is where you need the access keys you generated above.

Run aws configure to enter your credentials into the AWS CLI. In this tutorial, I’m going to use region us-west-2. Feel free to use whatever region you like, but just remember that you’ll have to change it at various places throughout the tutorial.

Java
 




xxxxxxxxxx
1


 
1
$ aws configure
2
AWS Access Key ID [****************7D7A]: {yourAwsAccessKeyId}
3
AWS Secret Access Key [****************I2nP]:  {yourAwsSecretAccessKey}
4
Default region name [us-west-2]: us-west-2
5
Default output format [None]:



You can test your configuration by running:

Java
 




xxxxxxxxxx
1


1
aws sts get-caller-identity



You should see something like:


Java
 




xxxxxxxxxx
1


1
{
2
    "UserId": "12345123451",
3
    "Account": "12345123451",
4
    "Arn": "arn:aws:iam::12345123451:root"
5
}



Create AWS Role

The lambda must be executed with a role that defines its permissions. Execute the commands below to create a role named lambda-ex and attach the predefined AWSLambdaBasicExecutionRole role policy.

Create the role:

Java
 




xxxxxxxxxx
1


 
1
aws iam create-role --role-name lambda-ex \
2
--assume-role-policy-document \
3
'{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'



It should output something like this:

Java
 




xxxxxxxxxx
1
22


 
1
{
2
    "Role": {
3
        "Path": "/",
4
        "RoleName": "lambda-ex",
5
        "RoleId": "AROAT7HGYFHI2ERFCZJ3H",
6
        "Arn": "arn:aws:iam::273214351825:role/lambda-ex",
7
        "CreateDate": "2020-04-09T19:54:33+00:00",
8
        "AssumeRolePolicyDocument": {
9
            "Version": "2012-10-17",
10
            "Statement": [
11
                {
12
                    "Effect": "Allow",
13
                    "Principal": {
14
                        "Service": "lambda.amazonaws.com"
15
                    },
16
                    "Action": "sts:AssumeRole"
17
                }
18
            ]
19
        }
20
    }
21
}
22
 
          



Notice the field Role.Arn (something like this arn:aws:iam::273214351825:role/lambda-ex). Save this value somewhere. You’re going to need it a little later.

Attach the role policy:

Java
 




xxxxxxxxxx
1


 
1
aws iam attach-role-policy --role-name lambda-ex --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole



Download the Project from GitHub

Download the code from this tutorials’s GitHub repository.

The build.gradle file has a few notable features I want to point out. First, notice the buildZip task:

Java
 




xxxxxxxxxx
1


 
1
task buildZip(type: Zip) {
2
    from compileJava
3
    from processResources
4
    into('lib') {
5
        from configurations.runtimeClasspath
6
    }
7
}
8
build.dependsOn buildZip



This task creates the Zip file that AWS lambda requires for deployment. The output of this task is what you’ll upload to the AWS servers. There’s also a line that defines the buildZip task as a dependency on the build task. This insures that the Zip file for deployment is built when the build task is run.

There also a deploy task that uses the Gradle AWS plugin to allow us to upload the zipped project using Gradle.

Java
 




xxxxxxxxxx
1


 
1
task deploy(type: AWSLambdaUpdateFunctionCodeTask) {
2
    functionName = lambdaName
3
    zipFile =  file(buildZip.archiveFile)
4
}



You won’t necessarily need the deploy task for this tutorial, but it was super handy for me while writing this tutorial and I thought I’d leave it in there to show you how to integrate deploying the lambda into Gradle.

Finally, looking at the dependencies, you’ll notice two dependencies for the Okta JWT Verifier, which is what you’ll use to validate JSON Web Tokens. To learn more, take a look at the Okta JWT Verifier for Java GitHub page.

Java
 




xxxxxxxxxx
1


 
1
dependencies {
2
    ....
3
    implementation 'com.okta.jwt:okta-jwt-verifier:0.4.0'
4
    implementation 'com.okta.jwt:okta-jwt-verifier-impl:0.4.0'
5
    ...
6
}



The code in src/main/java/com/okta/serverless/awslambda includes three classes:

  • Config.java: loads our OAuth configuration from src/main/resources/config.properties
  • LambdaInput: JSON-mapped model class for function input
  • Handler.java: the handler function itself, run by AWS Lambda

Config.java is pretty simple. It loads config.properties from the classpath and loads our four OAuth properties from the file into the instance of the Config object.

src/main/java/com/okta/serverless/awslambda/Config.java

Java
 




xxxxxxxxxx
1
32


 
1
package com.okta.serverless.awslambda;
2
 
          
3
...
4
 
          
5
public class Config {
6
 
          
7
    public final String issuer;
8
    public final String audience;
9
    public final Long connectionTimeoutSeconds;
10
    public final Long readTimeoutSeconds;
11
 
          
12
    Config() throws IOException {
13
 
          
14
        // Create input stream to read config.properties from classpath (src/main/resources)
15
        InputStream input = Config.class.getClassLoader().getResourceAsStream("config.properties");
16
 
          
17
        // Make sure we found it on the classpath
18
        if (input == null) {
19
            throw new IOException("Unable to load config properties.");
20
        }
21
 
          
22
        // Load the file into our properties object
23
        Properties prop = new Properties();
24
        prop.load(input);
25
      
26
       // Save our props into our class properties
27
        this.issuer = prop.getProperty("okta.oauth.issuer");
28
        this.audience = prop.getProperty("okta.oauth.audience");
29
        this.connectionTimeoutSeconds = Long.parseLong(prop.getProperty("okta.oauth.connectionTimeoutSeconds"));
30
        this.readTimeoutSeconds = Long.parseLong(prop.getProperty("okta.oauth.readTimeoutSeconds"));
31
    }
32
}



LambdaInput.java is even simpler. It has one string property named input. We are using this because AWS Lambda requires input and output to be JSON (unless you use the RequestStreamHandler, which allows you to deal with the input and output streams directly) and because Gson, our JSON parser, parses input JSON directly into Java object instances.

The file below really only needs the public String input property, the toString() is just handy for debugging but totally unnecessary.

src/main/java/com/okta/serverless/awslambda/LambdaInput.java

Java
 




xxxxxxxxxx
1
11


1
package com.okta.serverless.awslambda;
2
 
          
3
public class LambdaInput {
4
 
          
5
    public String input = "";
6
 
          
7
    @Override
8
    public String toString() {
9
        return "LambdaInput{input='" + input + "'}";
10
    }
11
}



The last class, where all the action is, is Handler.java. It contains one method, handleRequest(), which is the method executed by the AWS Lambda service. Notice that the class uses two AWS-specific classes for input and output: APIGatewayProxyRequestEvent and APIGatewayProxyResponseEvent. The AWS API Gateway, which is what you’ll use to expose the lambda to the world, is very picky about the format of the event objects it sends and receives. Using these predefined classes saves a lot of headaches.

The flow of the code if pretty simple. In the constructor, the Config class and the JWT verifier are initialized. Notice the line showing you how to get the logger from the context.

Java
 




xxxxxxxxxx
1


 
1
LambdaLogger logger = context.getLogger();
2
 
          



These log events show up in the CloudWatch logs and can be helpful debugging.

The general flow of the code is:

  1. Get the request body
  2. Use Gson to parse the request body to the LambdaInput object
  3. Get the Authorization header and remove the Bearer :  part of the header to extract the token
  4. Verify the JWT using the Okta JWT Verifier
  5. Strip all whitespace from the input string
  6. Populate the APIGatewayProxyResponseEvent object
  7. Return the response

The code handles two exceptions: a JSON parsing exception and a JWT verification exception.


Java
 




xxxxxxxxxx
1
93


1
package com.okta.serverless.awslambda;
2
 
          
3
...
4
 
          
5
public class Handler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
6
 
          
7
    // Parse and create JSON
8
    Gson gson = new GsonBuilder().create();
9
    // OAuth configuration properties
10
    Config config;
11
    // Okta token verifier
12
    AccessTokenVerifier jwtVerifier;
13
 
          
14
    public Handler() throws IOException {
15
        // Get our config properties
16
        this.config = new Config();
17
        // Initialize the Okta JWT verifier
18
        this.jwtVerifier = JwtVerifiers.accessTokenVerifierBuilder()
19
                .setIssuer(this.config.issuer)
20
                .setAudience(this.config.audience) // defaults to 'api://default'
21
                .setConnectionTimeout(Duration.ofSeconds(this.config.connectionTimeoutSeconds)) // defaults to 1s
22
                .setReadTimeout(Duration.ofSeconds(this.config.readTimeoutSeconds)) // defaults to 1s
23
                .build();
24
    }
25
 
          
26
    // Request method called by AWS Lambda service
27
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent request, Context context)
28
    {
29
 
          
30
        // Create the response object (AWS is picky about the response format, so using their event
31
        // objects is helfpul)
32
        APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = new APIGatewayProxyResponseEvent();
33
 
          
34
        try {
35
 
          
36
            // Get the logger from the context
37
            LambdaLogger logger = context.getLogger();
38
 
          
39
            // Get the response body
40
            String body = request.getBody();
41
 
          
42
            // Log the body--'cause, hey, we went through the trouble of getting the logger
43
            logger.log("BODY: " + body);
44
 
          
45
            // Use Gson to pare the response body to our LambdaInput class
46
            LambdaInput inputObj = gson.fromJson(body, LambdaInput.class);
47
 
          
48
            // Get the headers so we can check the auth header
49
            Map<String, String> headers = request.getHeaders();
50
 
          
51
            if (headers == null) {
52
                throw new JwtVerificationException("Authorization header empty");
53
            }
54
 
          
55
            // Get the auth header
56
            String authHeader = headers.get("Authorization");
57
 
          
58
            if (authHeader == null) {
59
                throw new JwtVerificationException("Authorization header empty");
60
            }
61
 
          
62
            String token = authHeader.replaceAll("\\s*Bearer\\s*", "");
63
 
          
64
            logger.log("TOKEN: " + token);
65
 
          
66
            // This verifies the token in the auth header and throws an
67
            // exception if it does't validate
68
            Jwt jwt = jwtVerifier.decode(token);
69
 
          
70
            // Strip the spaces!
71
            String stripped = inputObj.input.replaceAll("\\s", "");
72
 
          
73
            // Set the status code and response
74
            apiGatewayProxyResponseEvent.setStatusCode(200);
75
            apiGatewayProxyResponseEvent.setBody(stripped);
76
 
          
77
            // Log the full response object, just for fun
78
            logger.log(gson.toJson(apiGatewayProxyResponseEvent));
79
 
          
80
            // Return the result
81
            return apiGatewayProxyResponseEvent;
82
        } catch (JsonParseException ex) {
83
            apiGatewayProxyResponseEvent.setStatusCode(400);
84
            apiGatewayProxyResponseEvent.setBody("Failed to parse JSON: " + ex.getMessage());
85
            return apiGatewayProxyResponseEvent;
86
        } catch (JwtVerificationException ex) {
87
            apiGatewayProxyResponseEvent.setStatusCode(403);
88
            // In production, you probably just want to return a 403 and not return the error
89
            apiGatewayProxyResponseEvent.setBody("Invalid Auth: " + ex.getMessage());
90
            return apiGatewayProxyResponseEvent;
91
        }
92
    }
93
}



Build the application using the following command:

Java
 




xxxxxxxxxx
1


 
1
./gradlew build



Remember that because of the buildZip task and the dependency defined on the build task, the build command will also build the Zip file required for deployment to AWS.

Configure Okta JWT Auth

You’re going to use Okta as your OAuth 2.0 & OpenID Connect (OIDC) provider. OAuth 2.0 and OIDC are open standards that together provide a complete authentication and authorization system. Okta provides an implementation of these standards that you’ll use to add JSON Web Token (JWT) authentication to the serverless function.

You should have already signed up for a free developer account with Okta. Navigate to the developer dashboard at https://developer.okta.com. If this is your first time logging in, you may need to click the Admin button.

You need to create an OIDC application.

  1. From the top menu, click on the Application button. Click the Add Application button.
  2. Select application type Web.
  3. Click Next.
  4. Give the app a name. I named mine Okta Serverless Lambda.
  5. Under Login redirect URIs, add a new URI: https://oidcdebugger.com/debug

The rest of the default values will work.

Click Done.

Application settings

Leave the page open or take note of the Client ID. You’ll need it in a bit when you generate a token.

Open src/main/resources/config.properties and fill in the correct value for okta.oauth.issuer using your Okta developer URI. It will look something like dev-123456.okta.com. You can find it by going to your Okta developer dashboard and, from the top menu, selecting API and Authorization servers. You want the Issuer URI for the default authorization server in the table.

Java
 




xxxxxxxxxx
1


 
1
okta.oauth.issuer=https://{yourOktaUrl}/oauth2/default
2
okta.oauth.audience=api://default
3
okta.oauth.connectionTimeoutSeconds=10
4
okta.oauth.readTimeoutSeconds=10



You might notice that you don’t need any of the OIDC application values within the lambda function itself, only the issuer URI. That’s because you don’t need an OIDC application to verify tokens, but you do need one to create tokens. The JWT validator retrieves the public key used to sign the JWT using a well-known endpoint with your issue value as a base.

Create the Lambda

Now you can use the AWS CLI to create the lambda function and upload the code to the AWS servers. This command must be run from the root directory of your Java project (or you can change the relative path to the zip file).

You’ll notice that you’re defining a lot of important properties here.

  • You define the function name as stripSpaces.
  • You tell AWS what the handler function by specifying a class and method.
  • You specify the role the lambda should execute under (thus defining what the lambda is allowed to do).
  • You define a timeout of 30 seconds. The default is 3 seconds. You’re specifying a longer time because when the JWT verifier runs initially, it needs to download public keys and may take longer than 3 seconds.
  • Finally, you increase the memory from the default of 128 to 512 because the JWT verifier needs more than 128 MB to run.

You also need to fill in the Role ARN from the step above (where you created the Lambda Role) in the command below, replacing {yourRoleArn} with something like arn:aws:iam::8324982798:role/lambda-ex.

Java
 




xxxxxxxxxx
1


 
1
aws lambda create-function --function-name stripSpaces \
2
--zip-file fileb://build/distributions/okta-serverless-lambda-1.0-SNAPSHOT.zip \
3
--handler com.okta.serverless.awslambda.Handler::handleRequest \
4
--runtime java11 \
5
--role arn:aws:iam::273214351825:role/lambda-ex \
6
--timeout 30 \
7
--memory-size 512



You should get some output that looks like this:

Java
 




xxxxxxxxxx
1
20


1
{
2
    "FunctionName": "stripSpaces",
3
    "FunctionArn": "arn:aws:lambda:us-west-2:273214351825:function:stripSpaces",
4
    "Runtime": "java11",
5
    "Role": "arn:aws:iam::273214351825:role/lambda-ex",
6
    "Handler": "com.okta.serverless.awslambda.Handler::handleRequest",
7
    "CodeSize": 4854453,
8
    "Description": "",
9
    "Timeout": 30,
10
    "MemorySize": 128,
11
    "LastModified": "2020-04-09T19:59:57.882+0000",
12
    "CodeSha256": "Tnik93nDAd2+AXfYAgK2WA8BCznsIuxiBRLimlfY/6w=",
13
    "Version": "$LATEST",
14
    "TracingConfig": {
15
        "Mode": "PassThrough"
16
    },
17
    "RevisionId": "88fa30d0-4b60-4ac1-a48a-c1c7629f6983",
18
    "State": "Active",
19
    "LastUpdateStatus": "Successful"
20
}



Create an AWS API Gateway

Currently, your lambda has no way of talking with the outside world. Further, it’s written using some custom input and output objects that are specific to the AWS API Gateway. The next step is to create an AWS API Gateway REST API and assign it to proxy the lambda. This next step is much simpler to perform on the AWS console.

The API Gateway allows you to define publicly visible input paths to your lambda function. Of course, there are a bazillion various options and configurations, including complex mapping, versioning, staging, etc… That’s all well beyond the scope of this tutorial, but there is a (sometimes bewildering) cornucopia of documentation on the AWS website about it. In truth, AWS can be a little daunting if you don’t use it regularly. However, it’s super well documented, has a very active community, and generally, the quality of online resources for using it are high.

Open your AWS console and navigate to your lambda’s page. Make sure you’re in the right region, us-west-2, if you followed the tutorial.. From the top menu, select Services and enter Lambda. Click on the stripSpaces lambda listed in the Functions table.

Click Add Trigger.

Adding trigger

Select API Gateway from the drop-down.

Update the following fields:

  • API: Create an API
  • API type: REST API
  • Security: Open

Trigger config

Click Add. You should see a page that looks like this:

Stripspaces


Notice at the bottom is the public URI for your API endpoint.

The general format for the API URIs is: https://{restapi_id}.execute-api.{region}.amazonaws.com/{stage_name}/.

Test Your API Gateway URL

You can test the endpoint by making a request using HTTPie. In the command below, replace {yourGatewayApi} with the URI for the API Gateway you just created. It should look something like this: abcd342x2.execute-api.us-west-2.amazonaws.com/default

Run the request:

Java
 




xxxxxxxxxx
1


 
1
http POST https://{yourGatewayUri}/stripSpaces input="test input"



This should fail with:


Java
 




xxxxxxxxxx
1


1
HTTP/1.1 403 Forbidden
2
...



Your request requires a valid JSON Web Token issued by your Okta authorization server.

Generate a JWT Token

Now you’re going to use the OpenID Connect Debugger to generate a valid JWT that you can use to make a request against your serverless function.

Open https://oidcdebugger.com/.

Follow the below steps to continue:

  • Set the Authorize URI to: https://{yourOktaDomain}/oauth2/default/v1/authorize
  • Copy your Client ID from the Okta OIDC application you created above and fill it in under Client ID
  • Add something for State. It doesn’t matter what. Just can’t be blank. In production code, this is used to protect against cross-site request forgery attacks (CSRF).
  • Leave the default of code selected for Response type. Make sure neither token nor id_token are checked.
  • Scroll down. Click Send Request.

Send request

After you authenticate to your Okta org, Okta will redirect back and you’ll see the Authorization code shown in the browser:

Success

You’ll also see instructions for how to exchange the code for tokens. You’ll can use HTTPie for this. See the command below. Don’t forget to fill in the values in brackets: the authorization code, your Okta domain, your OIDC app client ID, and your OIDC app client secret.

Java
 




xxxxxxxxxx
1


 
1
http -f https://{yourOktaDomain}/oauth2/default/v1/token \
2
grant_type=authorization_code \
3
code=p48uzYGwqG19EeqvagWU \
4
client_id={clientId} \
5
client_secret={clientSecret} \
6
redirect_uri=https://oidcdebugger.com/debug



You should get a JSON response that includes an access token and an ID token.


Java
 




xxxxxxxxxx
1
12


1
HTTP/1.1 200 OK
2
Cache-Control: no-cache, no-store
3
Connection: keep-alive
4
...
5
 
          
6
{
7
    "access_token": "eyJraWQiOiJycGZWTzd4R2hDWmlvUXdrWWha...",
8
    "expires_in": 3600,
9
    "id_token": "eyJraWQiOiJycGZWTzd4R2hDWmlvUXdrWWhaSkph...",
10
    "scope": "openid",
11
    "token_type": "Bearer"
12
}



Copy the resulting JWT Access Token to the clipboard, and in the terminal where you are running your HTTPie commands, save the token value to a shell variable, like so:


Java
 




xxxxxxxxxx
1


 
1
TOKEN=eyJraWQiOiJxMm5rZmtwUDRhMlJLV2REU2JfQ...



Test the Protected Serverless Function

Now, with a valid JWT, you can use it to make a request to the Lambda.

Java
 




xxxxxxxxxx
1


 
1
http POST  https://g8ot0krnve.execute-api.us-west-2.amazonaws.com/default/stripSpaces input="test input" "Authorization: Bearer $TOKEN"



You should see a successful reply:

Java
 




xxxxxxxxxx
1


 
1
HTTP/1.1 200 OK
2
...
3
 
          
4
testinput



Learn More about AWS and Java

All done. In this tutorial you created a simple serverless function using Amazon Web Services, Java, and Gradle. You saw how to simply integrate AWS deployment with a Gradle script. You also saw how to use Okta to secure the serverless function with JSON Web Tokens, OAuth, and OpenID Connect.

You can find the source code for this tutorial on GitHub at oktadeveloper/okta-java-serverless-aws-example.

If you liked this tutorial, chances are you’ll like some of our other ones:

If you have any questions, please leave a comment below. You can also follow us @oktadev on Twitter. We have a popular YouTube channel too—check it out!

Topics:
amazon aws, aws, cloud, cloud storage, okta, serverless

Published at DZone with permission of Andrew Hughes , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}