From 3cd733e3cdf0f6acf70728629899b3a31475f5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Sun, 8 Dec 2024 23:22:06 +0000 Subject: [PATCH 1/6] First pass at auth0 integration docs --- docs/guides/authentication/auth0.md | 127 +++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index ff9193c0..b5f34aac 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -4,6 +4,129 @@ sidebar_position: 6 sidebar_label: 🚧 Auth0 --- -# 🚧 Integrating With Auth0 +# Integrating With Auth0 + + +This guide aims to give some simple examples of using Auth0 to provide authentication when used in conjunction with Zenstack. It will not take into account the different types of authentication that Auth0 offers. The premise is that you have and understanding of Auth0's method of authentication and are able to produce an object as a result of authenticating a user with Auth0. + +## Enhancing the prisma client + +The basic premise of applying a custom session object to Zenstack. + +Create a user object and provide it to the enhance function when creating the Prisma client. + +``` +export const getPrisma = async (req) => { + const user = await getAuthenticatedAuth0User(req); + return enhance(user); +}; +``` + +You can provide a type in the ZModel to express what the contents of the user object is. + +``` + +type Auth { + id String @id + specialKey String + @@auth +} +``` + +## Adding Auth0 authentication + +You can authenticate an Auth0 user and extract information from the authentication and apply is to the Zenstack session. + +Here's an example using a JWT: + +``` +export const getPrismaJWT = async (req) => { + try { + const jwks = jose.createRemoteJWKSet(new URL(process.env.AUTH0_JWKS_URI)); + const token = toString(req.headers.get('authorization')).replace('Bearer ', ''); + const res = await jose.jwtVerify(token, jwks, { + issuer: `${process.env.AUTH0_ISSUER_BASE_URL}/`, + audience: process.env.AUTH0_TWIST_API_AUDIENCE, + algorithms: ['RS256'], + }); + + const userId = res.payload.sub; + const user = { + id: userId, + specialKey: res.payload.metadata.specialKey + }; + + return enhance(prisma, {user}); + catch (err) { + // unauthenticated error + } +}; +``` + +This would populate your `auth()` in the Zmodel with the object you've just created from auth0; enabling checks like: + +``` +@@allow('read, update, create', auth().id == this.id) +``` + +or + +``` +@@allow('read, update, create', auth().specialKey == 'SUPERMAN') +``` + +You can add what you need to this variable and set the types for it as referred to in the *Enhancing the prisma client* section. + + +## Working along side a user model + +You may want to keep a record of User's in your own database. + +You can create your application in such a way that a lack of the user existing in the managed database triggers a process to create one, such as a user onboarding flow. + +``` +const currentUser = async (req) => { + const session = await getSession(req); // get your auth0 auth session + + if (!session?.user.sub) { + throw new Error('UNAUTHENTICATED'); // Throw an error if the user isn's authenticated + } + + const dbUser = await prisma.user.findUnique({ // Find the user in the db + where: { id: session.user.sub }, + }); + + return { + id: session.user.sub, + dbUserExists: !isNull(dbUser), // Cause onboarding behavior + }; +}; + +// Create the client using the currentUser +export const getPrisma = async (req) => { + const user = await currentUser(req); + return enhance(user); +}; +``` + +When the client is created, the database is queried using the contents of the Auth0 token. + +In this case, the Auth tyoe is what provide authentication, not the User model, for example: + +``` +// Specify the auth type +type Auth { + id String @id + @@auth // And decorate it +} + +// add your user model as a regular model +model User { + id String @id + name String? + email String? + + @@allow('create, read, update, delete', auth().id == this.id) +} +``` -Coming soon. From 28c40493e2b0a178286d0d02ce7a0dc5a5a13ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 10 Dec 2024 10:17:58 +0000 Subject: [PATCH 2/6] Update auth0.md Address PR comments --- docs/guides/authentication/auth0.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index b5f34aac..0576d5d4 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -7,15 +7,15 @@ sidebar_label: 🚧 Auth0 # Integrating With Auth0 -This guide aims to give some simple examples of using Auth0 to provide authentication when used in conjunction with Zenstack. It will not take into account the different types of authentication that Auth0 offers. The premise is that you have and understanding of Auth0's method of authentication and are able to produce an object as a result of authenticating a user with Auth0. +This guide aims to give some simple examples of using Auth0 to provide authentication when used in conjunction with ZenStack. It will not take into account the different types of authentication that Auth0 offers. The premise is that you have and understanding of Auth0's method of authentication and are able to produce an object as a result of authenticating a user with Auth0. ## Enhancing the prisma client -The basic premise of applying a custom session object to Zenstack. +The basic premise of applying a custom session object to ZenStack. Create a user object and provide it to the enhance function when creating the Prisma client. -``` +```ts export const getPrisma = async (req) => { const user = await getAuthenticatedAuth0User(req); return enhance(user); @@ -24,8 +24,7 @@ export const getPrisma = async (req) => { You can provide a type in the ZModel to express what the contents of the user object is. -``` - +```prisma type Auth { id String @id specialKey String @@ -35,11 +34,11 @@ type Auth { ## Adding Auth0 authentication -You can authenticate an Auth0 user and extract information from the authentication and apply is to the Zenstack session. +You can authenticate an Auth0 user and extract information from the authentication and apply it to the ZenStack session. Here's an example using a JWT: -``` +```ts export const getPrismaJWT = async (req) => { try { const jwks = jose.createRemoteJWKSet(new URL(process.env.AUTH0_JWKS_URI)); @@ -65,13 +64,13 @@ export const getPrismaJWT = async (req) => { This would populate your `auth()` in the Zmodel with the object you've just created from auth0; enabling checks like: -``` +```prisma @@allow('read, update, create', auth().id == this.id) ``` or -``` +```prisma @@allow('read, update, create', auth().specialKey == 'SUPERMAN') ``` @@ -84,12 +83,12 @@ You may want to keep a record of User's in your own database. You can create your application in such a way that a lack of the user existing in the managed database triggers a process to create one, such as a user onboarding flow. -``` +```ts const currentUser = async (req) => { const session = await getSession(req); // get your auth0 auth session if (!session?.user.sub) { - throw new Error('UNAUTHENTICATED'); // Throw an error if the user isn's authenticated + throw new Error('UNAUTHENTICATED'); // Throw an error if the user isn't authenticated } const dbUser = await prisma.user.findUnique({ // Find the user in the db @@ -111,9 +110,9 @@ export const getPrisma = async (req) => { When the client is created, the database is queried using the contents of the Auth0 token. -In this case, the Auth tyoe is what provide authentication, not the User model, for example: +In this case, the Auth type is what provide authentication, not the User model, for example: -``` +```prisma // Specify the auth type type Auth { id String @id From 0123fc898871ba5421fe62c5918eedae803761bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 10 Dec 2024 12:35:56 +0000 Subject: [PATCH 3/6] Update auth0.md Added more detail to the section on working alongside a User model --- docs/guides/authentication/auth0.md | 43 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index 0576d5d4..4d8304d3 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -1,7 +1,7 @@ --- description: Integrating with Auth0. sidebar_position: 6 -sidebar_label: 🚧 Auth0 +sidebar_label: Auth0 --- # Integrating With Auth0 @@ -97,7 +97,7 @@ const currentUser = async (req) => { return { id: session.user.sub, - dbUserExists: !isNull(dbUser), // Cause onboarding behavior + dbUserExists: !isNull(dbUser), // If the user doesn't exist in the database, this variable can be set in the session }; }; @@ -108,6 +108,45 @@ export const getPrisma = async (req) => { }; ``` +You can use the result of this token to redirect a user to an onboarding flow, such as a signup form: + +```ts +app.get('/', async (req, res) => { + const user = await currentUser(req); + if (!user.dbUserExists) { + res.redirect('/onboarding'); + } + res.send('hello user'); +}); +``` + +and create a user record using the ID from the Auth0 session, here's a small example using the Auth0 React SDK: + +```ts +import React from "react"; +import { useAuth0 } from "@auth0/auth0-react"; + +const Profile = () => { + const { user, isAuthenticated, isLoading } = useAuth0(); + const { trigger, isMutating } = useCreateUser(); + + const createUser = useCallback(async (event: FormEvent) => { + const formData = new FormData(event.currentTarget); + const name = formData.get('name'); + + await trigger({ + data: { + id: user.sub, + name: name, + }, + }); + }, [trigger, user]) + + return +}; +``` + + When the client is created, the database is queried using the contents of the Auth0 token. In this case, the Auth type is what provide authentication, not the User model, for example: From 7878992c66442cdb57ee029d87150684abe8273b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 10 Dec 2024 12:40:04 +0000 Subject: [PATCH 4/6] Update auth0.md Wording improvement --- docs/guides/authentication/auth0.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index 4d8304d3..90b74c50 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -6,8 +6,7 @@ sidebar_label: Auth0 # Integrating With Auth0 - -This guide aims to give some simple examples of using Auth0 to provide authentication when used in conjunction with ZenStack. It will not take into account the different types of authentication that Auth0 offers. The premise is that you have and understanding of Auth0's method of authentication and are able to produce an object as a result of authenticating a user with Auth0. +This guide provides simple examples of using Auth0 authentication with ZenStack. While Auth0 offers various authentication methods, this guide assumes you understand Auth0's authentication basics and can obtain a user object after authentication. ## Enhancing the prisma client From cb1ed2798846585bf8202eae59e507826a6dc9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 10 Dec 2024 12:43:20 +0000 Subject: [PATCH 5/6] Update auth0.md A few more comments --- docs/guides/authentication/auth0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index 90b74c50..2185b4a2 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -154,7 +154,7 @@ In this case, the Auth type is what provide authentication, not the User model, // Specify the auth type type Auth { id String @id - @@auth // And decorate it + @@auth // And decorate it with @@auth to tell ZenStack to use this as the session object } // add your user model as a regular model @@ -163,6 +163,7 @@ model User { name String? email String? + // You can now use the Auth object, populated by Auth0, to write policies @@allow('create, read, update, delete', auth().id == this.id) } ``` From 6c941a45473372eb2d9603f6c83c727e8b8b16e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sinan=20G=C3=BC=C3=A7l=C3=BC?= Date: Tue, 10 Dec 2024 12:47:08 +0000 Subject: [PATCH 6/6] Update auth0.md Improved gramma --- docs/guides/authentication/auth0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/authentication/auth0.md b/docs/guides/authentication/auth0.md index 2185b4a2..44aa1c91 100644 --- a/docs/guides/authentication/auth0.md +++ b/docs/guides/authentication/auth0.md @@ -8,9 +8,9 @@ sidebar_label: Auth0 This guide provides simple examples of using Auth0 authentication with ZenStack. While Auth0 offers various authentication methods, this guide assumes you understand Auth0's authentication basics and can obtain a user object after authentication. -## Enhancing the prisma client +## The basic premise of applying a custom session object to ZenStack. -The basic premise of applying a custom session object to ZenStack. +This section explains how to apply a custom session object to ZenStack by creating a user object and providing it to the enhance function when creating the Prisma client. Create a user object and provide it to the enhance function when creating the Prisma client. @@ -44,7 +44,7 @@ export const getPrismaJWT = async (req) => { const token = toString(req.headers.get('authorization')).replace('Bearer ', ''); const res = await jose.jwtVerify(token, jwks, { issuer: `${process.env.AUTH0_ISSUER_BASE_URL}/`, - audience: process.env.AUTH0_TWIST_API_AUDIENCE, + audience: process.env.AUTH0_AUDIENCE, algorithms: ['RS256'], });