Before the server starts, all plugins etc are registered. I create my strategy and set JWT as default authentication method for the server.
await server.register(require('hapi-auth-jwt2'));
await server.register(require('~/utils/jwt-key-signer'));
server.auth.strategy(
'jwt', 'jwt',
{ key: process.env.API_KEY,
validate: true, // Temporarily using true
verifyOptions: { algorithms: [ 'HS256' ] }
});
server.auth.default('jwt');
Here's my route. I pass the payload from the handler request into a plugin that signs my key and returns a token:
'use strict';
const Boom = require('boom');
exports.plugin = {
name: 'user-create',
register: (server, options) => {
server.route({
method: 'POST',
path: '/user/create',
options: { auth: 'jwt' },
handler: async (request, h) => {
const { payload } = await request;
const { JWTKeySigner } = await server.plugins;
const token = await JWTKeySigner.signKeyReturnToken(payload);
const cookie_options = {
ttl: 365 * 24 * 60 * 60 * 1000, // expires a year from today
encoding: 'none', // we already used JWT to encode
isSecure: true, // warm & fuzzy feelings
isHttpOnly: true, // prevent client alteration
clearInvalid: false, // remove invalid cookies
strictHeader: true // don't allow violations of RFC 6265
}
return h.response({text: 'You have been authenticated!'}).header("Authorization", token).state("token", token, cookie_options);
},
options: {
description: 'Creates a user with an email and password',
notes: 'Returns created user',
tags: ['api']
}
});
}
};
Here's how I sign my key:
const jwt = require('jsonwebtoken');
exports.plugin = {
name: 'JWTKeySigner',
register: (server, options) => {
server.expose('signKeyReturnToken', async (payload) => {
jwt.sign(payload, process.env.API_KEY, { algorithm: 'HS256' }, async (err, token) => {
if (err) {
console.log(err)
}
await console.log(`TOKEN${token}`);
return token;
});
})
}
};
I then visit my route from Postman, then pass my user which contains an email address and password back to the round as JSON, and this is the response I get:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing authentication"
}
Ok, so that proves my route is successfully being protected. I now proceed to add my token into Postman:
Then I get this error:
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "An internal server error occurred"
}
If I remove the token from postman, I get the "unauthorised" error.
All I'm trying to do is block outside access to my API, and only allow people who have permission to access it. This would be regular users who signup.
When I paste my token into JWT.io, I can see my data on the right hand side of the page, but JWT tells me it's an invalid signature.
I'd really appreciate some clarity here. I'm using hapi-auth-jwt2.
Thanks in advance
Hmm, I was writing another message to you but then I checked hapi-auth-jwt2 docs for validate options and it says;
validate - (required) the function which is run once the Token has been decoded with signature
async function(decoded, request, h) where:
decoded - (required) is the decoded and verified JWT received in the request
request - (required) is the original request received from the client
h - (required) the response toolkit.
Returns an object { isValid, credentials, response } where:
isValid - true if the JWT was valid, otherwise false.
credentials - (optional) alternative credentials to be set instead of decoded.
response - (optional) If provided will be used immediately as a takeover response.
Just try
server.auth.strategy(
'jwt', 'jwt',
{ key: process.env.API_KEY,
validate: validate: () => ({isValid: true}), // Temporarily using true
verifyOptions: { algorithms: [ 'HS256' ] }
});
Then let's see if 500 error continues.
Maybe some other thing in your code that throws an error. Did you enable debug on your server setup? You should see the details of that 500 error in your server console.
Related
using next auth with custom verify request page but it won't load after sign in (i.e. the page hangs or just stays on the same page it was already on) because of the following error, anyone know the reasoning?
API resolved without sending a response for /api/auth/verify-request?provider=email&type=email, this may result in stalled requests.
/api/auth/verify-request.tsx
const Verify = () => {
return (
<div className="">
<div className="text-3xl">Check your email!!!!!!!!</div>
</div>
);
};
[...nextauth].js
export default NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
// Passwordless / email sign in
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
maxAge: 3600,
}),
],
secret: process.env.SECRET,
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
session.user.id = user.id;
return Promise.resolve(session);
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
},
},
pages: {
verifyRequest: "/auth/verify-request", // (used for check email message)
},
});
login form, tried both submitting form with csrfToken as well as tried signIn function by next-auth/react
signIn("email", { email: inputEmail });
Solved. the custom verify page had to be outside the api folder
I am working on a Next.js project, in which I have included a login system with NextAuth.
Everything was working fine at the beginning, but recently I keep getting an error every time I try to get the session.
The Error:
https://pastebin.com/Mh624N3c
StackOverflow doesn't let me post the whole error, so I had to use Pastebin.
This is the first time I encounter such an error, and I can't seem to be able to find a solution. I am using JWT as the session strategy, if that has to do anything with the issue.
This is the code I use for handling the authentication & session:
await NextAuth(req, res, {
adapter: MongoDBAdapter(clientPromise),
pages: {
signIn: "/login"
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email", placeholder: "example#email.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
const account = await Accounts.exists(credentials.email)
const cryptr = new Cryptr(Config.secret)
const encEmail = cryptr.encrypt(credentials.email)
const url = process.env.NODE_ENV == "development" ? "http://localhost:3000/" : Config.url
if (account) {
const password = cryptr.decrypt(account.password)
if (credentials.password == password) {
return {
...account,
_id: null
}
} else {
return res.redirect("/login?error=true")
}
} else {
const code = await Accounts.requestVerification(credentials.email, password)
const message = {
to: credentials.email,
from: "noreply#bytestobits.dev",
subject: "BytesToBits API - Create Your Account",
html: emailStyle(credentials?.email, url, code),
}
SendGrid.send(message).then(() => console.log("Email Sent"))
return res.redirect("/verify?email=" + encEmail)
}
}
})
],
jwt: {
secret: Config.secret,
encryption: true
},
secret: Config.secret,
session: {
strategy: "jwt"
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.user = user
}
return token
},
async session({ session, token }) {
let data = token.user
if (data) {
if (await Accounts.exists(data.email)) {
data.token = await Accounts.getToken(data.email)
}
data.tokenInfo = await Accounts.tokenInfo(data.token)
}
return data
}
}
})
This happens every time I try to fetch the session or authenticate.
When the user authenticates, a session must be formed, which can be fetched from the client for usage. However, whenever I try to authenticate of fetch the session, a "Parse Error: Header overflow" occurs.
I managed to fix the issue! Turns out the Session object was way too long and caused this error.
Basically in the data's tokenInfo field, it had a really long array. So removing that specific field fixed the issue!
When i try to login using Talend API Tester, getting this into terminal:
API resolved without sending a response for /api/auth/callback/credentials, this may result in stalled requests.
Besides, here is the image of Request:
I followed this: Next-Auth.js -> Rest API
Why I'm getting this kind of response & warning?
Below is the [..nextauth.js] file's code. What's wrong with my code?
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import prisma from '../../../lib/prisma'
const options = {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email", placeholder: "something#example.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const {email, password} = credentials
const user = await prisma.user.findFirst({ where: { email, password } })
console.log(user);
// If no error and we have user data, return it
if (user) {
return user
}
// Return null if user data could not be retrieved
return null
}
})
]
}
export default async function handle(req, res) {
console.log('I see');
NextAuth(req, res, options)
}
Note: Actually I don't want to send csrfToken in the body. Only email & password to the endpoint. Please have a look into my this question How to do authentication using NextAuth.js without login page (using postman)
How the registration is handled with a custom credential provider ( email + password)?
Currently my [...nextauth].js looks like this:
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import axios from 'axios'
import jwt from "next-auth/jwt";
const YOUR_API_ENDPOINT = process.env.NEXT_PUBLIC_API_ROOT + 'auth/store'
const providers = [
Providers.Credentials({
name: 'Credentials',
authorize: async (credentials) => {
try {
const user = await axios.post(YOUR_API_ENDPOINT,
{
password: credentials.password,
username: credentials.email
},
{
headers: {
accept: '*/*',
'Content-Type': 'application/json'
}
})
console.log('ACCESS TOKEN ----> ' + JSON.stringify(user.data.access_token, null, 2));
if (user) {
return {status: 'success', data: user.data}
}
} catch (e) {
const errorMessage = e.response.data.message
// Redirecting to the login page with error messsage in the URL
throw new Error(errorMessage + '&email=' + credentials.email)
}
}
})
]
const callbacks = {
/*
|--------------------------------------------------------------------------
| Callback : JWT
|--------------------------------------------------------------------------
*/
async jwt(token, user, account, profile, isNewUser) {
if (user) {
token.accessToken = user.data.access_token
}
return token
},
/*
|--------------------------------------------------------------------------
| Callback : Session
|--------------------------------------------------------------------------
*/
async session(session, token) {
// Store Access Token to Session
session.accessToken = token.accessToken
/*
|--------------------------------------------------------------------------
| Get User Data
|--------------------------------------------------------------------------
*/
const API_URL = 'http://localhost:3000/api';
const config = {
headers: { Authorization: `Bearer ${token.accessToken}` }
};
let userData;
await axios.get(`${API_URL}/user`, config)
.then(response => {
userData = {
id: response.data.id,
uuid: response.data.uuid,
username: response.data.username,
avatar_location: response.data.avatar_location,
gender_id: response.data.gender_id,
date_of_birth: response.data.date_of_birth,
country_id: response.data.country_id,
location: response.data.location,
about_me: response.data.about_me,
interests: response.data.interests,
website: response.data.website,
timezone: response.data.timezone,
full_name: response.data.full_name,
formatted_created_at: response.data.formatted_created_at,
formatted_last_seen: response.data.formatted_last_seen,
album_count: response.data.album_count,
total_unread_message_count: response.data.total_unread_message_count,
};
// Store userData to Session
session.user = userData
}).catch((error) => {
// Error
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
// console.log(error.response.data);
// console.log(error.response.status);
// console.log(error.response.headers);
console.log('error.response: ' + error.request);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the
// browser and an instance of
// http.ClientRequest in node.js
console.log('error.request: ' + error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
return session
}
}
const options = {
providers,
callbacks,
session: {
// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
},
secret: process.env.SECRET,
jwt: {
// signingKey: process.env.JWT_SIGNING_PRIVATE_KEY,
//
// // You can also specify a public key for verification if using public/private key (but private only is fine)
// verificationKey: process.env.JWT_SIGNING_PUBLIC_KEY,
//
// // If you want to use some key format other than HS512 you can specify custom options to use
// // when verifying (note: verificationOptions should include a value for maxTokenAge as well).
// // verificationOptions = {
// // maxTokenAge: `${maxAge}s`, // e.g. `${30 * 24 * 60 * 60}s` = 30 days
// // algorithms: ['HS512']
// // },
secret: process.env.JWT_SECRET,
},
pages: {
error: '/login' // Changing the error redirect page to our custom login page
}
}
export default (req, res) => NextAuth(req, res, options)
All tutorials I found online only shows the login/signIn without details on how to implement registration
Registration is the process of registering the user, saving new users' credentials into the database. It is independent of next-auth. You create a form, submit the form and save the form data properly into the database.
next-auth takes over when you are logged in, in other words, you are authenticated meaning that you are already a registered, genuine user. Once you pass the security checks and successfully logged in, next-auth creates a session for the user.
You can view the set up demo for v4: next-auth 4 session returns null, next.js
next-auth only supports Sign In, Sign Up and Sign Out. When a user creates an account for the first time, It registers them automatically without the need for a new set of registration process.
Why am I not getting the response with the capture details on the client side?
I am trying to implement a server side integration for PayPal's smart buttons. I have tried a few different methods, and this is the method I have had the most success with.
However, it still doesn't appear to be working 100%. Atm, clicking a button opens the payment window, I can login with the sandbox personal account, go through the checkout flow, and then I get the standard alert, but for some reason I am not getting the desired response from the server.
When I sign into sandbox paypal, on the personal account, I can see the transactions being sent successfully (they are pending, awaiting confirmation from the merchant). When I sign into the sandbox merchant account, there are no transactions available. When I take the order ID from the smart button, and send it to PayPal's api route to get the order details, it comes back as captured and completed.
Has anyone else experienced something similar with the payments not showing up on the merchant sandbox account? If I sign into the developer account, and look at the API log, I can see the orders being created and captured successfully, but they still don't show up on the merchant account.
Here's my server side code:
const express = require("express");
const router = express.Router();
// 1. Set up your server to make calls to PayPal
// 1a. Import the SDK package
const paypal = require("#paypal/checkout-server-sdk");
// 1b. Import the PayPal SDK client that was created in `Set up Server-Side SDK`.
/**
*
* PayPal HTTP client dependency
*/
const payPalClient = require("./PayPalConfig");
// route to set up a transaction
router.post("/orders/create", async (req, res) => {
// 3. Call PayPal to set up a transaction
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "4.20",
},
},
],
});
let order;
try {
order = await payPalClient.client().execute(request);
} catch (err) {
// 4. Handle any errors from the call
console.error(err);
return res.sendStatus(500);
}
// 5. Return a successful response to the client with the order ID
res.json({
orderID: order.result.id,
});
console.log(order.result.id);
});
// route to handle capturing of orders
router.post("/orders/capture", async (req, res) => {
// const captureDetails
let captureDetails = "";
// 2a. Get the order ID from the request body
const orderID = req.body.orderID;
// 3. Call PayPal to capture the order
const request = new paypal.orders.OrdersCaptureRequest(orderID);
request.requestBody({});
try {
const capture = await payPalClient.client().execute(request);
// 4. Save the capture ID to your database. Implement logic to save capture to your database for future reference.
const captureID = capture.result.purchase_units[0].payments.captures[0].id;
captureDetails = capture.result;
// await database.saveCaptureID(captureID);
res.json(captureDetails);
} catch (err) {
// 5. Handle any errors from the call
console.error(err);
return res.sendStatus(500);
}
// 6. Return a successful response to the client
// res.sendStatus(200).json({ details: captureDetails });
res.json({ details: captureDetails });
});
module.exports = router;
Here's my client side code:
// Render the PayPal button into #paypal-button-container
paypal
.Buttons({
// Call your server to set up the transaction
createOrder: function (data, actions) {
return fetch("http://localhost:3000/payment/paypal/orders/create", {
method: "post",
})
.then(function (res) {
return res.json();
})
.then(function (orderData) {
return orderData.orderID;
console.log(orderData.orderID);
});
},
// Call your server to finalize the transaction
onApprove: function (data) {
return fetch("http://localhost:3000/payment/paypal/orders/capture", {
method: "post",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
orderID: data.orderID,
}),
})
.then(function (res) {
return res;
})
.then(function (details) {
console.log(details);
alert("Transaction funds captured from " + details.payer_given_name);
});
},
})
.render("#paypal-button-container");
Here's the details being logged from the client
Response {type: "cors", url: "http://localhost:3000/payment/paypal/orders/capture", redirected: false, status: 200, ok: true, …}
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "http://localhost:3000/payment/paypal/orders/capture"
__proto__: Response
On the server side, don't specify 'details' as a key.
res.json(captureDetails);
You need to return res.json() on the client side. It hasn't parsed the json object.
When I sign into the sandbox merchant account, there are no
transactions available. When I take the order ID from the smart
button, and send it to PayPal's api route to get the order details, it
comes back as captured and completed.
You are signing in to the wrong sandbox merchant account. The correct one will depend on the sandbox clientId you are using.