Validate Credentials
Introduction
In this recipe, you'll learn how to compare a raw text value, such as a user password, with a stored hashed password. This is critical when authenticating users securely in your application.
Before continuing, ensure you have:
- A local Hasura DDN project.
- A lambda connector added to your project.
- Familiarity with bcrypt for hashing and comparing passwords.
NB: The bcrypt library is a secure and widely supported method for password handling across various systems.
Recipe
Step 1. Write the function
- TypeScript
- Python
- Go
npm install bcryptjs
import bcrypt from "bcryptjs";
/**
* @readonly
*/
export async function comparePassword(password: string, hashedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, hashedPassword);
}
bcrypt==4.2.0
from hasura_ndc import start
from hasura_ndc.function_connector import FunctionConnector
import bcrypt
connector = FunctionConnector()
@connector.register_query
async def compare_password(password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(password.encode("utf-8"), hashed_password.encode("utf-8"))
if __name__ == "__main__":
start(connector)
go get golang.org/x/crypto/bcrypt
go get golang.org/x/net/idna@v0.26.0
package functions
import (
"context"
"golang.org/x/crypto/bcrypt"
"hasura-ndc.dev/ndc-go/types"
)
// ComparePasswordArguments defines the input arguments for the function
type ComparePasswordArguments struct {
Password string `json:"password"`
HashedPassword string `json:"hashed_password"`
}
// ComparePasswordResult defines the output result for the function
type ComparePasswordResult string
// FunctionComparePassword compares a password with a hashed password
func FunctionComparePassword(ctx context.Context, state *types.State, arguments *ComparePasswordArguments) (*ComparePasswordResult, error) {
err := bcrypt.CompareHashAndPassword([]byte(arguments.HashedPassword), []byte(arguments.Password))
if err != nil {
result := ComparePasswordResult("false")
return &result, nil
}
result := ComparePasswordResult("true")
return &result, nil
}
Step 2. Track your function
To add your function, generate the related metadata that will link together any functions in your lambda connector's source files and your API:
ddn connector introspect <connector_name>
Then, you can generate an hml
file for the function using the following command:
ddn command add <connector_name> "*"
Step 3. Create a relationship (optional)
It's a safe assumption that the argument's input type matches that of a password
field belonging to a User model; you
can create a relationship from the type to the command. This will enable you to make nested queries that will invoke
your custom business logic using the value of the field from the related model!
Create a relationship in the corresponding model's HML file.
---
kind: Relationship
version: v1
definition:
name: comparePassword
sourceType: Users
target:
command:
name: ComparePassword
mapping:
- source:
fieldPath:
- fieldName: password
target:
argument:
argumentName: hashedPassword
Step 4. Test your function
Create a new build of your supergraph:
ddn supergraph build local
In your project's explorer, you should see the new function exposed as a type and should be able to make a query like this:
If you created a relationship, you can make a query like this, too:
Wrapping up
In this guide, you learned how to securely compare raw text values to hashed passwords to authenticate users in your API. By leveraging lambda connectors with relationships, you can add custom business logic to your authentication flows.
Learn more about lambda connectors
- TypeScript Node.js connector.
- Python connector.
- Go connector.