Skip to main content

@krakentech/blueprint-auth

This is our package that provides Next.js authentication utilities for the Kraken API. You can install this package via npm with an organisation token - please get in touch if you need one.

API reference

withBlueprintAuth

This is a next.config.js webpack config wrapper to fix 'No QueryClient set, use QueryClientProvider to set one' issue with @tanstack/react-query when imported in server-side code:

Import

import { withBlueprintAuth } from '@krakentech/blueprint-auth';

Usage

In your Next.js next.config.js file:

const withBlueprintAuth = require('@krakentech/blueprint-auth');

module.exports = {
(...)
webpack: (config, context) => {
(...)
return withBlueprintAuth({ config, context });
},
};

AuthProvider

Provides context to auth hooks.

Import

import { AuthProvider } from '@krakentech/blueprint-auth';

Usage

In your Next.js _app.tsx file:

import { AuthProvider } from '@krakentech/blueprint-auth';

function App({ Component, pageProps }) {
return (
<AuthProvider>
<Component {...pageProps} />
</AuthProvider>
);
}

export default App;

This is a very basic example. You probably have other providers in your _app.tsx file, make sure you wrap the ones that need to use the auth hooks with the AuthProvider.

authMiddleware

This function is meant to be used in a Next.js middleware function:

  • It will check if the user is authenticated and has the necessary tokens to access the requested protected route.
  • If the user is not authenticated, it will redirect them to the login page.
  • If the user is authenticated, it will check if the access token is still valid and refresh it if necessary.

The krakenConfig.xClientIpSecrectKey is used as the value of the x-client-ip-authorization header. This header is used by Kraken to prevent valid server-side requests, which originate from a single/pool of IPs (this will be the case if you host your app in Vercel for example), from being erroneously rate-limited. More information can be found on this Kraken announcement.

Import

import { authMiddleware } from '@krakentech/blueprint-auth';

We expose this function separately to avoid bundling code that is unsupported in Edge Runtime. Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation

Usage

In your Next.js middleware.ts file:

import { NextResponse, type NextRequest } from 'next/server';
import { authMiddleware } from '@krakentech/blueprint-auth';

export const config = {
matcher: [
'/anon/:path*',
'/dashboard/:path*',
'/login',
'/masquerade/:path*',
],
};

export async function middleware(req: NextRequest) {
const res = NextResponse;

return await authMiddleware({
req,
res,
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
xClientIpSecretKey: process.env.KRAKEN_X_CLIENT_IP_SECRET_KEY,
},
appRoutes: {
anon: {
path: '/anon',
getPreSignedKey: (url) => {
const [_anon, preSignedKey] = url.pathname
.split('/')
.filter(Boolean);

return { preSignedKey };
},
},
dashboard: {
path: '/dashboard',
},
login: {
path: '/login',
},
masquerade: {
path: '/masquerade',
getUserIdAndMasqueradeToken: (url) => {
const [_masquerade, userId, masqueradeToken] = url.pathname
.split('/')
.filter(Boolean);

return { userId, masqueradeToken };
},
},
},

setCustomHeaders: (headers) => {
headers.set(name, value);
},
useSecureCookie: !process.env.NODE_ENV === 'test',
});
}

loginHandler

Handles the login process. This function must be used in a Next.js API route:

  • It first checks if the request method is POST, if not, it returns 405 response.
  • Then it creates a new Headers object, optionally including any custom headers passed in.
  • If the request is successful, it sets the necessary auth cookies necessary and returns a 200 response with a standardised JSON object.
  • If the request fails, it returns a 400 response with a standardised JSON object.

Import

import { loginHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js login api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { loginHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await loginHandler({
req,
res,
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
xClientIpSecretKey: process.env.KRAKEN_X_CLIENT_IP_SECRET_KEY,
},
setCustomHeaders: (headers) => {
headers.set(name, value);
},
useSecureCookie: !process.env.NODE_ENV === 'test',
});
}

logoutHandler

Handles the logout process. This function must be used in a Next.js API route:

  • It first checks if the request method is GET, if not, it returns 405 response.
  • Then it removes every auth related cookie and returns a 200 response with the Cache-Control header set and a standardised JSON object.

Import

import { logoutHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js logout api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { logoutHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
logoutHandler({
req,
res,
});
}

sessionHandler

Handles the session state. This function must be used in a Next.js API route:

  • It first checks if the request method is GET, if not, it returns 405 response.
  • Then asserts the current auth state of the user by retrieving the auth related cookies.
    • The isAuthenticated value is derived from the presence of either:
      • both 'access' and 'refresh' cookies
      • a 'masquerade' cookie
    • The isMasquerading value is derived from the presence of a 'masquerade' cookie.
    • The sub state is derived from the presence of a 'sub' cookie.
  • Then returns a 200 response with the Cache-Control header set to prevent caching and a JSON object with the derived state.

Import

import { sessionHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js session api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { sessionHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
sessionHandler({
req,
res,
});
}

graphqlHandler

Handles every Kraken GraphQL request. This function must be used in a Next.js API route:

  • It first checks if the request method is POST, if not, it returns 405 response.
  • Then it creates a new Headers object, optionally including any custom headers passed in.
  • If the request is successful, it returns a 200 response with the data object.
  • If the request fails, it returns a 200 response with the error object.
  • If the request fails due to a ACCESS TOKEN EXPIRED error, it will attempt to refresh the access token and retry the request.
  • If the request fails due to other auth related errors, it will remove the auth cookies and return the error.

If you intend to make organization scoped requests (either by using the getGraphQLClient helper with it's scope prop set to organization or by using the organizationScopedGraphQLClient returned by the useGraphQLClients hook) you need to pass a few additional props:

  • krakenConfig.organizationSecretKey: the secret key of the organization.
  • encryption: an object with the key and iv values used to encrypt the organization token. We use these to encrypt/decrypt the organization token. This is to prevent the token from being exposed to the client as this is a sensitive and unique value issued by Kraken and a bad actor could potentially use it to make organization scoped authenticated queries/mutations. Both the key and iv must be at least 12 characters long. One possible way of generating them is by using the following command: openssl rand -base64 12. We recommend storing these values in your environment variables. Both of them should be rotated periodically.
  • edgeConfig: an object with the envVar, teamId and authToken values. We are storing the encrypted organization token in the Vercel Edge Config Store connected to you Vercel project, which means that you need to set one up. You can do that at the following URL: https://vercel.com/<team-id>/<your-project-name>/stores. Once you create one, Vercel will automatically create an EDGE_CONFIG environment variable for you. This is the value that you need to pass to the edgeConfig.envVar prop. Its value should look like this: https://edge-config.vercel.com/<edge-config-store-id>?token=<auth-token>. You'll also have to create an access token so that you can interact with the store.

If you try to make an organization scoped request without passing these props, the request will fail with errors stating that they are missing. Basically, if you see any of the following errors, it means that you need to pass the missing props to the graphqlHandler function:

  • blueprint-auth: getOrganizationAccessToken — krakenConfig.organizationSecretKey is required.
  • blueprint-auth: createOrganizationTokenUtils — edgeConfig.envVar, edgeConfig.teamId, and edgeConfig.authToken are required.
  • blueprint-auth: createEncryptionUtils — encryption.key and encryption.iv are required. Bear in mind that their values should have at least 12 characters. One possible way of generating them is by using the following command: openssl rand -base64 12.

Import

import { graphqlHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js graphql api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { graphqlHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await graphqlHandler({
req,
res,
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
organizationSecretKey: process.env.KRAKEN_ORGANIZATION_KEY,
xClientIpSecretKey: process.env.KRAKEN_X_CLIENT_IP_SECRET_KEY,
},
setCustomHeaders: (headers) => {
headers.set(name, value);
},
useSecureCookie: !process.env.NODE_ENV === 'test',
shouldValidateUrl: !process.env.NODE_ENV === 'test',
encryption: {
key: process.env.AUTH_ENCRYPTION_KEY,
iv: process.env.AUTH_ENCRYPTION_IV,
},
edgeConfig: {
envVar: process.env.EDGE_CONFIG,
teamId: process.env.VERCEL_TEAM_ID,
authToken: process.env.VERCEL_AUTH_TOKEN,
},
});
}

krakenOAuthHandler

Handles the OAuth process. This function must be used in a Next.js API route:

Import

import { krakenOAuthHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js OAuth api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { krakenOAuthHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await krakenOAuthHandler({
req,
res,
krakenConfig: {
authEndpoint: process.env.KRAKEN_AUTH_ENDPOINT,
clientId: process.env.KRAKEN_CLIENT_ID,
clientSecret: process.env.KRAKEN_CLIENT_SECRET,
},
appRoutes: {
login: '/login',
logout: '/logout',
krakenOAuth: '/api/auth/kraken-oauth',
},
});
}

updateOrgTokenHandler

Handles the update organization token request. This is meant to be a cron job and must be used in a Next.js API route:

  • It first checks if the request is authorized by checking if the authorization header matches the CRON_SECRET environment variable. The secret can be any string value.
  • Then it updates the organization token.
  • If both the request to get the token and write the token to the edge config are successful, it returns a 200 response with the data object.
  • If the request fails, it returns a 200 response with the error object.

You will also have to configure the api route where you use this handler to be a cron job in Vercel. You can do that by creating a vercel.json file at the root of your project with the following content:

{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [
{
"path": "/api/auth/update-org-token",
"schedule": "*/55 * * * *"
}
]
}

Set the schedule value to the desired cron job schedule. The example above will run the cron job every 55 minutes. (usually, the organization token expires every hour)

Import

import { updateOrgTokenHandler } from '@krakentech/blueprint-auth';

Usage

In your Next.js update organization token api route:

import type { NextApiRequest, NextApiResponse } from 'next';
import { updateOrgTokenHandler } from '@krakentech/blueprint-auth';

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await updateOrgTokenHandler({
req,
res,
krakenConfig: {
graphqlEndpoint: process.env.KRAKEN_GRAPHQL_ENDPOINT,
organizationSecretKey: process.env.KRAKEN_ORGANIZATION_KEY,
xClientIpSecretKey: process.env.KRAKEN_X_CLIENT_IP_SECRET_KEY,
},
encryption: {
key: process.env.AUTH_ENCRYPTION_KEY,
iv: process.env.AUTH_ENCRYPTION_IV,
},
edgeConfig: {
envVar: process.env.EDGE_CONFIG,
teamId: process.env.VERCEL_TEAM_ID,
authToken: process.env.VERCEL_AUTH_TOKEN,
},
});
}

createKrakenOAuthURI

Creates the Kraken OAuth URI.

Import

import { createKrakenOAuthURI } from '@krakentech/blueprint-auth';

Usage

In your Next.js page where you want to redirect the user to the Kraken OAuth page. This URI should be used in a button or link to redirect the user to the Kraken OAuth page.

import { createKrakenOAuthURI } from '@krakentech/blueprint-auth';

...

export const getServerSideProps: GetServerSideProps = async (context) => {
const { req, res } = context;

const authorizeURI = await createKrakenOAuthURI({
req,
res,
krakenConfig: {
authEndpoint: process.env.KRAKEN_AUTH_ENDPOINT as string,
oauthClientId: process.env.KRAKEN_OAUTH_CLIENT_ID as string,
},
krakenOAuthApiRoute: '/api/auth/kraken-oauth',
});

return {
props: {
authorizeURI,
},
};
};

useLogin

Hook that provides a function to login a user.

Import

import { useLogin } from '@krakentech/blueprint-auth';

Usage

In your component:

import { useLogin } from '@krakentech/blueprint-auth';

const LoginButton = ({ email, password }) => {
const { mutate } = useLogin();

return <button onClick={() => mutate({ email, password })}>Login</button>;
};

useLogout

Hook that provides a function to logout a user.

Import

import { useLogout } from '@krakentech/blueprint-auth';

Usage

In your component:

import { useLogout } from '@krakentech/blueprint-auth';

const LogoutButton = () => {
const { mutate } = useLogout();

return <button onClick={() => mutate()}>Logout</button>;
};

useSession

Hook that provides the current auth session data. Bear in mind that this "@tanstack/react-query" hook has all refetch properties set to true ( refetchOnMount, refetchOnWindowFocus and refetchOnReconnect) which means that you should see quite a few requests being made to the session endpoint, especially if you keep refocusing the window when using the browser's developer tools.

Import

import { useSession } from '@krakentech/blueprint-auth';

Usage

In your component:

import { useSession } from '@krakentech/blueprint-auth';

const Page = () => {
const {
isInitialLoading,
isAuthenticated,
isMasquerading,
sub,
invalidateSessionCache,
} = useSession();

if (isInitialLoading) {
return <p>Loading...</p>;
}

return (
<div>
<p>Authenticated: {isAuthenticated ? 'Yes' : 'No'}</p>
<p>Masquerading: {isMasquerading ? 'Yes' : 'No'}</p>
<p>Sub: {sub}</p>
<button onClick={() => invalidateSessionCache()}>
Invalidate session
</button>
</div>
);
};

useKrakenAuthErrorHandler

There are two methods available in this hook:

  • getAuthErrorCode(e: unknown) - this method returns the errorCode based on the errorType (such as 'UNAUTHORIZED', 'AUTH_HEADER_NOT_PROVIDED', etc) from the server response.
  • handleKrakenErrors(onError?: (e: unknown) => void)) - this method can be used in the errorCallback prop in the Blueprint hooks and in other places where you need to handle the authentication related errors. It will check the error code and either call the custom onError function if provided, or redirect the user to the login page with the current page as the next page and the error code as a query parameter

Import

import { useKrakenAuthErrorHandler } from '@krakentech/blueprint-auth';

Usage

In your component:

import { useKrakenAuthErrorHandler } from "@krakentech/blueprint-auth";

...
// Check the error code using the error received from the server. If it's authentication related error, you will get the error code.
const { getAuthErrorCode } = useKrakenAuthErrorHandler();

...

const errorCode = getAuthErrorCode({ response: { errors: error.graphQLErrors }});

if (errorCode) {
//Do something
}


// Pass it in the errorCallback in the Blueprint hooks. Example with useMeasurements hook below
const { handleKrakenErrors } = useKrakenAuthErrorHandler();

...

const measurements = useMeasurements({
variables: {
accountNumber,
propertyId,
utilityType,
dateLocale: config.i18n.dateLocales[locale || ''],
marketSupplyPointId: prm.id,
enabled: true,
startAt: STATS_START_AT,
endAt: STATS_ANCHOR_DATE,
first: 14,
dateRange: DateRangeToggleValue.WEEK,
},
errorCallback: handleKrakenErrors,
locale,
});

getServerSideGraphQLClients

This helper function returns GraphQL clients either scoped to the user or the organization to make it easier for you to make authenticated GraphQL requests.

It's meant to be used in getServerSideProps or getStaticProps functions in Next.js pages.

Bear in mind that, if you enabled Vercel's deployment protection, you will have to also enable the "Protection Bypass for Automation" so that requests that use either of these functions can be made successfully in preview deployments as well.

redirectEntry block in Storyblok

CAVEAT - Requests made with this client in getStaticProps won't be authenticated if the scope is set to user. This is because getStaticProps runs at build time and doesn't have access to the session's cookies.

Import

import { getServerSideGraphQLClients } from '@krakentech/blueprint-auth';

Usage

In your Next.js page:

import { getServerSideGraphQLClients } from '@krakentech/blueprint-auth';
import { getVercelOrigin } from '@krakentech/blueprint-utils/environment'; // we recommend using this utility if you're hosting on Vercel

...

const ViewerQuery = gql`
query Viewer {
viewer {
preferredName
email
accounts {
number
}
}
}
`;

const CreateAccountMutation = gql`
mutation CreateAccount($input: CreateAccountInput!) {
createAccount(input: $input) {
accountNumber
token
userId
}
}
`;

export const getServerSideProps: GetServerSideProps = async (context) => {
const { userScopedGraphQLClient, organizationScopedGraphQLClient } = getServerSideGraphQLClients({
graphqlApiRoute: `${getVercelOrigin()}/api/graphql`,
context,
});

const viewer = await userScopedGraphQLClient.request(ViewerQuery);

const createdAccount = await organizationScopedGraphQLClient.request(CreateAccountMutation, {
input: {
...
},
});

return {
props: {
...,
},
};
};

useGraphQLClients

This hook returns an object with two GraphQL clients: one scoped to the user and the other to the organization.

Import

import { useGraphQLClients } from '@krakentech/blueprint-auth';

Usage

In a custom react query hook, for example:

import { useMutation } from 'react-query';
import { getGraphQLClient } from '@krakentech/blueprint-auth';

const UpdatePassword = gql`
mutation UpdatePassword($input: UpdatePasswordInput!) {
updatePassword(input: $input) {
viewer {
givenName
familyName
mobile
email
}
}
}
`;

export const useCreateAccount = (input: UpdatePasswordInput) => {
const { userScopedGraphQLClient } = useGraphQLClients();

return useMutation({
mutationFn: async () => {
return userScopedGraphQLClient.request(UpdatePassword, {
input,
});
},
});
};
import { useMutation } from 'react-query';
import { getGraphQLClient } from '@krakentech/blueprint-auth';

const CreateAccountMutation = graphql(`
mutation CreateAccount($input: CreateAccountInput!) {
createAccount(input: $input) {
accountNumber
token
userId
}
}
`);

export const useCreateAccount = (input: CreateAccountInput) => {
const { organizationScopedGraphQLClient } = useGraphQLClients();

return useMutation({
mutationFn: async () => {
return organizationScopedGraphQLClient.request(
CreateAccountMutation,
{
input,
}
);
},
});
};

setNextPageSearchParam

This function sets the nextPage search param in the URL. This is the search param that we are setting in the authMiddleware function when we redirect the user to the login page.

Import

import { setNextPageSearchParam } from '@krakentech/blueprint-auth';

Usage

import { setNextPageSearchParam } from '@krakentech/blueprint-auth';

const url = new URL(...);
const nextUrl: NextUrl = ...;
setNextPageSearchParam({ url, nextUrl });

redirectToNextPage

This function redirects the user to the nextPage search param in the URL. It sanitizes the URL to prevent XSS attacks by checking if the URL is either a relative or an absolute path.

Import

import { redirectToNextPage } from '@krakentech/blueprint-auth';

Usage

In your Next.js page:

import { redirectToNextPage } from '@krakentech/blueprint-auth';

...

const Page = () => {
const router = useRouter();

useEffect(() => {
redirectToNextPage({
router,
defaultPath: '/dashboard',
});
}, []);

return <p>Redirecting...</p>;
};