How to build document access control with S3, WorkOS FGA, and Lambda authorizers
In this tutorial, paired with companion code, you’ll learn to build a secure, scalable document access control system using WorkOS FGA, AWS Lambda Authorizers, and Amazon S3.
In this tutorial, paired with companion code, you’ll learn to build a secure, scalable document access control system using WorkOS FGA, AWS Lambda Authorizers, and Amazon S3. We’ll guide you through:
- Why this architecture is ideal for secure, scalable document management
- How the WorkOS FGA schema enables seamless and flexible access control for diverse use cases
- Step-by-step implementation, spotlighting the key building blocks
- Testing and validating the entire setup with an automated demo script
The challenge and solution
The challenge
Alice, a software engineer on the Engineering team, creates a document in S3 that contains sensitive design specifications.
She needs to share it with her team but not with outsiders like Charlie, who isn't part of the team.
Bob, another team member, should be able to view the document but not edit it. Therefore our requirements are:
- 👩 Alice can view her own document
- 👨 Bob can view the team document
- 🧑 Charlie cannot view Alice's document
How can we enforce these nuanced, relationship-based access controls in a cloud-native serverless architecture?
The solution
The system we’re building solves these challenges by combining:
- Amazon S3 for secure, scalable document storage.
- WorkOS FGA for relationship-based authorization, enabling inheritance and team-based permissions.
- AWS Lambda Authorizers for enforcing these permissions dynamically, based on user tokens and access policies.
Alice’s document permissions can be automatically inherited by her team, ensuring Bob has seamless access while Charlie is denied:
- 👨 ✅️ Bob can view the team document
- 🙅♂️ Charlie cannot view Alice's document
The result is a secure, cloud-native solution that scales with your team’s needs.
How it works: overview and architecture
Let's examine the system architecture and its key components:
- Amazon S3: Stores documents securely.
- WorkOS FGA: Manages relationship-based permissions with a schema supporting users, teams, and roles like owner, editor, and viewer.
- AWS Lambda Authorizer: Acts as a gatekeeper, validating JWT tokens and checking permissions with WorkOS FGA.
Workflow
- User Authentication: A user initiates a request to access a document by including their JWT token in the request headers. In a production environment, this token would likely be issued by your organization's authentication system, but for the purposes of this demo, we'll use a simple JWT token.
- Authorization Check:
- The Lambda authorizer validates the token and extracts the user ID
- The Lambda authorizer queries WorkOS FGA to check if the user can access the requested document
- Request Execution: If authorized, the request is forwarded to S3 to retrieve the document. Otherwise, access is denied.
This layered approach separates concerns, allowing S3 to handle storage and WorkOS FGA to manage access logic.
Following along with the code
To follow along, you'll need:
- An AWS Account with appropriate permissions.
- Node.js 18 or later installed.
- AWS CLI configured locally.
- A WorkOS account and API key.
- AWS CDK CLI (
npm install -g aws-cdk
).
For detailed environment setup instructions, refer to the companion GitHub repository.
Designing the authorization schema
The core of our system is the WorkOS FGA authorization model. Our FGA schema defines three main types:
user
: Individual users in the system.team
: Groups of users with shared access.document
: Documents with controlled access.
Here's our complete schema:
type user
type team
relation member [user]
type document
relation parent [team]
relation owner [user]
relation editor [user]
relation viewer [user]
inherit editor if
relation owner
inherit viewer if
any_of
relation editor
relation member on parent [team]
What does this enable?
- Team-based access: Documents can be shared with entire teams.
- Document ownership: Users can be designated as document owners.
- Role-based permissions: Support for owner, editor, and viewer roles.
- Permission inheritance:
- Document owners automatically get editor permissions.
- Team members inherit viewer access to team documents.
- Editors automatically get viewer permissions.
The WorkOS FGA authorization model is extremely flexible, and you can use it to build custom permissions systems for any scenario.
Why define our system with Infrastructure as Code (IaC)?
To manage and deploy the infrastructure for this project, we leveraged AWS CDK. CDK’s code-first approach lets us define AWS resources like the Lambda function, API Gateway, and S3 bucket directly in TypeScript.
This approach integrates seamlessly with application logic, making infrastructure easier to manage and understand.
Building and deploying the system: step by step
Let’s walk through the implementation step by step, starting with document storage and progressing through secure authentication, authorization, and testing.
1. Create the Amazon S3 Bucket
Set up secure, scalable document storage with Amazon S3. This bucket stores the documents while ensuring security via encryption and strict access policies:
CDK enables us to provide a path to our sample documents and deploy them to the S3 bucket as part of the deployment process.
2. Create the Lambda authorizer function
Add a Lambda function, or authorizer, which our API Gateway will use to authorize requests before passing them to the S3 bucket.
We pass in the WorkOS API key and JWT secret as environment variables, which we can set in our .env.local file.
This CDK code automatically bundles the authorizer function and creates a Lambda function with the contents of index.ts
, which means we don't have to manage the deployment in a separate file format such as JSON or YAML.
We can write TypeScript for both our application and infrastructure code and let CDK handle the rest.
The authorizer function validates the JWT token and checks the user's permissions with WorkOS FGA:
Lambda authorizers work by executing whatever custom logic you want and then returning an IAM policy document that either allows or denies access to the requested resource.
WorkOS FGA directly drives the authorization logic, and Lambda returns the appropriate IAM policy document based on check results.
3. Secure JWT authentication
Add stateless authentication using JWTs. Each token encodes the user ID and is signed with a secret to ensure integrity.
Why JWTs?
- Tokens are compact and easy to pass in request headers.
- They support stateless validation, reducing server-side complexity.
Generating JWTs:
Using JWTs in API Requests:
- Tokens are included in the
Authorization
header:
Authorization: Bearer <JWT_TOKEN>
During Lambda execution, the authorizer:
- Validates the JWT signature and extracts the
sub
field. - Maps
sub
to the user’s permissions in WorkOS FGA.
4. Integrate with API Gateway
Use API Gateway to route requests to S3 with Lambda-based authorization.
This code sets up an API Gateway endpoint to retrieve objects from an S3 bucket using a dynamic documentId
path parameter:
This code sets up an IAM role, configures API Gateway, and integrates it with S3 to dynamically retrieve objects using a documentId path parameter.
API Gateway uses the apiGatewayRole to access S3 securely. The setup includes a documents resource and a nested documents/{documentId}
resource, allowing dynamic S3 key resolution.
A GET method on /documents/{documentId}
maps documentId to an S3 key, retrieves the object, and returns it with a 200 status on success.
The Lambda authorizer and WorkOS FGA enforce user-specific permissions, authorizing or denying requests. When a user calls GET /documents/{documentId}
, the document is securely returned, or access is denied based on their permissions.
5. Deploy and test the system
Now that the components are in place, deploy the infrastructure and validate it with a demo script. CDK simplifies deployment by synthesizing these constructs into CloudFormation templates and managing the entire stack. To deploy the system, follow along with the instructions in the README:
- Ensure prerequisites like the AWS CLI and CDK CLI are installed.
- Clone the repository and set up environment variables.
- Deploy the two stacks:
cdk deploy --all
6. Testing with JWTs
We’ve included a comprehensive demo script in the repository to ensure our system works as expected. It generates JWTs and tests authorization rules using API Gateway.
Here’s a simplified version of the flow:
1. Generate JWTs for test users:
2. Make API requests:
Running the demo script
Ensure your environment is properly configured with:
WORKOS_API_KEY
: From your WorkOS Dashboard.JWT_SECRET
: Set this in your .env.local file. You can use a random string or openssl to generate one: (openssl rand -base64 32
). Note that the demo script and deployed Lambda authorizer must use the exact same secret.
Run the script with:
npm run demo
Expected results:
The demo script performs comprehensive tests to validate the system's functionality:
- Alice can access her document.
- Bob can access team documents.
- Charlie is denied access to Alice’s document.
Here's what you'll see when you run it:
Thanks for reading
This architecture provides a robust foundation for document access control that:
- Supports fine-grained permissions at both user and team levels.
- Scales efficiently with your organization.
- Minimizes operational overhead through serverless components.
Consider extending the system with:
- Access logging and audit trails.
- Custom role definitions.
- Integration with your existing authentication system.
For complete code and deployment instructions, visit our GitHub repository.
If you're looking to implement FGA in your own system, WorkOS FGA is a great place to start.