API Authentication

Is there a good example of how to add an api endpoint for authentication? I'm primarily using username and password for now but eventually I'd like make use of all the authentication methods provided by wasp, but through an api.
27 Replies
kapa.ai
kapa.ai4mo ago
Hi @JLegendz. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Wasp provides authentication and authorization support out of the box, including username and password authentication. However, it doesn't directly provide an API endpoint for authentication. Instead, it provides a mechanism to create custom API endpoints and use them in your application. Here's a basic example of how you can create a custom API endpoint:
// main.wasp
api fooBar {
fn: import { fooBar } from "@src/apis",
httpRoute: (GET, "/foo/bar")
}
// main.wasp
api fooBar {
fn: import { fooBar } from "@src/apis",
httpRoute: (GET, "/foo/bar")
}
// src/apis.js
export const fooBar = (req, res, context) => {
res.set("Access-Control-Allow-Origin", "*");
res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` });
};
// src/apis.js
export const fooBar = (req, res, context) => {
res.set("Access-Control-Allow-Origin", "*");
res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` });
};
In this example, fooBar is a custom API endpoint that sends a different message based on whether the user is authenticated or not. The context.user object is provided by Wasp and contains user session information if the user is authenticated. To use this API from your client, you can import the Axios wrapper from wasp/client/api and invoke a call. For example:
import { api } from "wasp/client/api";

async function fetchCustomRoute() {
const res = await api.get("/foo/bar");
console.log(res.data);
}
import { api } from "wasp/client/api";

async function fetchCustomRoute() {
const res = await api.get("/foo/bar");
console.log(res.data);
}
This will call the fooBar API endpoint and log the response. For authentication, you can enable it in your app declaration in the Wasp file:
app MyApp {
title: "My app",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
// other auth methods...
},
onAuthFailedRedirectTo: "/someRoute"
}
}
app MyApp {
title: "My app",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
// other auth methods...
},
onAuthFailedRedirectTo: "/someRoute"
}
}
In this example, usernameAndPassword: {} enables username and password authentication. You can add other authentication methods in the methods field. Please note that this is a simplified example and you might need to adjust it based on your requirements. For more details, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/apis). Sources: - Custom HTTP API Endpoints (https://wasp-lang.dev/docs/advanced/apis) - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai are there examples of how to setup a username and password endpoint?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, Wasp provides a built-in mechanism for setting up username and password authentication. Here's a step-by-step guide on how to set it up: 1. Enable Username Authentication: Add the following to your main.wasp file to enable username and password authentication:
app myApp {
wasp: {
version: "^0.13.0"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable username authentication
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
app myApp {
wasp: {
version: "^0.13.0"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable username authentication
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
2. Add the User Entity: You need to define a User entity in your main.wasp file. Here's an example of how to do it:
entity User {=psl
id Int @id @default(autoincrement())
tasks Task[]
psl=}
entity User {=psl
id Int @id @default(autoincrement())
tasks Task[]
psl=}
3. Add the Auth Routes and Pages: Define the routes and pages for the authentication pages in your main.wasp file:
// 4. Define the routes
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth.jsx"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth.jsx"
}
// 4. Define the routes
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth.jsx"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth.jsx"
}
4. Use Auth UI Components in Pages: Create a auth.tsx file in the src/pages folder and add the following to it:
import { LoginForm, SignupForm } from 'wasp/client/auth'
import { Link } from 'react-router-dom'

export function Login() {
return (
<Layout>
<LoginForm />
<br />
<span className="text-sm font-medium text-gray-900">
Don't have an account yet? <Link to="/signup">go to signup</Link>.
</span>
</Layout>
);
}

export function Signup() {
return (
<Layout>
<SignupForm />
<br />
<span className="text-sm font-medium text-gray-900">
I already have an account (<Link to="/login">go to login</Link>).
</span>
</Layout>
);
}
import { LoginForm, SignupForm } from 'wasp/client/auth'
import { Link } from 'react-router-dom'

export function Login() {
return (
<Layout>
<LoginForm />
<br />
<span className="text-sm font-medium text-gray-900">
Don't have an account yet? <Link to="/signup">go to signup</Link>.
</span>
</Layout>
);
}

export function Signup() {
return (
<Layout>
<SignupForm />
<br />
<span className="text-sm font-medium text-gray-900">
I already have an account (<Link to="/login">go to login</Link>).
</span>
</Layout>
);
}
For more details, you can refer to the Wasp documentation on Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai I want to authenticate from postman, not from inside the app directly.
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- To authenticate from Postman or any other external tool, you would typically send a request to the authentication endpoint of your application with the necessary credentials. However, Wasp currently doesn't provide a direct API endpoint for authentication that you can use from Postman or similar tools. That said, you can create custom API endpoints in Wasp and handle authentication manually in those endpoints. Here's a simplified example of how you might do it:
// main.wasp
api login {{
fn: import {{ login }} from "@src/apis",
httpRoute: (POST, "/login")
}}
// main.wasp
api login {{
fn: import {{ login }} from "@src/apis",
httpRoute: (POST, "/login")
}}
// src/apis.js
import bcrypt from 'bcrypt'
import jwt from 'jsonwebtoken'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export const login = async (req, res) => {{
const { username, password } = req.body

const user = await prisma.user.findUnique({ where: { username } })
if (!user) {{
res.status(401).json({ message: 'No such user found' })
return
}}

const valid = await bcrypt.compare(password, user.password)
if (!valid) {{
res.status(401).json({ message: 'Incorrect password' })
return
}}

const token = jwt.sign({ id: user.id }, 'your-secret-key')
res.json({ token })
}}
// src/apis.js
import bcrypt from 'bcrypt'
import jwt from 'jsonwebtoken'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export const login = async (req, res) => {{
const { username, password } = req.body

const user = await prisma.user.findUnique({ where: { username } })
if (!user) {{
res.status(401).json({ message: 'No such user found' })
return
}}

const valid = await bcrypt.compare(password, user.password)
if (!valid) {{
res.status(401).json({ message: 'Incorrect password' })
return
}}

const token = jwt.sign({ id: user.id }, 'your-secret-key')
res.json({ token })
}}
In this example, login is a custom API endpoint that handles username and password authentication. It uses bcrypt to compare the provided password with the hashed password stored in the database, and if the password is correct, it generates a JWT token and sends it in the response. You can then use this token to authenticate subsequent requests from Postman by including it in the Authorization header. Please note that this is a simplified example and you might need to adjust it based on your requirements. For more details, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/apis). Sources: - Custom HTTP API Endpoints (https://wasp-lang.dev/docs/advanced/apis) - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai is the user.password actually present for comparison?
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In the Wasp framework, the user's password is not directly accessible for comparison. The password is hashed before it is stored in the database for security reasons. This means you cannot retrieve the original password from the database. However, in the context of the custom login API endpoint example I provided earlier, the user.password refers to the hashed password stored in the database. The bcrypt.compare() function is used to compare the plaintext password provided in the login request with the hashed password. This function internally hashes the provided password with the same salt used for the stored password and then compares the two hashes. Please note that this example assumes that you are manually handling password hashing and storage in your custom API endpoint, which is not the typical usage of Wasp. In the standard Wasp authentication flow, password hashing and comparison are handled automatically by the framework. For more details, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/apis). Sources: - Custom HTTP API Endpoints (https://wasp-lang.dev/docs/advanced/apis) - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai I get an error when trying to access user.password in this matter.
[ Wasp ] ext-src/apis.ts(20,57): error TS2339: Property 'password' does not exist on type 'GetResult<{ id: number; createdAt: Date; email: string; username: string; lastActiveTimestamp: Date; isAdmin: boolean; stripeId: string; checkoutSessionId: string; subscriptionTier: string; subscriptionStatus: string; sendEmail: boolean; datePaid: Date; credits: number; }, unknown> & {}'.
[ Wasp ] ext-src/apis.ts(20,57): error TS2339: Property 'password' does not exist on type 'GetResult<{ id: number; createdAt: Date; email: string; username: string; lastActiveTimestamp: Date; isAdmin: boolean; stripeId: string; checkoutSessionId: string; subscriptionTier: string; subscriptionStatus: string; sendEmail: boolean; datePaid: Date; credits: number; }, unknown> & {}'.
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The error message you're seeing indicates that the password field does not exist on the user object. This is expected behavior in Wasp, as the framework automatically hashes passwords and does not include them when fetching user data from the database for security reasons. If you need to authenticate a user, you should use the built-in authentication methods provided by Wasp, which handle password hashing and comparison for you. If you're trying to create a custom login endpoint, you would typically not compare the passwords directly. Instead, you would use a function provided by Wasp or a library like bcrypt to validate the provided password. Here's an example of how you might create a custom login function using Wasp's built-in functions:
import {
createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from 'wasp/server/auth';

export const login = async (args, context) => {
const providerId = createProviderId('email', args.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throw new HttpError(400, "Unknown user")
}

const providerData = deserializeAndSanitizeProviderData(authIdentity.providerData)

// Validate the password.
if (!providerData.hashedPassword || !await bcrypt.compare(args.password, providerData.hashedPassword)) {
throw new HttpError(400, "Incorrect password")
}

// User is authenticated, return user data or token as needed.
}
import {
createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from 'wasp/server/auth';

export const login = async (args, context) => {
const providerId = createProviderId('email', args.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throw new HttpError(400, "Unknown user")
}

const providerData = deserializeAndSanitizeProviderData(authIdentity.providerData)

// Validate the password.
if (!providerData.hashedPassword || !await bcrypt.compare(args.password, providerData.hashedPassword)) {
throw new HttpError(400, "Incorrect password")
}

// User is authenticated, return user data or token as needed.
}
In this example, login is a custom function that uses Wasp's built-in functions to fetch the user's hashed password from the database and validate the provided password. Please note that this is a simplified example and you might need to adjust it based on your requirements. For more details, you can refer to the Wasp documentation (https://wasp-lang.dev/docs/advanced/apis). Sources: - Custom HTTP API Endpoints (https://wasp-lang.dev/docs/advanced/apis) - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai Why would bcrypt say false when comparing the passed in password with the hashed Password. I know the password is correct. Should the password being passed in already be hashed? Currently it's just the exact password being passed in.
MEE6
MEE64mo ago
Wohooo @JLegendz, you just became a Waspeteer level 4!
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The bcrypt.compare() function is designed to take a plaintext password as the first argument and a hashed password as the second argument. It hashes the plaintext password with the salt from the hashed password and then compares the two hashes. If they match, it returns true; otherwise, it returns false. If bcrypt.compare() is returning false even though you're sure the password is correct, there could be a few reasons: 1. Incorrect Hash: The hashed password stored in the database might not be correct. This could happen if there was an error when the password was hashed and stored. 2. Different Salt: Each time you hash a password with bcrypt, a new salt is generated. If you're hashing the password again before comparing, a different salt will be used, resulting in a different hash. 3. Password Modification: The password might have been modified after it was hashed (for example, by trimming whitespace). This would cause the hash of the modified password to be different from the stored hash. Here's an example of how you should use bcrypt.compare():
const isValidPassword = await bcrypt.compare(plainTextPassword, hashedPassword);
const isValidPassword = await bcrypt.compare(plainTextPassword, hashedPassword);
In this example, plainTextPassword is the password provided by the user and hashedPassword is the hashed password fetched from the database. If you're still having issues, you might want to check the value of the hashed password stored in the database and ensure that your passwords are being hashed correctly when they are stored. Sources: - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai Everything seems to work fine with this, except bcrypt.compare is returning false when the password is correct:
import { MiddlewareConfigFn, HttpError } from "wasp/server";
import { FooBar, GetListings, GetPromotions, GetListing, GetPromotion } from "wasp/server/api"; // This type is generated by Wasp based on the `api` declaration above.
import bcrypt from 'bcryptjs'
import { Request, Response } from 'express';
import { EmailProviderData } from 'wasp/server/auth'; // Add this import
import {

createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from 'wasp/server/auth';




export const login = async (req: Request, res: Response) => {
const { email, password } = req.body;


const providerId = createProviderId('email', email);
const authIdentity = await findAuthIdentity(providerId);

if (!authIdentity) {
throw new HttpError(400, "Unknown user");
}

const providerData = deserializeAndSanitizeProviderData(authIdentity.providerData);

// Validate the password.
const isValidPassword = await bcrypt.compare(password, (providerData as EmailProviderData).hashedPassword);

if (!isValidPassword) {
throw new HttpError(400, "Incorrect password");
} else {
console.log("CORRECT PASSWORD!");
}

// User is authenticated, return user data or token as needed.
}
import { MiddlewareConfigFn, HttpError } from "wasp/server";
import { FooBar, GetListings, GetPromotions, GetListing, GetPromotion } from "wasp/server/api"; // This type is generated by Wasp based on the `api` declaration above.
import bcrypt from 'bcryptjs'
import { Request, Response } from 'express';
import { EmailProviderData } from 'wasp/server/auth'; // Add this import
import {

createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from 'wasp/server/auth';




export const login = async (req: Request, res: Response) => {
const { email, password } = req.body;


const providerId = createProviderId('email', email);
const authIdentity = await findAuthIdentity(providerId);

if (!authIdentity) {
throw new HttpError(400, "Unknown user");
}

const providerData = deserializeAndSanitizeProviderData(authIdentity.providerData);

// Validate the password.
const isValidPassword = await bcrypt.compare(password, (providerData as EmailProviderData).hashedPassword);

if (!isValidPassword) {
throw new HttpError(400, "Incorrect password");
} else {
console.log("CORRECT PASSWORD!");
}

// User is authenticated, return user data or token as needed.
}
kapa.ai
kapa.ai4mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Your code seems correct and should work as expected. The bcrypt.compare() function is used correctly: it takes the plaintext password and the hashed password, hashes the plaintext password with the salt from the hashed password, and then compares the two hashes. If bcrypt.compare() is returning false even though the password is correct, there could be a few reasons: 1. Incorrect Hash: The hashed password stored in the database might not be correct. This could happen if there was an error when the password was hashed and stored. 2. Different Salt: Each time you hash a password with bcrypt, a new salt is generated. If you're hashing the password again before comparing, a different salt will be used, resulting in a different hash. 3. Password Modification: The password might have been modified after it was hashed (for example, by trimming whitespace). This would cause the hash of the modified password to be different from the stored hash. Here are a few debugging steps you can take: - Check the hashed password: Print out the hashed password stored in the database and the plaintext password that you're trying to compare. Make sure they are what you expect. - Check the bcrypt version: There could be compatibility issues between different versions of bcrypt. Make sure you're using a version of bcrypt that is compatible with the way the passwords were hashed. - Try a known password: Hash a known password, store it in the database, and then try to authenticate with that password. This can help you determine if the issue is with the hashing process or the comparison process. If you're still having issues, you might want to ask for help on the Wasp community forum (https://discourse.wasp-lang.dev/) or GitHub issues (https://github.com/wasp-lang/wasp/issues). Sources: - Auth Overview (https://wasp-lang.dev/docs/auth/overview) - Username & Password Authentication (https://wasp-lang.dev/docs/auth/username-and-pass) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendz4mo ago
@kapa.ai How can I confirm that bcryptjs is compatible with the way that passwords are hashed by wasp?
Want results from more Discord servers?
Add your server