next-auth custom auth window not defined - reactjs

I am trying to use next-auth with my backend but it doesn't work. I use version 4 with typescript. The error is
{error: 'window is not defined', status: 200, ok: true, url: null}
Why?????. Thanks a lot.
My custom API /login result is
{
"data": {
"username": "test",
"users": {
"id": 2,
"username": "test",
"email": "test#test.com",
"createdAt": "2021-05-24",
"updatedAt": "2021-05-24",
"name": "John Smith",
"id_groups": 99,
"groups": "guest",
"avatar": null
},
"timestamp": 1646808511,
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiG9.eyJpc3MiOiJodHRwOlwvXC90d2luYXBwLml0IiwiYXVkIjoiaHR0cDpcL1wvdHdpbmFwcC5pdCIsImlhdCI6MTM1Njk5OTUyNCwibmJmIjoxMzU3MDAwMDAwLCJleHAiOjE2NDY4MTIxMTEsImRhdGEiOiJtYXJjb2JvbmNpIn0.R1aAX99GHmoSPRKv4Vnzso8iRjUhrDWhPEdq4oql_r0"
},
"status": "",
"code": 200
}
Now, I'm try to configure next auth
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import gApi from "../../../api/gApi";
export default NextAuth({
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: {label: "Username",type: "text", placeholder: "username"},
password: { label: "Passwort", type: "password" },
},
async authorize(credentials) {
const resp = await gApi.post("/login", JSON.stringify(credentials));
const user = resp.data;
console.log('CALL MY API');
console.log(resp);
if ( resp.status && user) {
return user;
}
return null;
},
}),
],
callbacks: {
async jwt({ token, user, account, isNewUser }) {
if (user) {
if (user.jwt) {
token = { accessToken: user.jwt };
}
}
return token;
},
async session({ session, token }) { // this token return above jwt()
session.accessToken = token.accessToken;
return session;
},
},
pages: {
signIn: "/auth/Login",
},
});
In my login page I have e simple form and i call with:
const onSubmit: SubmitHandler<FormData> = async data => {
const resp: any = await signIn("credentials", {
username: data.username,
password: data.password,
redirect: false,
});
console.log('RESPO signin');
console.log(resp);
if (resp && !resp.error) {
router.replace('/')
} else return;
}

Related

Authenticated user shows up in console.log but can't access the object

I'm using Supabase for authentication and I get a user object when logging in.
This is how it looks like:
{
"id": "cb43b195-22cc-48c8-946a-d323f70165bd",
"aud": "authenticated",
"role": "authenticated",
"email": "joe#*******.com",
"email_confirmed_at": "2022-01-26T18:34:31.105402Z",
"phone": "",
"confirmed_at": "2022-01-26T18:34:31.105402Z",
"last_sign_in_at": "2022-02-01T18:00:27.998776Z",
"app_metadata": {
"provider": "github",
"providers": [
"github"
]
},
"user_metadata": {
"avatar_url": "https://avatars.githubusercontent.com/u/93337091?v=4",
"email": "joe#*******.com",
"email_verified": true,
"full_name": "Joe",
"iss": "https://api.github.com",
"name": "Joe",
"preferred_username": "joe",
"provider_id": "93337091",
"sub": "93337091",
"user_name": "joe"
},
"identities": [
{
"id": "93337091",
"user_id": "cb43b195-22cc-48c8-946a-d323f70165bd",
"identity_data": {
"avatar_url": "https://avatars.githubusercontent.com/u/93337091?v=4",
"email": "joe#*******.com",
"email_verified": true,
"full_name": "Joe",
"iss": "https://api.github.com",
"name": "Joe",
"preferred_username": "joe",
"provider_id": "93337091",
"sub": "93337091",
"user_name": "joe"
},
"provider": "github",
"last_sign_in_at": "2022-01-26T18:34:31.102361Z",
"created_at": "2022-01-26T18:34:31.102403Z",
"updated_at": "2022-01-26T18:34:31.102403Z"
}
],
"created_at": "2022-01-26T18:34:31.098348Z",
"updated_at": "2022-01-26T18:37:12.766+00:00",
"username": "joe",
"avatar_url": "0.181358731179603.png",
"website": null }
I'm trying to access any property but for instance, if I try to render {user.username} I get a "Cannot read username property of null" error.
Any idea why that happens?
This is the context that gives the user info - I'm using it to provide the auth data:
import { createContext, useState, useEffect, useContext } from "react";
import { supabase } from "../utils/supabase";
import { useRouter } from "next/router";
const Context = createContext();
const Provider = ({ children }) => {
const router = useRouter();
const [user, setUser] = useState(supabase.auth.user());
useEffect(() => {
const getUserProfile = async () => {
const sessionUser = supabase.auth.user();
if (sessionUser) {
const { data: profile } = await supabase
.from("profiles")
.select("*")
.eq("id", sessionUser.id)
.single();
setUser({
...sessionUser,
...profile,
});
}
};
getUserProfile();
supabase.auth.onAuthStateChange(() => {
getUserProfile();
});
}, []);
const login = async () => {
await supabase.auth.signIn({
provider: "github",
});
};
const logout = async () => {
await supabase.auth.signOut();
setUser(null);
router.push("/");
};
const exposed = {
user,
login,
logout,
};
return <Context.Provider value={exposed}>{children}</Context.Provider>;
};
export const useUser = () => useContext(Context);
export default Provider;
Thanks!
Could be because you are trying to access the user information before the API returns the data. In the component you are rendering the user data, check if the user exists first before returning your component.
if(!user) {
return <p>Loading...</p>
}
I had a similar issue and changing {user.username} to {user?.username} fixed the error for me. I am not exactly sure why, but hopefully this might help somebody.

React + fetch: adding extra characters and backslashes to my url

I have this code in React 17
useEffect(() => {
getLocalJson('../json/login/login.json', props.headers)
.then(resp => {
setFields(resp);
});
}, [props.headers]);
And the getLocalJson method is in a different file:
export const getLocalJson = async (url, headers) => {
console.log(url)
const resp = await fetch(url, {'headers': headers});
const json = await resp.json();
return json;
}
However the call to load the local JSON file from the public folder is:
Request URL: http://localhost:3000/json/login/%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5C%5Cdx%5Cjson%5Clogin%5Clogin.json
Ths is the JSON
[
{
"order": 0,
"systemName": "title",
"friendlyName": "Login",
"dataType": {
"type": "TITLE"
}
},
{
"order": 1,
"required": true,
"systemName": "username",
"friendlyName": "Username",
"errorMsg": "Invalid username",
"dataType": {
"type": "TEXT"
}
},
{
"order": 2,
"required": true,
"systemName": "password",
"friendlyName": "Password",
"errorMsg": "Invalid password",
"dataType": {
"type": "PASSWORD"
}
},
{
"order": 3,
"systemName": "title",
"friendlyName": "Login",
"dataType": {
"type": "BUTTON",
"submit": true
}
}
]
And it makes the call over and over and over
This exact code works on my ubuntu dev box, but is failing as abovw on my windows box
I think there is some issue with the way you are passing down the headers, look into the documentation to have a better idea.
Put your function in the body of your component where you're using useEffect and wrap it with useCallback like this:
const getLocalJson = useCallback( async (url, headers) => {
console.log(url)
const resp = await fetch(url, {'headers': headers});
const json = await resp.json();
return json;
},[])

'redirect_uri' is invalid for Microsoft login MSAL authentication Azure

Using login with Microsoft on Azure AD B2C I get the following error:
invalid_request: The provided value for the input parameter 'redirect_uri' is not valid. The expected value is a URI which matches a redirect URI registered for this client application.
I can reach other providers and login with email just not Microsoft.. lol.
I have searched for hours and tried everything I can think of, hopefully someone else can help identify the issue. Initially I was only able to get Microsoft login to work using https://login.microsoft.com/common or something similar but that did not use my userflow/ allow other providers. Now that I have the userflow working from my application I cannot login with Microsoft. Below is my config and code.
I initially followed the Microsoft tutorial here:
https://learn.microsoft.com/en-us/azure/developer/javascript/tutorial/single-page-application-azure-login-button-sdk-msal
then pieced others together to get it to use my userflow to execute and it works other than login with Microsoft.
Registered Application Manifest on Azure:
{
"id": "<ID>",
"acceptMappedClaims": null,
"accessTokenAcceptedVersion": 2,
"addIns": [],
"allowPublicClient": true,
"appId": "<app id>",
"appRoles": [],
"oauth2AllowUrlPathMatching": false,
"createdDateTime": "2021-06-09T22:15:39Z",
"disabledByMicrosoftStatus": null,
"groupMembershipClaims": null,
"identifierUris": [],
"informationalUrls": {
"termsOfService": null,
"support": null,
"privacy": null,
"marketing": null
},
"keyCredentials": [],
"knownClientApplications": [],
"logoUrl": null,
"logoutUrl": null,
"name": "Management",
"oauth2AllowIdTokenImplicitFlow": true,
"oauth2AllowImplicitFlow": true,
"oauth2Permissions": [],
"oauth2RequirePostResponse": false,
"optionalClaims": null,
"orgRestrictions": [],
"parentalControlSettings": {
"countriesBlockedForMinors": [],
"legalAgeGroupRule": "Allow"
},
"passwordCredentials": [],
"preAuthorizedApplications": [],
"publisherDomain": "dwsdevb2c.onmicrosoft.com",
"replyUrlsWithType": [
{
"url": "https://jwt.ms/",
"type": "Spa"
},
{
"url": "https://jwt.ms",
"type": "Spa"
},
{
"url": "http://localhost:3000/",
"type": "Spa"
},
{
"url": "http://localhost:3000",
"type": "Spa"
}
],
"requiredResourceAccess": [
{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{
"id": "37f7f235-527c-4136-accd-4a02d197296e",
"type": "Scope"
},
{
"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182",
"type": "Scope"
}
]
}
],
"samlMetadataUrl": null,
"signInUrl": "http://localhost:3000/",
"signInAudience": "AzureADandPersonalMicrosoftAccount",
"tags": [
"notApiConsumer",
"singlePageApp"
],
"tokenEncryptionKeyId": null
}
azure-authentication-config.tsx
import { Configuration, LogLevel } from '#azure/msal-browser';
const AzureActiveDirectoryAppClientId: any =
process.env.REACT_APP_AZURE_ACTIVE_DIRECTORY_APP_CLIENT_ID;
export const b2cPolicies = {
names: {
signUpSignIn: 'B2C_1_dwsdevuserflow01',
forgotPassword: 'B2C_1_dwsdevuserflow01',
editProfile: 'B2C_1_dwsdevprofileflow01',
},
authorities: {
signUpSignIn: {
authority:
'https://dwsdevb2c.b2clogin.com/dwsdevb2c.onmicrosoft.com/B2C_1_dwsdevuserflow01',
},
forgotPassword: {
authority:
'https://dwsdevb2c.b2clogin.com/dwsdevb2c.onmicrosoft.com/B2C_1_dwsdevuserflow01',
},
editProfile: {
authority:
'https://dwsdevb2c.b2clogin.com/dwsdevb2c.onmicrosoft.com/B2C_1_dwsdevprofileflow01',
},
},
authorityDomain: 'https://dwsdevb2c.b2clogin.com',
// authorityDomain: 'https://login.microsoft.com/common',
};
export const MSAL_CONFIG: Configuration = {
auth: {
clientId: AzureActiveDirectoryAppClientId,
authority: b2cPolicies.authorities.signUpSignIn.authority,
knownAuthorities: [b2cPolicies.authorityDomain],
redirectUri: window.location.origin,
postLogoutRedirectUri: window.location.origin, // Indicates the page to navigate after logout.
navigateToLoginRequestUrl: false,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.error(message);
return;
case LogLevel.Verbose:
console.error(message);
return;
case LogLevel.Warning:
console.error(message);
return;
default:
break;
}
},
},
},
};
azure-authentication-context.tsx
import {
PublicClientApplication,
AuthenticationResult,
AccountInfo,
EndSessionRequest,
RedirectRequest,
PopupRequest,
} from '#azure/msal-browser';
import { MSAL_CONFIG } from './azure-authentication-config';
export class AzureAuthenticationContext {
private myMSALObj: PublicClientApplication = new PublicClientApplication(
MSAL_CONFIG,
);
private account?: AccountInfo;
private loginRedirectRequest?: RedirectRequest;
private loginRequest?: PopupRequest;
public isAuthenticationConfigured = false;
constructor() {
// #ts-ignore
this.account = null;
this.setRequestObjects();
if (MSAL_CONFIG?.auth?.clientId) {
this.isAuthenticationConfigured = true;
}
}
private setRequestObjects(): void {
this.loginRequest = {
scopes: ['openid', 'profile'],
prompt: 'select_account',
};
this.loginRedirectRequest = {
...this.loginRequest,
redirectStartPage: MSAL_CONFIG.auth.redirectUri, //window.location.href,
};
}
login(signInType: string, setUser: any): void {
if (signInType === 'loginPopup') {
this.myMSALObj
.loginPopup(this.loginRequest)
.then((resp: AuthenticationResult) => {
this.handleResponse(resp, setUser);
})
.catch((err) => {
console.error(err);
});
} else if (signInType === 'loginRedirect') {
this.myMSALObj.loginRedirect(this.loginRedirectRequest);
}
}
logout(account: AccountInfo): void {
const logOutRequest: EndSessionRequest = {
account,
};
this.myMSALObj.logout(logOutRequest);
}
handleResponse(response: AuthenticationResult, incomingFunction: any) {
if (response !== null && response.account !== null) {
this.account = response.account;
} else {
this.account = this.getAccount();
}
if (this.account) {
incomingFunction(this.account);
}
}
private getAccount(): AccountInfo | undefined {
console.log(`loadAuthModule`);
const currentAccounts = this.myMSALObj.getAllAccounts();
if (currentAccounts === null) {
// #ts-ignore
console.log('No accounts detected');
return undefined;
}
if (currentAccounts.length > 1) {
// #ts-ignore
console.log(
'Multiple accounts detected, need to add choose account code.',
);
return currentAccounts[0];
} else if (currentAccounts.length === 1) {
return currentAccounts[0];
}
}
}
export default AzureAuthenticationContext;
In AzureAD navigate to Home => App Registrations > YOUR_APP
Under “Single-page application” you should see the Redirect URIs listed. It is my understanding that the redirectUri value under Auth in your MSAL_CONFIG file needs to match on of the URI’s listed there. Have you confirmed that is the case? I am unable to tell what ‘window.location.origin’ is producing based on your config.

Axios send strange array to React

I geting the data back from my API in React from a post request and I get just the first object of the entire Array.prototype
My API for the upload:
router.post("/uploads", upload.any(), async (req, res) => {
try {
if (!req.files) {
res.send({
status: false,
message: "No file uploaded",
});
} else {
let data = req.files;
res.send({
status: true,
message: "Files are uploaded",
data: data,
});
}
} catch (error) {
res.status(500).send(err);
}
});
POSTMAN gives me back:
{
"status": true,
"message": "Files are uploaded",
"data": [
{
"fieldname": "uploads\n",
"originalname": "46335256.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "46335256-2020-08-04.jpg",
"path": "client/uploads/46335256-2020-08-04.jpg",
"size": 19379
},
{
"fieldname": "uploads\n",
"originalname": "120360358.jpg",
"encoding": "7bit",
"mimetype": "image/jpeg",
"destination": "client/uploads/",
"filename": "120360358-2020-08-04.jpg",
"path": "client/uploads/120360358-2020-08-04.jpg",
"size": 78075
}
]
}
perfect!
this is my function in React to upload
const uploadFiles = () => {
uploadModalRef.current.style.display = "block"
uploadRef.current.innerHTML = "File(s) Uploading..."
for (let i = 0; i < validFiles.length; i++) {
const formData = new FormData()
formData.append("images", validFiles[i])
axios
.post("http://localhost:5000/api/db/uploads", formData, {
onUploadProgress: progressEvent => {
const uploadPercentage = Math.floor(
(progressEvent.loaded / progressEvent.total) * 100
)
...// code for graphic upload
},
})
.then(resp => {
console.log(resp.data.data)
resp.data.data.map(item => {
console.log(item)
})
})
.catch(() => {
... // code
}
}
and with this I get (from the console):
[{…}]
0:
destination: "client/uploads/"
encoding: "7bit"
fieldname: "images"
filename: "46335256-2020-08-04.jpg"
mimetype: "image/jpeg"
originalname: "46335256.jpg"
path: "client/uploads/46335256-2020-08-04.jpg"
size: 19379
__proto__: Object
length: 1
__proto__: Array(0)
is an array(if I map it works) but with just the first object.
How is it possible ??
I tried even with async/await but nothing changes
Where I'm mistaking?
Thanks!

Express Response: Sending an Array as JSON

I'm having an issue trying to get data from my backend express api. I'm using mongodb and mongoose too. Here's my code:
Code:
const show = (req, res) => {
const product = {}
product.array = new Array()
console.log(req.cart.product[1])
for (let i = 0; i < req.cart.product.length; i++) {
Product.find({_id: ObjectId(req.cart.product[i])},function(err,products){
if (err) {
res.sendStatus(500)
} else {
product.array.push(products)
console.log(product.array)
}
})
}
req.cart.product = product.array
res.json({
cart: req.cart.toJSON({ virtuals: true, user: req.user })
})
}
Console.logs:
[ [ { _id: 5952b57ea52d092b8d34c6b0,
name: 'test00000',
price: 0,
description: 'test',
__v: 0 } ] ]
[ [ { _id: 5952b57ea52d092b8d34c6b0,
name: 'test00000',
price: 0,
description: 'test',
__v: 0 } ],
[ { _id: 5952b57ea52d092b8d34c6b0,
name: 'test00000',
price: 0,
description: 'test',
__v: 0 } ] ]
URL Response:
{
"cart": {
"_id": "5953b153d2108941d15a7fe9",
"updatedAt": "2017-06-28T13:38:27.406Z",
"createdAt": "2017-06-28T13:38:27.406Z",
"owner": "595153ad6f18427ef38c416b",
"__v": 0,
"product": [],
"id": "5953b153d2108941d15a7fe9",
"editable": false
}
}
Everything in the console logs is what I want to return in the products array for my response but it won't populate the array when I push it. Any thoughts?
You are trying to call asynchronous code, (e.g. Db query) inside the synchronous code (e.g. for-loop). That's why it returns data to client once it gets the data for the first time. You can async or promise.all to solve the problem.
var async = require('async')
const show = (req, res) => {
const product = {}
product.array = new Array()
console.log(req.cart.product[1])
async.each(req.cart.product, function(id, cb){
Product.find({_id: ObjectId(id)},function(err,products){
if (err) {
cb(err)
} else {
product.array.push(products)
console.log(product.array)
cb()
}
})
}, function(err){
if (err) {
return res.sendStatus(500)
} else {
req.cart.product = product.array
return res.json({
cart: req.cart.toJSON({ virtuals: true, user: req.user })
})
}
})
}
Promise based solution:
const show = (req, res) => {
const product = {}
product.array = new Array()
console.log(req.cart.product[1])
const promises = []
req.cart.product.forEach(function(id){
promises.push(Product.find({_id: ObjectId(req.cart.product[i])}))
})
Promise.all(req.cart.product.map(function(id) {
return Product.find({_id: ObjectId(id)})
})).then(function(products){
req.cart.product = product.array
return res.json({
cart: req.cart.toJSON({ virtuals: true, user: req.user })
})
}).catch(function(err){
return res.sendStatus(500)
})
}

Resources