1.2.13 • Published 7 months ago

next-auth-hashpack v1.2.13

Weekly downloads
-
License
-
Repository
github
Last release
7 months ago

next-auth-hashpack

NextAuth.js is an open-source library that simplifies the implementation of authentication in serverless and server-rendered web applications using various authentication providers. It is built on top of the Next.js framework and provides a simple API and pre-built authentication providers.

install

npm install next-auth-hashpack

UI View

Authentication Flow

The Flow

Authentication Flow

Usage

HashpackProvider

after configuring next-auth, call HashpackProvider in the providers array:

NextAuth({
    providers: [
        hashpackProvider({
            userReturnCallback: ({ accountId, network }, userPublicKey) => {
                return { id: "DavinciGraph", accountId: accountId }
            },
            privateKey: "ServerAccountPrivateKey",
            mirrorNodeAccountInfoURL: {
                testnet: 'https://testnet.mirrornode.hedera.com/api/v1/accounts',
                mainnet: 'https://mainnet-public.mirrornode.hedera.com/api/v1/accounts'
            },
            getUserPublicKey: ({accountId, network}) => {
                "mechanism to return user's public key"
                return "UserPublicKey";
            },
            checkOriginalData: ({accountId, originalData, network}) => {
                "check the originData you had sent to the user to sign with an external source like database"
                return true;
            },
            debug: true
        })
    ]
})

hashpackProvider Options

userReturnCallback mandatory this callback would be executed when user is authenticated successfully. it gets credentials inputs including user's accountId which can be used to fetch user's data from for example a database, and userPublicKey.

hashpackProvider({
    ...
    userReturnCallback: ({signedPayload, userSignature, accountId, network}, userPublicKey) => {}
})

privateKey mandatory the server's hedera hashgraph account private key, this key would be used to generate a publicKey and verify the signed data. after version 1.1.6 private key for different network is supported.

hashpackProvider({
    ...
    privateKey: "server private key",

    //or
    privateKey: (network) => {
        if( network === 'mainnet' ){
            return 'pk for mainnet.'
        }

        return 'pk for testnet';
    }

    // or
    privateKey: {
        testnet: "server private key for testnet",
        mainnet: "server private key for mainnet"
    }
})

💡 Attention: prior to 1.2.2 we had used publicKey directly but it has some issues with verifying on mainnet. therefore after that we generate the publicKey from this privateKey

mirrorNodeAccountInfoURL optional for development after version 1.0.6 you must define this variable as an object with two properties tesetnet and mainnet which both are urls pointing to a mirror node to get an account information. If left empty default urls are the public mirror nodes.

getUserPublicKey optional if you want to implement your mechanism to get user's account public key, you can pass a callback to this property. you'll get signing-in user account id as the input

hashpackProvider({
    ...
    getUserPublicKey: ({accountId, network}) => {
        return "mechanism to return user's public key"
    }
})

checkOriginalData optional if you want to add an extra security layer to check if the data you had sent to the user matches the receiving one, you can use this function.

hashpackProvider({
    ...
    checkOriginalData: ({accountId, originalData, network}) => {
        "check the originData you had sent to the user to sign with an external source like database"
        return true;
    }
})

debug optional a flag which when true shows the process of running functionalities in the console.

authInitializer

The usual next-auth providers have a similar flow to initiate authentication(since they implement OAuth standard), but with a cryptocurrency wallet this is not available. therefore we need another route, which must be created manually.

By default, a hashpack.ts file is supposed to exist in pages/api/auth containing a route that calls authInitializer as presented in the following snippet.

export default async function hashpack(req: NextApiRequest, res: NextApiResponse) {
    const accountId = AccountId("server's hedera account Id");
    const privateKey = PrivateKey.fromString("Server's hedera account private key");

    let data = {
        url: 'https://davincigraph.io', 
        data: {
            token: "a randomly generated token"
        }
    };

    // optionally you can define a preInitializingCallback and give it to authInitializer.
    // after validation of user's account ID, preInitializingCallback would receive it
    const preInitializingCallback = (accountId) => {
        // logic: e.g. store the accountId and data in the database
    }

    // return whatever authInitializer return to to prevent stale requests
    return authInitializer(req, res, accountId, privateKey, data, "testnet", preInitializingCallback, true);
    // the final argument is hedera network, and it accepts either testnet or mainnet
}

as you see, authInitializer requires the route request and response, server's Hedera hashgraph account Id and private key, and some data to be signed by both parts (server & user). also a preInitializingCallback that would run after validation of the request. debug is also available here.

💡 the pages/api/auth/hashpack.ts path is not mandatory and the route can be created anywhere needed, the client knows about it in a way which would be explained in the following.

💡 the request contains network in its body object.

Sign-in page

the other essential configuration for the next-auth is the sign-in page url. by default next-auth creates a page with this url pages/api/auth/signin. as you remembered crypto wallets cannot authenticate like oAuth providers by the default page treat all as that, so we cannot use it. instead we use some of next-auth-hashpack functionalities in the react.

to change the sign-in page url:

NextAuth({
    providers: [
        ...
    ],
    pages: {
        signIn: "/auth/signin" // you can write any path you want
    }
})

React

The previous functionalities described are responsible for handling the authentication flow related to the backend. In contrast, the following section explains how the frontend handles the authentication flow.

hashConnect

To connect to hashpack at any time, we must instantiate HashConnect and hold some of its properties as states. next-auth-hashpack requires 3 entries to interact with hashpack. the hashConnect instance, hedera network, hashConnect topic, hashConnect state, and the state holding the paired or to-be-paired user's account id.

💡 Notice that another prerequisite is to have next-auth session context set up.

💡 Import HashConnect instance from this path:

import { HashConnect } from "hashconnect/dist/esm/hashconnect"; 

useHashpackAuthentication hook

returns authenticate function & error state. when authenticate function is called it initiate the hashpack authentication flow, for example by clicking on a button.

useHashpackAuthentication(
    hashConnect, // the hashConnect instance 
    network
    hashConnectTopic, // hashConnect's connection topic state
    pairedAccountId, // paired or to be paired account id state
    singInOptions, // Optional - next-auth sign-in options
    authInitializerApiRoute ,// Optional the route that initialize the authentication, as mentioned above the default path is `pages/api/auth/hashpack.ts`
    onSuccess // a callback to run when the authentication succeeded
)

💡 use this hook when you want to completely create a new UI for the sign-in options, otherwise next-auth-hashpack provides enough customizable components to satisfy every need.

HashpackButton component

default next-auth-hashpack initializer button. it accepts all the useHashpackAuthentication inputs as props and also several props for the styling purposes.

import { HashpackButton } from "next-auth-hashpack/dist/react";

<HashpackButton
    hashConnect={hashConnect}
    network={network}
    hashConnectTopic={hashConnectTopic}
    hashConnectState={hashConnectState}
    pairedAccountId={pairedAccountId}
    signInOptions={signInOptions}
    authInitializerApiRoute={authInitializerApiRoute}
    onStart={() => {console.log("authentication starts")}}
    onSuccess={(accountId) => {console.log("successfully signed in")}}
    onError={(error) => {console.log(error)}}
    id="whatever" // this and the two following can be used for customizing styles
    styles="whatever"
    className="whatever"
    />

the content of the button also can be replace.

import { HashpackButton } from "next-auth-hashpack/dist/react";
 <HashpackButton
    hashConnect={hashConnect}
    network={network}
    hashConnectTopic={hashConnectTopic}
    hashConnectState={hashConnectState}
    pairedAccountId={pairedAccountId}
    signInOptions={signInOptions}
    authInitializerApiRoute={authInitializerApiRoute}
    id="whatever" // this and the two following can be used for customizing styles
    styles="whatever"
    className="whatever"
> Custom sign in content </HashpackButton>

if you want to handle the authentication error (showing below the button) yourself, give the button an onError callback, it receives the error as its input.

import { HashpackButton } from "next-auth-hashpack/dist/react";
 <HashpackButton
    ...
    onError={(error: string) => console.log(error)}
    ...
> Sign in </HashpackButton>

💡 If you need to use this button to show it alongside other providers in your custom way, you need it get the providers from next-auth manually and construct them, otherwise just use ProvidersCard.

ProvidersCard component

shows the hashpack button alongside the other providers for sign-in. good for the case which sign-in through several providers is available. e.g: hashpack and google or ...

import { ProvidersCard } from "next-auth-hashpack/dist/react";

<ProvidersCard 
    hashConnect={hashConnect}
    network={network}
    hashConnectTopic={hashConnectTopic}
    hashConnectState={hashConnectState}
    pairedAccountId={pairedAccountId}
    signInOptions={signInOptions}
    authInitializerApiRoute={authInitializerApiRoute} />

💡 other providers button have the css class next-auth-provider-button, use it to customize their style.

ProvidersCard component

use this component when you just want a boilerplate default sign-in page.

import { SignInSection } from "next-auth-hashpack/dist/react";

<SignInSection 
    hashConnect={hashConnect!} 
    network={network}
    hashConnectTopic={hashConnectTopic} 
    hashConnectState={hashConnectState} 
    pairedAccountId={pairingData?.accountIds[0]!}
    signInOptions={signInOptions} 
    authInitializerApiRoute={authInitializerApiRoute}/>

some notes about configurations

changing user structure

by default next-auth user object just have id, name, email and image properties, which among them a string id is mandatory. but if there is case of extending this object, do as follow:

...
    callbacks: {
        async jwt({ token, user, account, profile, isNewUser }: any) {
            if (user?.accountId) {
                // add what ever properties you want to the token
                token.accountId = user?.accountId;
            }
            return Promise.resolve(token);
        },
        async session({ session, token, user }: any) {
            if (token?.accountId) {
                // add the token properties to the session
                session.user.accountId = token?.accountId;
            }
            return Promise.resolve(session)
        }
    },
...

This way, the session in the front-end contains the user properties you want

Do not forget the jwt secret

set environment variable NEXTAUTH_SECRET or do it in the [...nextauth].ts:

...
    jwt: {
        secret: "a strong phrase"
    }
...
1.2.13

7 months ago

1.2.0

1 year ago

1.2.8

1 year ago

1.1.9

1 year ago

1.2.7

1 year ago

1.1.8

1 year ago

1.2.6

1 year ago

1.2.5

1 year ago

1.1.6

1 year ago

1.2.4

1 year ago

1.2.3

1 year ago

1.2.2

1 year ago

1.2.9

1 year ago

1.2.12

1 year ago

1.2.10

1 year ago

1.2.11

1 year ago

1.1.5

1 year ago

1.1.4

1 year ago

1.1.3

1 year ago

1.1.2

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.0.9

1 year ago

1.0.8

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.2.3

1 year ago

0.2.2

1 year ago

0.2.1

1 year ago

0.2.0

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago