Skip to content

Instantly share code, notes, and snippets.

@brentjanderson
Created January 25, 2023 20:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brentjanderson/52f33607d03dc7e46e2d9438264067e8 to your computer and use it in GitHub Desktop.
Save brentjanderson/52f33607d03dc7e46e2d9438264067e8 to your computer and use it in GitHub Desktop.
Knock In-app Notifications with NextAuth/AuthJS
# These values all come from the dashboard
NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY=pk_test_1234567890_abcdefghijklmnop
NEXT_PUBLIC_KNOCK_FEED_ID=b935c367-2347-43b1-8f28-867330a2a636
# Replace each line break with `\n` and then wrap the whole thing in JSON. JSON.parse will properly decode all of it.
KNOCK_SIGNING_KEY='{"key": "-----BEGIN RSA PRIVATE KEY-----\n<The rest of the key goes here>\n-----END RSA PRIVATE KEY-----"}'
// This file is adapted from https://github.com/nextauthjs/next-auth-example/blob/main/pages/api/auth/%5B...nextauth%5D.ts
import NextAuth, { NextAuthOptions } from "next-auth"
import CredentialsProvider from 'next-auth/providers/credentials'
import jwt from "jsonwebtoken";
// Here is where we extract the Knock signing key to use later.
const knockSigningKey = JSON.parse(process.env.KNOCK_SIGNING_KEY as string).key;
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({ // Using this provider as an example.
name: "Credentials",
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
const user = { id: "1", name: "J Smith", email: "some-user@example.com" }
if (user) {
return user
} else {
return null
}
}
}),
],
callbacks: {
async session({ session, token, user }) {
// This section is adapted from the Knock docs: https://docs.knock.app/in-app-ui/security-and-authentication#2-sign-the-jwt
// JWT NumericDates specified in seconds:
const currentTime = Math.floor(Date.now() / 1000);
// Using any because the session type does not support custom properties, but it will pass them through to the client.
(session as any).knockToken =
jwt.sign(
{
sub: token.sub, // token.sub is the user ID from our provider. You can incorporate the JWT callback, or draw from the user's email, although Knock discourages using email as the user's ID since emails can change but IDs are immutable.
iat: currentTime,
exp: currentTime + 60 * 60, // 1 hour from now
},
knockSigningKey,
{
algorithm: "RS256",
},
);
// The user ID is passed through here so we can surface it in the component.
(session as any).user.id = token.sub;
return session;
}
},
}
export default NextAuth(authOptions)

These are some excerpts adapted from the Nextauth example.

Getting started

  1. Grab your Knock Client Secret Key. Replace every line break with \n and wrap it in JSON (see example). Store this in the environment variable KNOCK_SIGNING_KEY. Be sure to not let this variable be public.
  2. Grab your Knock public key for the environment. Store it in NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY.
  3. Grab the ID of the channel you want to surface in the in-app feed. Store it in NEXT_PUBLIC_KNOCK_FEED_ID
  4. Update the session callback in your nextauth code to set the knockToken
// This is from https://github.com/nextauthjs/next-auth-example/blob/main/pages/index.tsx
// You will need to adapt the instructions from the Knock docs for including the feed to fit your use case.
// See https://docs.knock.app/in-app-ui/security-and-authentication#3-send-the-jwt-to-the-client
import {
KnockFeedProvider,
NotificationIconButton,
NotificationFeedPopover,
} from "@knocklabs/react-notification-feed";
// Required CSS import, unless you're overriding the styling
import "@knocklabs/react-notification-feed/dist/index.css";
import { useSession } from "next-auth/react";
import { useRef, useState } from "react";
import Layout from "../components/layout"
export default function IndexPage() {
const { data: session } = useSession();
const [isVisible, setIsVisible] = useState(false);
const notifButtonRef = useRef(null);
return (
<Layout>
<h1>NextAuth.js Example</h1>
<p>
This is an example site to demonstrate how to use{" "}
<a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
</p>
{/* Here is where we start integrating the Knock feed */}
{(session?.user as any)?.id && <KnockFeedProvider
apiKey={process.env.NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY as string}
feedId={process.env.NEXT_PUBLIC_KNOCK_FEED_ID as string}
userId={(session?.user as any)?.id}
userToken={(session as any)?.knockToken}
>
<>
<NotificationIconButton
ref={notifButtonRef}
onClick={(e) => setIsVisible(!isVisible)}
/>
<NotificationFeedPopover
buttonRef={notifButtonRef}
isVisible={isVisible}
onClose={() => setIsVisible(false)}
/>
</>
</KnockFeedProvider> }
</Layout>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment