Fauna doesn't (yet?) provide guaranteed expiration/TTL for ABAC tokens, so we need to implement it ourselves if we care about it.
3 javascript functions, each of which can be imported into your project or run from the command-line
using node path/to/script.js arg1 arg2 ... argN
:
deploy-schema.js
: a javascript function for creating supporting collections and indexes in your Fauna database.- Intended to be called once per Fauna database. Safe to call it multiple times (will not cause harm).
- Needs to be called with a Fauna SERVER secret prior to using the other provided functions.
- Logic
- args:
fauna_server_secret
- returns: a
Promise
- Creates a collection named
auth0_token_exchanges
.- This collection will store one document for each user secret issued in exchange for an Auth0 token.
- The documents will contain this data:
token_ref
: the Ref of the Fauna user token which was issuedexpires_at
: the Time at which this token should expiremeta
: any other data passed into thecustom_metadata
argument toexchange-jwt-for-secret.js
- For example, you might want to log the decoded JWT payload or the entire JWT, which could be useful for your own indexing/querying/auditing/debugging purposes.
- We exclude any identifying data by default to avoid unintentionally storing any sensitive user data which may be governed by HIPAA, etc.
- Creates an index named
auth0_token_exchanges_by_expiration
which indexes the documents bydata.jwt_payload.exp
.
- args:
exchange-jwt-for-secret.js
: verifies Auth0 JWTs, looks-up the user in Fauna by auth0_id, creates an ABAC token for the user, records the token and JWT expiration time in Fauna, and returns the token secret.- This is intended to be served in an API endpoint that you create.
- Clients should call this endpoint upon receiving a JWT to obtain a Fauna user secret.
- Clients can then use this Fauna user secret to communicate directly with your Fauna database, e.g. for the native GraphQL endpoint.
- Logic
- args:
auth0_jwt, custom_metadata, auth0_client_id, auth0_client_cert_pubkey, fauna_server_secret, fauna_index_users_by_auth0_id
- returns: a
Promise
- Verifies the JWT (via Auth0 for safety). Rejects the promise if invalid or expired.
- Looks up user by Auth0 user ID (from index named by
fauna_user_index_auth0_id
)- you need to setup this index prior to using this function.
- Create a user token for the user (i.e.
Login()
, but viaCreate(Tokens(), ...)
). - Create a document in the
auth0_token_exchanges
collection containing theref
of the user token (NOT the secret), the provided JWT, and the decoded JWT payload. - Return the user secret (by resolving the promise).
- args:
- In my app's integration, I added some logic at the beginning to create the User document for this Auth0 ID if there isn't one already.
function findOrCreateUserRef(index_users_by_auth0_id, auth0_id) { return q.Select( ['ref'], q.Let( {userMatch: q.Match(q.Index(index_users_by_auth0_id), auth0_id)}, q.If( q.Exists(q.Var('userMatch')), q.Get(q.Var('userMatch')), q.Create(q.Collection('users'), { data: { auth0_id: auth0_id } }) ) ) ) }
delete-expired-tokens.js
: deletes all expired tokens which were issued in exchange for Auth0 JWTs.- This is intended to be called in a cron, and can be called as often as desired. The lower limit is probably once/day and the upper limit is probably once every 5 minutes.
- If you don't call this function in a cron, then the rest of this code is pointless, because your user tokens will never expire (which is the normal behavior without any of this code).
- Logic
- args:
fauna_server_secret
- returns: a
Promise
- Queries index
auth0_token_exchanges_by_expiration
for all documents which are past the expiration timestamp specified indata.jwt_payload.exp
. - For each matching instance returned from
auth0_token_exchanges
, deletes the ABAC token referenced bydata.token_ref
. - Deletes each of the matching instances returned from
auth0_token_exchanges
.
- args:
@donaldboulton I think he is trying to leverage a couple of things out of JWTs, one of them that they have an expiration, and use this to reasonably expire faunadb secrets.
Seems to me that a simpler version is possible using emails and expiring an hour into the future or something similar, but now your jwt expiration and secret expiration do not coincide.
On the other hand, if you are in an scenario like mine, with netlify identity (or if your provider's behavior and architecture coincide with mine), you'd always have access to your user metadata without an explicit expiration timestamp and matching by email would be an option. In any case we are still in charge of removing expired stuff.
This guy has a related article by the way: https://www.felix-gehring.de/articles/2020/01/28/using-faunadb-with-an-identity-provider/