Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc about securing api keys in lambdas #3380

Merged
merged 8 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 10 additions & 99 deletions astro/src/content/docs/extend/code/lambdas/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ tertcategory: lambdas
topOfNav: true
---
import AdvancedPlanBlurb from 'src/content/docs/_shared/_advanced-plan-blurb.astro';
import APIBlock from 'src/components/api/APIBlock.astro';
import APIField from 'src/components/api/APIField.astro';
import Aside from 'src/components/Aside.astro';
import InlineField from 'src/components/InlineField.astro';
import LambdaTypes from 'src/content/docs/_shared/_lambda-types.astro';
import MembershipLambda from 'src/content/docs/extend/code/_membership-lambda.md';
import { YouTube } from '@astro-community/astro-embed-youtube';


Expand Down Expand Up @@ -51,87 +48,17 @@ function populate(jwt, user, registration) {
}
```

### Using Lambda HTTP Connect
## Using Lambda HTTP Connect

This feature allows you to make HTTP requests from within a lambda.
Lambda HTTP Connect allows you to make HTTP requests from within a lambda.

<AdvancedPlanBlurb />

Here is a FusionAuth lambda that adds additional claims to a JWT based on an HTTP request:
[Learn more about making API calls from lambdas](/docs/extend/code/lambdas/lambda-remote-api-calls).

```javascript title="A lambda which adds claims based on an external API."
function populate(jwt, user, registration) {
var response = fetch("https://api.example.com/api/status?" + user.id, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});

if (response.status === 200) {
// assuming successful response looks like:
// {"status":"statusValue"}
var jsonResponse = JSON.parse(response.body);
jwt.status = jsonResponse.status;
} else {
jwt.status = "basic";
}
}
```

You can also call FusionAuth APIs with a valid API key:

<MembershipLambda />

Use port 9012, or the configured value for `fusionauth-app.http-local.port`, whenever making a FusionAuth API call in a lambda. Doing so minimizes network traffic contention and improves performance.

Here's a video showing more details about Lambda HTTP Connect:

<YouTube id="_TnDUPQm3aQ" />
## Engine

#### Headers

You can provide request header values in a number of different ways:

```javascript title="An anonymous object"
headers: {
"Content-Type": "application/json"
}
```

```javascript title="A hash or map"
headers: new Headers({
"Content-Type": "application/json"
})
```

```javascript title="An array"
headers: new Headers([
["Content-Type", "application/json"]
])
```

#### Response

A response object will be returned. It will have the following fields:

<APIBlock>
<APIField name="headers" type="Object">
The headers returned by the response. The keys of this object are the header names. All header keys are lower cased.
</APIField>
<APIField name="status" type="Integer">
The HTTP status code.
</APIField>
<APIField name="body" type="String">
The body of the response.
</APIField>
</APIBlock>

## JavaScript

### Engine

#### GraalJS
### GraalJS

GraalJS is built on top of the Java virtual machine. For security reasons, FusionAuth restricts access to various GraalJS features during a lambda invocation.

Expand All @@ -142,7 +69,7 @@ Here is documentation for the GraalJS engine:

This engine has been available since version `1.35.0`.

#### Nashorn
### Nashorn

<Aside type="caution">
As of version 1.49 the Nashorn engine has been removed. Upgrading to version `1.49.0` or beyond will migrate existing lambdas to GraalJS.
Expand All @@ -152,7 +79,7 @@ Nashorn is built on top of the Java virtual machine and while Nashorn permits ac

The Nashorn engine supports ECMAScript version 5.1.

### Console
## Console

In addition to the standard JavaScript objects and constructs, FusionAuth provides the `console` object to allow you to create entries in the Event Log during a lambda invocation.

Expand All @@ -179,12 +106,12 @@ function populate(jwt, user, registration) {
}
```

### Exceptions
## Exceptions

Any exception thrown in a lambda does two things:

* write an event log entry
* exit the lambda code path
* writes an event log entry
* exits the lambda code path

What that means for the overall user experience depends on the type of lambda. For example, for a JWT populate lambda, the JWT will not be modified. For a reconcile lambda, the user will not be created or linked.

Expand All @@ -200,22 +127,6 @@ The FusionAuth lambdas do not have full access to JavaScript modules and librari

`console.log` and other `console` methods only take one argument; this differs from the `console` method available in web browsers.

### Lambda HTTP Connect Limitations

<AdvancedPlanBlurb />

When using Lambda HTTP Connect to make HTTP requests, do not call a FusionAuth API which invokes the calling lambda, because it will fail. For example, in a JWT Populate lambda, do not invoke the Login API.

Requests from a lambda require the lambda to use the GraalJS engine. HTTP requests will time out after two seconds.

The `fetch` method in a lambda does not implement the [entire `fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as implemented in a browser.
The first argument to `fetch` must always be a string URL.
Only the following options are supported:

* `method`, which defaults to `GET`
* `headers`, which defaults to null
* `body`, which must be a string

## Managing Lambdas

You can use the [FusionAuth APIs](/docs/apis/lambdas), [client libraries](/docs/sdks) or [the CLI tool](/docs/customize/cli) to manage your lambdas.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: Making API Calls From Lambdas
description: An overview of how to make API calls from Lambdas.
section: extend
subcategory: code
tertcategory: lambdas
topOfNav: true
---
import AdvancedPlanBlurb from 'src/content/docs/_shared/_advanced-plan-blurb.astro';
import APIBlock from 'src/components/api/APIBlock.astro';
import APIField from 'src/components/api/APIField.astro';
import Aside from 'src/components/Aside.astro';
import InlineField from 'src/components/InlineField.astro';
import LambdaTypes from 'src/content/docs/_shared/_lambda-types.astro';
import MembershipLambda from 'src/content/docs/extend/code/_membership-lambda.md';
import { YouTube } from '@astro-community/astro-embed-youtube';

<AdvancedPlanBlurb />

## Overview

Lambda HTTP Connect allows you to make HTTP requests from within a lambda. Any lambda can make a request to any network accessible URL.

This features allows you to access data from external systems to configure token claims or to add data to a user profile. It also allows you to push profile or other data from FusionAuth to an external system as needed.

Here's a video showing more details about Lambda HTTP Connect:

<YouTube id="_TnDUPQm3aQ" />

### Example Lambda
mooreds marked this conversation as resolved.
Show resolved Hide resolved

Here is a FusionAuth lambda that adds additional claims to a JWT based on an HTTP request:

```javascript title="A lambda which adds claims based on an external API."
function populate(jwt, user, registration) {
var response = fetch("https://api.example.com/api/status?" + user.id, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});

if (response.status === 200) {
// assuming successful response looks like:
// {"status":"statusValue"}
var jsonResponse = JSON.parse(response.body);
jwt.status = jsonResponse.status;
} else {
jwt.status = "basic";
}
}
```

You can also call FusionAuth APIs with a valid API key:

<MembershipLambda />

Use port 9012, or the configured value for `fusionauth-app.http-local.port`, whenever making a FusionAuth API call in a lambda. Doing so minimizes network traffic contention and improves performance.

### Headers

You can provide request header values in a number of different ways:

```javascript title="An anonymous object"
headers: {
"Content-Type": "application/json"
}
```

```javascript title="A hash or map"
headers: new Headers({
"Content-Type": "application/json"
})
```

```javascript title="An array"
headers: new Headers([
["Content-Type", "application/json"]
])
```

### Response

A response object will be returned. It will have the following fields:

<APIBlock>
<APIField name="headers" type="Object">
The headers returned by the response. The keys of this object are the header names. All header keys are lower cased.
</APIField>
<APIField name="status" type="Integer">
The HTTP status code.
</APIField>
<APIField name="body" type="String">
The body of the response.
</APIField>
</APIBlock>

### Securing API Keys In Lambdas

Being able to make API requests against FusionAuth can be useful, but requires an API key to be stored in the Lambda code.

To secure that API key, you should:

* scope your API keys as narrowly as possible (both in terms of tenants and permissions)
* create a unique API key for each lambda to make revocation easy
* limit who has access to view lambda code to limit who can see API keys
* [rotate API keys regularly](/docs/operate/secure-and-monitor/key-rotation)

There's an [open GitHub issue](https://github.com/FusionAuth/fusionauth-issues/issues/1629) which discusses product improvements to API key handling.

## Lambda HTTP Connect Limitations

When using Lambda HTTP Connect to make HTTP requests, do not call a FusionAuth API which invokes the calling lambda, because it will fail. For example, in a JWT Populate lambda, do not invoke the Login API.

Requests from a lambda require the lambda to use the GraalJS engine.

HTTP requests will time out after two seconds.

The `fetch` method in a lambda does not implement the [entire `fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as implemented in a browser.
The first argument to `fetch` must always be a string URL.
Only the following options are supported:

* `method`, which defaults to `GET`
* `headers`, which defaults to null
* `body`, which must be a string


2 changes: 1 addition & 1 deletion astro/src/content/docs/extend/code/lambdas/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ The output should be as follows

If all your unit tests for a lambda pass, you can safely upload it to FusionAuth manually or with the CLI for further testing.

If your HTTP Connect fetch request fails when deployed to FusionAuth, please review the [documentation](/docs/extend/code/lambdas/#using-lambda-http-connect). In particular, ensure you are using a license and have purchased the correct plan (Essentials or Enterprise).
If your HTTP Connect fetch request fails when deployed to FusionAuth, please review the [documentation](/docs/extend/code/lambdas/lambda-remote-api-calls). In particular, ensure you are using a license and have purchased the correct plan (Essentials or Enterprise).

### Unit Test: Populate JWT From FusionAuth

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ If you add Richard to the `Admin Group`, he will receive the role `admin` in Pie

Sometimes you want membership information in an access token generated by a login event. This data is not directly available in the lambda arguments.

However, you can do this by using [Lambda HTTP Connect](/docs/extend/code/lambdas/#using-lambda-http-connect) to request memberships using the [Group API](/docs/apis/groups) from within a [JWT Populate Lambda](/docs/extend/code/lambdas/jwt-populate). Here's an example of such a lambda.
However, you can do this by using [Lambda HTTP Connect](/docs/extend/code/lambdas/lambda-remote-api-calls) to request memberships using the [Group API](/docs/apis/groups) from within a [JWT Populate Lambda](/docs/extend/code/lambdas/jwt-populate). Here's an example of such a lambda.

<MembershipLambda />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ You can add arbitrary JSON to the `user.data` field. You can do this using the [

The downstream application can examine the tokens and determine access.

A variant of this is using [Lambda HTTP Connect](/docs/extend/code/lambdas/#using-lambda-http-connect) which can pass a user Id to an external service during authentication and retrieve user attributes. This has the advantage of avoiding synchronization at the cost of increased latency during the authentication event. The exact amount of latency depends on API responsiveness.
A variant of this is using [Lambda HTTP Connect](/docs/extend/code/lambdas/lambda-remote-api-calls) which can pass a user Id to an external service during authentication and retrieve user attributes. This has the advantage of avoiding synchronization at the cost of increased latency during the authentication event. The exact amount of latency depends on API responsiveness.

This approach works well when you want FusionAuth to handle authentication but keep all user segmentation logic in your application.

Expand Down