Connect to an OpenID Connect identity provider such as Okta or Auth0 using OAuth 2.0, validate authentication status at the Edge, and authorize access to your edge or origin hosted applications.
This project is closely based on the Compute@Edge OAuth application starter kit by the Fastly Developer Relations team. We created this application to demonstrate role-based access control (RBAC) in the Access Control for Cloud Object Storage: Enforcing Policy at the Edge conference session, presented at DeveloperWeek CloudX in August 2023.
We added the following features to the original:
- Role-based Access Control - the app allows or denies requests to URL paths based on the authenticated user's group membership.
- A simple JavaScript single-page application (SPA) that demonstrates RBAC, along with sample resources.
- AWS V4 request signing to serve files from private buckets in S3-compatible cloud object storage platforms such as Backblaze B2.
- Logout from the app, and single logout from the identity provider.
We used Okta as our identity provider for the demo; other OpenID Connect providers should work just as well.
This is a self-contained Rust implementation 🦀 for the OAuth 2.0 Authorization Code flow with Proof Key for Code Exchange (PKCE), deployed to Compute@Edge.
It includes JSON Web Token (JWT) verification, and access token introspection.
Scroll down to view the flow in more detail.
- A Backblaze B2 account - sign up here if you do not already have one.
- A Fastly account - sign up here.
- The fastly command-line interface (CLI). Follow the instructions to install the CLI and configure it with your Fastly API token.
-
Create a Backblaze B2 bucket. You can leave the privacy and other settings with their default values. Make a note of the endpoint shown in the bucket details. This has the form
s3.{your-bucket-region}.backblazeb2.com
, where{your-bucket-region}
is the bucket's region, for example,us-west-004
. -
Create an application key with read-only access to your bucket. Keep careful note of the application key ID and the application key itself. You will not be able to retrieve the application key value later!
-
Using the Fastly CLI, create a new project using this project template somewhere on your computer:
fastly compute init --from=https://github.com/backblaze-b2-samples/devweek2023-demo
Or click the button below to create a GitHub repository, provision a Fastly service, and set up continuous deployment:
After you have created your project, you'll need to do some configuration before you can deploy it, so that Fastly knows which identity provider to use and how to authenticate.
You might operate your own identity service, but any OAuth 2.0, OpenID Connect (OIDC) conformant provider (IdP) will work. You will need the following from your IdP:
- A Client ID, and possibly also a Client Secret, depending on the IdP -> Add to
src/config.toml
- An OpenID Connect Discovery document -> Save as
src/well-known/openid-configuration.json
- A JSON Web key set -> Save as
src/well-known/jwks.json
- The hostname of the IdP's authorization server -> Create as a backend called
idp
on your Fastly service
As an example, if you are using Okta, follow these steps after installing the starter kit:
- Sign up for an Okta Developer Edition: click "Sign up free for Developer Edition" at https://developer.okta.com/signup/. Make a note of your Okta domain - this has the form
dev-12345678.okta.com
. You'll use this later as the identity provider authorization server when you deploy the app to Fastly, - In the Okta Dashboard, in the left navigation bar, choose the Applications menu, then the Applications menu item, and click Create App Integration. Select OIDC - OpenID Connect, then Web Application, and click Next. Give your app a name and, under Assignments, select Allow everyone in your organization to access and deselect Enable immediate access with Federation Broker Mode. Click Save.
- The client ID (eg.
0oaamq3it70DgCF2K5d7
) is shown in the Client Credentials section; a client secret is listed in the CLIENT SECRETS section.
- The client ID (eg.
- Edit your app's Client Credentials and select Require PKCE as additional verification. Click Save.
- Click the Sign On tab, scroll down, and edit OpenID Connect ID Token. Set Groups claim type to Filter, and set Groups claim filter to Matches regex, with a regex of
.*
(all groups). Click Save. - Open
src/config.toml
in your Fastly project and paste in theclient_id
andclient_secret
from Okta. Set thenonce_secret
field to a long, non-guessable random string of your choice. Save the file. - In a new tab, navigate to
https://{okta-domain}/.well-known/openid-configuration
. Save it tosrc/well-known/openid-configuration.json
in your Fastly project. - Open the file you just created and locate the
jwks_uri
property. Fetch the document at that URL and save it tosrc/well-known/jwks.json
in your Fastly project.
This app allows access to resources based on users' group membership. You will need to create a group, at least two test users, and assign one of the test users to the group.
- In the Okta Dashboard, in the left navigation bar, choose the Directory menu, then the Groups menu item, and click Add Group. Name the group
accounting
(all lower case) and click Save. Refresh the browser page to see the new group. - In the left navigation bar, choose the People menu item, and click Add Person. Fill out first name, last name and username. Note that username must be in the form of an email, but it need not be a real email address. For example, you might use
[email protected]
. Select I will set password, set a password for the user, and deselect User must change password on first login. Click Save and Add Another and supply the same information for a second test user. - In the People list, click one of your test users, then click the Groups tab. Type
acc
into the field, and selectaacounting
from the drop-down list.
Now you can build and deploy your new service:
$ fastly compute publish
You'll be prompted to enter the hostname of your own origin to configure the backend called backend
, which you should set to {your-bucket-name}.s3.{your-bucket-region}.backblazeb2.com
, and also the authorization server of the identity provider (for Okta, this is the Okta domain, with the form dev-12345678.okta.com
) which will be used to configure a backend called idp
. When the deploy is finished you'll be given a Fastly-assigned domain such as random-funky-words.edgecompute.app
.
Add https://{your-fastly-domain}/callback
to the list of allowed callback URLs in your identity provide's app configuration (In Okta, edit your application's General Settings, scroll down to LOGIN, add the callback URL to Sign-in redirect URIs, and click Save).
This allows the authorization server to send the user back to the Compute@Edge service.
The sample stores its Backblaze B2 credentials in a secret store. At present, the secret store feature is part of a beta release. Open a Fastly support ticket to request that secret store be enabled for your Fastly account.
Once the secret store feature is enabled for your account, create a secret store in the Fastly web UI:
- Click Resources in the top navigation menu.
- Click the Secret stores tab.
- Click Create a secret store and name it
devweek2023-demo
.
Add the following key-value pairs:
B2_APPLICATION_KEY_ID
: your Backblaze B2 application key IDB2_APPLICATION_KEY
: your Backblaze B2 application key
Link the secret store to your Fastly service:
- Click the Link to services button.
- Select your Fastly service.
- Click the Next button.
- Select Link and activate.
- Click Confirm and activate to link the secret store to a new version of the service and activate that version.
Note - you may use an alternative name for your secret store. If you do so, you will need to change the value of the FASTLY_SECRET_STORE
constant in main.rs.
The repository contains sample resources in the res
directory. You can copy these to your B2 bucket using either the Backblaze B2 CLI or the AWS CLI, as you prefer.
- Install the Backblaze B2 CLI, if you do not already have it.
- Authenticate to Backblaze B2 with the credentials you created earlier:
b2 authorize-account "{application-key-id}" "{application-key}"
- Use the
b2 sync
command to copy the contents of theres
directory to your bucket. From your repository directory:b2 sync res b2://[your-bucket-name]/
- Verify that the resources have been uploaded to B2:
You should see the following listing:
b2 ls --recursive devweek2023-demo
accounting/accounts.pdf favicon.ico index.html internal.pdf logged-out.html public.html
- Install the AWS CLI, if you do not already have it. Note that you will need version 2.13.0 or later for the next step. Older versions of the AWS CLI do not support configuration of the endpoint via an environment variable.
- Configure the AWS CLI with your Backblaze B2 credentials and your bucket's endpoint via environment variables:
There are several other mechanisms for configuring the AWS CLI, but this is the most straightforward.
export AWS_ACCESS_KEY_ID="{application-key-id}" export AWS_SECRET_ACCESS_KEY="{application-key}" export AWS_ENDPOINT_URL="https://s3.{your-bucket-region}.backblazeb2.com"
- Use the
aws s3 sync
command to copy the contents of theres
directory to your bucket. From your repository directory:aws s3 sync res s3://[your-bucket-name]/
- Verify that the resources have been uploaded to B2:
You should see a listing like this:
aws s3 ls --recursive s3://devweek2023-demo/
2023-07-24 09:05:37 26599 accounting/accounts.pdf 2023-07-28 09:39:34 85182 favicon.ico 2023-07-24 09:05:37 3448 index.html 2023-08-01 12:53:34 32227 internal.pdf 2023-07-31 14:26:46 1286 logged-out.html 2023-07-31 13:19:44 1388 public.html
Now you can visit the sample app at your Fastly-assigned domain, the URL that looks like https://random-funky-words.edgecompute.app
. You'll see a home page with the text, "You are not currently authenticated against your Okta org.".
- At the home page, click the Login link. You will be redirected to Okta to login.
- Log in as your
accounting
group member. You will be redirected back to a different home page, that greets the user by name and lists links to an "Internal doc" and an "Accounting doc". - Each of the docs is a PDF containing dummy text. Note that the accounting doc has restricted access - only members of the
accounting
group should be able to access that document! Make a note of both document URLs so you can paste them into the browser location bar later. They have the formshttps://random-funky-words.edgecompute.app/internal.pdf
andhttps://random-funky-words.edgecompute.app/accounting/accounts.pdf
. - Click the Logout link. You will be redirected to Okta briefly, then straight back to the app.
- Click the Home link. You will see the "not currently authenticated" message.
- Now paste the
internal.pdf
link into the browser location bar and try to go to that page. You are redirected for login, since that document is restricted to authenticated users. - Log in as your other test user, which is not a member of the
accounting
group. You will see the internal PDF. - Delete
internal.pdf
from the end of the URL in your browser location bar to go back to your app's home page. Again, the test user is greeted by name, but this time only the internal doc is listed. - Paste the
accounting/accounts.pdf
link into the browser location bar and try to go to that page. You will see the message, "Access to /accounting directory denied!", since this user is not a member of theaccounting
group.
Here is how the authentication process works:
- The user makes a request for a protected resource, but they have no session cookie.
- At the edge, this service generates:
- A unique and non-guessable
state
parameter, which encodes what the user was trying to do (e.g., load/internal.pdf
). - A cryptographically random string called a
code_verifier
. - A
code_challenge
, derived from thecode_verifier
. - A time-limited token, authenticated using the
nonce_secret
, that encodes thestate
and anonce
(a unique value used to mitigate replay attacks).
- A unique and non-guessable
- The
state
andcode_verifier
are stored in session cookies. - The service builds an authorization URL and redirects the user to the authorization server operated by the IdP.
- The user completes login formalities with the IdP directly.
- The IdP will include an
authorization_code
and astate
(which should match the time-limited token we created earlier) in a post-login callback to the edge. - The edge service authenticates the
state
token returned by the IdP, and verifies that the state cookie matches its subject claim. - Then, it connects directly to the IdP and exchanges the
authorization_code
(which is good for only one use) andcode_verifier
for security tokens:- An
access_token
– a key that represents the authorization to perform specific operations on behalf of the user) - An
id_token
, which contains the user's profile information, including their group membership.
- An
- The end-user is redirected to the original request URL (
/internal.pdf
), along with their security tokens stored in cookies. The cookies are available to JavaScript running on pages served from the sample app, so the home page can display the user's name and list the accounting doc if appropriate. - When the user makes the redirected request (or subsequent requests accompanied by security tokens), the edge verifies the integrity, validity and claims for both tokens and, if the request URL contains a 'directory', verifies that the user is a member of a group with the same name as the directory. If the tokens are still good, and the user is allowed access to the URL, it proxies the request to your origin.
If you encounter any bug or unexpected behavior, please file an issue using the bug report template.