Firebase google authentication need to double-click to log in - firebase

I try to login into firebase with google authentication (using React hooks), and I need to click on the button twice. I want the user to get into the homepage after the first click. What is wrong here?
googleLogin.js
import firebase from "firebase/app";
import { auth,app } from "../../../../config/firebase";
export const googleLogin = async (e, information, setInformation, signup, login, history) => {
var provider = new firebase.auth.GoogleAuthProvider();
firebase
.auth()
.signInWithPopup(provider)
.then( resp => {
let {user, credential,additionalUserInfo: userInfo} = resp;
let token = credential.accessToken;
if (userInfo.isNewUser) signupWithGoogle(user, credential, userInfo);
})
.then(()=>{
history.push('/')
})
.catch((error) => {
...
});
};
const signupWithGoogle = (user, credential, userInfo)=>{
app.firestore().collection('users').doc(user.uid).set({
firstName: userInfo.profile.given_name,
lastName: userInfo.profile.family_name});
const batch = app.firestore().batch();
const initData = [
{ Applied: { positionIds: [], title: 'Applied' } },
{ Contract: { positionIds: [], title: 'Contract' } },
{ Denied: { positionIds: [], title: 'Denied' } },
{ InProgress: { positionIds: [], title: 'In Progress' } },
{ ReceivedTask: { positionIds: [], title: 'Received Task' } },
];
initData.forEach((doc) => {
const docRef = app
.firestore()
.collection('users')
.doc( user.uid)
.collection('columns')
.doc(Object.keys(doc)[0]);
batch.set(docRef, Object.values(doc)[0]);
});
return batch.commit();
}

I had the same problem to login and the problem was in my HTLM code.
I was using a form.
To resolve it, I used a simpler code in HTLM.

Related

Is it possible to add more scopes to NextAuth provider during session?

I am currently using NextAuth to signIn in my application, and want to add more scopes into it while the user is already signed in so I can use the Google Fit API.
I've been reading the documentation of NextAuth and doing some research but did not find anything helpful for the current NextAuth v4 in this scope situation.
My current Google configuration:
import NextAuth from 'next-auth';
import GoogleProvider from "next-auth/providers/google"
const GOOGLE_AUTHORIZATION_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
new URLSearchParams({
prompt: 'consent',
access_type: 'offline',
response_type: 'code'
})
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: GOOGLE_AUTHORIZATION_URL,
}),
],
callbacks: {
async jwt({ token, user, account }) {
// Initial sign in
if (account && user) {
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
user
}
}
// Return previous token if the access token has not expired yet
if (Date.now() < token.accessTokenExpires) {
return token
}
// Access token has expired, try to update it
return refreshAccessToken(token)
},
async session({ session, token }) {
session.user = token.user;
session.accessToken = token.accessToken
session.error = token.error
return session
}
},
jwt: {
secret: process.env.NEXTAUTH_JWT_SECRET,
},
secret: process.env.NEXTAUTH_SECRET,
})
async function refreshAccessToken(token) {
try {
const url =
"https://oauth2.googleapis.com/token?" +
new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
})
const response = await fetch(url, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
})
const refreshedTokens = await response.json()
if (!response.ok) {
throw refreshedTokens
}
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_at * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
}
} catch (error) {
console.log(error)
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
My current code is working just fine, so I just need the scopes to authorize and use the Google Fitness API.
Actually made it work, created a file called add_scopes.js inside pages/api/auth/
export default (req, res) => {
if (req.method === 'POST') {
// construct the authorize URL with additional scopes
const scopes = 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/fitness.activity.read https://www.googleapis.com/auth/fitness.location.read'
const redirectUri = process.env.GOOGLE_CALLBACK_URL
const clientId = process.env.GOOGLE_CLIENT_ID
const authorizationUrl = `https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code&scope=${scopes}&redirect_uri=${redirectUri}&client_id=${clientId}`
// send the authorization URL to the client
res.json({ authorizationUrl });
} else {
res.status(405).end(); // Method Not Allowed
}
}
then made a button to call this api route:
import { useCallback } from 'react';
import { Button } from 'react-bootstrap';
const AddScopesButton = ({scopes=scopes}) => {
const isAuthorized = scopes.includes("https://www.googleapis.com/auth/fitness.activity.read") && scopes.includes("https://www.googleapis.com/auth/fitness.location.read")
const handleClick = useCallback(async () => {
try {
const res = await fetch("/api/auth/add_scopes", { method: "POST" });
const json = await res.json()
if (res.ok) {
window.location.href = json.authorizationUrl;
} else {
throw new Error(res.statusText);
}
} catch (error) {
console.error(error);
}
}, []);
return (
<>
{!isAuthorized && (
<Button className='mt-2' onClick={handleClick}>Add Scopes</Button>
)}
{isAuthorized && <span>Authorized</span>}
</>
);
};
export default AddScopesButton;
The only problem is if you signOut and signIn back in you need to get the authorization again, would really like to know if there is a way to save the accessToken/scopes that were authorized.

How to pass additional parameters in next-auth social login in NextJs?

I need to pass additional parameters to signIn function using next-auth in a NextJs project.
Here is what I tried.
<button
onClick={() =>
signIn(providers.facebook.id, { userType: "customer" })
}
>
<img src="images/facebook.svg" className="w-5 h-5" />
</button>
[...nextAuth].js code
import NextAuth from "next-auth";
import dbConnect from "../../../lib/dbConnect";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "../../../models/User";
import brcypt from "bcryptjs";
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
import InstagramProvider from "next-auth/providers/instagram";
dbConnect();
export default NextAuth({
session: {
strategy: "jwt",
},
secret: process.env.NEXTAUTH_SECRET,
//The providers are the authentication method
providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: "Credentials",
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
try {
const email = credentials.email;
const password = credentials.password;
const user = await User.findOne({ email: email });
if (!user) {
return null;
}
if (user) {
let allow = await signInUser({ password, user });
if (allow == true) {
return user;
} else {
return null;
}
}
} catch (error) {
return null;
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl:
"https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
}),
FacebookProvider({
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
authorization: {
params: {
userType: "customer" || "admin",
},
},
}),
InstagramProvider({
clientId: process.env.INSTAGRAM_CLIENT_ID,
clientSecret: process.env.INSTAGRAM_CLIENT_SECRET,
}),
],
pages: {
signIn: "/login",
},
database: process.env.MONGODB_URI,
callbacks: {
async jwt(token, profile) {
console.log("jwt token>>>>", token);
console.log("jwt profile>>>>", profile);
return token;
},
async session({ session, user, token }) {
if (token) {
const name = token.token.user.name;
const email = token.token.user.email;
const image = token.token.user.image;
const platform = token.token.account.provider;
handleUser(name, email, image, platform);
}
return token.token.token;
},
},
});
const signInUser = async ({ password, user }) => {
let allow = true;
if (!password) {
allow = false;
}
const isMatch = await brcypt.compare(password, user.password);
if (!isMatch) {
allow = false;
}
return allow;
};
async function handleUser(name, email, image, platform) {
console.log("Handle User>>>>>", name);
console.log("Handle email>>>>>", email);
console.log("Handle image>>>>>", image);
console.log("Handle platform>>>>>", platform);
}
Inside the callbacks function I tried logging token & profile. The additional params I passed is not being sent.
What is the right way to achieve this in Next.js?
You can find an article on the GitHub discussion section for next-auth here. The general gist of the answer is that you specify additional custom parameters in the Google Provider parameter (as below). There are other steps, but overall they appear to have solved it. Hope this helps.
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
userType: "user" || "admin", <-- THIS ONE
},
},
}),

How can i test 'auth().signInWithCredential' in firebase/app?

I am writing test code using firebase/auth and google login with react-native
But, there are always bug in my test code
my UI component is below.
import { GoogleSignin } from "#react-native-google-signin/google-signin";
import auth from "#react-native-firebase/auth";
let userInfo;
try {
userInfo = await GoogleSignin.signIn();
} catch (error) {
console.error(error);
}
const googleCredential = auth.GoogleAuthProvider.credential(userInfo.idToken);
let testPromise;
try {
testPromise = await auth().signInWithCredential(googleCredential);
} catch (error) {
// In real code environment, there are no problem.
// But, In my test environment, the engine of the code always occur error in here
console.error(error);
}
The error message is below.
console.error
TypeError: (0 , _auth.default) is not a function
my mocking part of the test code is below
jest.mock("#react-native-firebase/auth", () => {
return {
GoogleAuthProvider: {
credential: jest.fn().mockReturnValue({ providerId: "fakeProviderId", secret: "fakeSecret", token: "fakeToken" }),
},
signInWithCredential: jest.fn(),
};
});
some tricks are in there.
i think the way i suggests is not best practice.
but, you can do this problem like this.
In react component...
import auth from "#react-native-firebase/auth";
import { firebase } from "#react-native-firebase/auth"; << KEY POINT
test component...
const mockedFbAuth = jest.fn();
jest.mock("#react-native-firebase/auth", () => {
return {
firebase: {
auth: jest.fn().mockImplementation(() => ({
signInWithCredential: mockedFbAuth.mockReturnValue({
additionalUserInfo: {
profile: {
email: "fakeCredentialEmail",
name: "fakeCredentialName",
},
},
}),
})),
},
};
});
above this way, i can solve my problem!

How to use next-auth using ldap and prisma

i am using next-auth with ldap to authenticate user name and password. i am able to log/authenticate the user using username and password. but when i can't create user using prisma as await is not allowed inside promise.
this is my [...next-auth].js
`
const ldap = require("ldapjs");
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaClient } from "#prisma/client";
const url = `ldap://${process.env.LDAP_SERVER}`;
const prisma = new PrismaClient();
export default NextAuth({
providers: [
CredentialsProvider({
name: "LDAP",
credentials: {
username: { label: "DN", type: "text", placeholder: "" },
password: { label: "Password", type: "password" },
},
authorize: async (credentials, req) => {
// You might want to pull this call out so we're not making a new LDAP client on every login attemp
const client = ldap.createClient({
url: url,
});
return new Promise((resolve, reject) => {
client.bind(
`${credentials.username}#${process.env.LDAP_DOMAIN}`,
credentials.password,
(error) => {
if (error) {
console.log("Wrong email or password.");
reject("Wrong email or password.");
} else {
console.log("Successfully Logged In");
resolve({
username: credentials.username,
password: credentials.password,
});
}
const filter = `(sAMAccountName=${credentials.username})`;
client.search(
process.env.LDAP_BASE_DN,
{
filter,
scope: "sub",
attributes: [
"mail",
"employeeid",
"title",
"name",
"division",
"department",
"section",
],
},
(err, results) => {
if (err) {
reject(`User ${username} LDAP search error`);
}
const entries = [];
results.on("searchEntry", (entry) => {
entries.push(entry.object);
});
results.on("error", (err) => {
reject("LDAP SEARCH error");
});
results.on("end", (result) => {
if (entries.length == 0) {
reject("Something went wrong. Please try again. (AD)");
}
console.log({ entries });
const searchResult = JSON.stringify(entries[0]);
const adEmployee = JSON.parse(searchResult);
const empId = adEmployee?.employeeID;
const name = adEmployee.name;
console.log(empId);
const newUser= await prisma.user.findUnique({
where:{
oracleId:oracleId
}
})
if(!newUser){
await prisma.user.create({
data:{
oracleId:empId,
fullName:name
}
})
}
});
}
);
}
);
});
},
}),
],
pages: {
signIn: "/auth/sign-in",
},
callbacks: {
jwt: async ({ token, user }) => {
if (user) {
token.username = user.username;
token.password = user.password;
}
return token;
},
session: async ({ session, token }) => {
if (token) {
session.id = token.id;
session.username = token.username;
}
// console.log(token);
return session;
},
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXTAUTH_SECRET,
jwt: {
secret: process.env.NEXTAUTH_SECRET,
encryption: true,
},
});
`
await is not allowed inside promise, where should i call prisma.
Thanks
For this u need to use API endpoint (as prisma is used on server side and cannot be used on client side especially when you pass db url from env also not shown on frontend), your create for example /api/register where:
import { PrismaClient } from '#prisma/client';
import dotenv from 'dotenv'
dotenv.config();
const prisma = new PrismaClient();
const Handler = async (
req,
res
) => {
await prisma.$connect()
const users = await prisma.user.findMany()
//check if user u add is already in db
//if not then
try {
savedUser = await prisma.user.create({ data: new_user });
await prisma.$disconnect()
} catch (error: any) {
await prisma.$disconnect()
// show db error
return res.status(501).json({message: error.message})
}
res.status(200).json({ message: 'User added to db ' + savedUser.name });
}
this is just a simple explanation of what you need to do to make it work, you may add some safety:
const { username, password } = req.body
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
if (!username || !password) {
return res.status(400).json({ message: 'Username and password are required' });
}
then u call api endpoint:
const response = await axios.post(
LOGIN_URL,
JSON.stringify({ username, password }),
{
headers: { 'Content-Type': 'application/json' },
withCredentials: true
}
)
where LOGIN_URL could be /api/register

Cannot get Firebase Cloud Messages into Flutter Application

I create a simple interface to send notification using firebase cloud messaging service. It displays notification when I send notification using firebase - console. But when I insert data using my interface to firebase, It is not showing anything. How can I solve this problem.
Future<void> insertItem() async {
final form = formKey.currentState;
form.save();
Firestore.instance.runTransaction((transaction) async {
await transaction.set(Firestore.instance.collection("Notification").document(), {
'User': _user,
'Title': _title,
'Body': _msg,
});
});}
-Insert Data into Firebase Code-
exports.notificationTrigger = functions.firestore
.document("Notification/{notificationId}")
.onCreate((snapshot, context) => {
msgData = snapshot.data();
var userRef = admin.firestore().collection("Token").doc(msgData.User);
return userRef.get().then((doc) => {
if (!doc.exists) {
console.log("No Devices");
} else {
token = doc.data().Token;
var payload = {
notification: {
title: msgData.Title,
body: msgData.Body,
},
data: {
sendername: msgData.Title,
message: msgData.Body,
},
};
return admin
.messaging()
.sendToDevice(token, payload)
.then(() => {
console.log("pushed notification");
})
.catch((err) => {
console.log(err);
});
}
});});
index.js -

Resources