I am working with next js app. For backend I use laravel with sanctum autentication. I want to implement credential authentication (username, password) using laravel sanctum and nextjs SPA app. All these working with client side. But I can not access to protected request in getServerSideProps. It requires crsf token.
pages/login.js
import React, { useState } from 'react';
import api from '#/util/api';
import { logIn } from '#/util/auth';
const LogInPage = () => {
const [formInput, setFormInput] = useState({ username: '', password: '' });
const signIn = (e) => {
e.preventDefault();
api()
.get('/sanctum/csrf-cookie')
.then(() => {
api()
.post('/api/login', formInput)
.then((response) => {
if (response.data.error) {
console.log(response.data.error);
} else {
router.push('/')
}
});
});
};
pages/index.js (protected route)
const Home = ({ user }) => {
const [users, setUsers] = useState([]);
useEffect(() => {
api()
.get('/api/users')
.then((response) => {
setUsers(response.data);
});
}, []);
}
Question: How to do it with getServerSideProps? Or how to use this implementation using NextAuth.js? Or maybe need bridge between client to server
export async function getServerSideProps() {
let users = [];
api()
.get('/api/users')
.then((response) => {
users = response.data;
})
.catch((error) => {
console.error(error);
});
return {
props: { users },
};
}
'/api/users' route is protected, requires authentication, so it responds 401 unauthorized
Related
I was working on a project using Firebase cloud messaging react. I was sending this to my server, but it doesn't work. Surely I have tried, but I don't know what's wrong again.
Below is the code.
Here it sends a POST request to Firebase, and it should send a notification to the user.
async function sendNotification(id, userMessage) {
const headers = {
'Authorization': `key=${code}`,
'Content-Type': 'application/json'
}
const message = {
'to': `${id}`,
'content_available': true,
'apns_priority': 5,
'notification': {
body: `${userMessage}`
},
const url = 'https://fcm.googleapis.com/fcm/send'
//console.log(code)
await axios.post(url, message, {
headers: headers
})
}
const sendMessageToServer = async (e) => {
//e.preventDefault();
toggle()
const docRe = doc(database, "help", mailer);
const data = {
email: user.email,
user: newMessage,
}
//console.log(data, 'not clear')
setNewMessage('')
//console.log(data, newMessage, 'cleared')
setShow(false)
if(newMessage === '') {
}
else {
const docRef = doc(database, "users", mailer);
await updateDoc(docRe, {
msg: arrayUnion(data)
})
.then(() => {
async function p() {
const id = await getDoc(docRef)
//console.log(id.data())
sendNotification(id.data().notice, `Admin : ${data.user}`)
}
p()
})
}
Sometimes it sends to my localhost because I tested there, but it doesn't work on my Netlify app. Secondly, I noticed that it keeps generating the same token for each user, but that's not the issue, but if you can help in both I would be grateful.
export default function Dashboard() {
async function callToken() {
await getToken(messaging, {vapidKey: process.env.NOTIFICATION})
.then((code) => {
//console.log(code)
async function docRef() {
const dc = doc(database, "users", auth.currentUser.email);
await updateDoc(dc, {
notice: code
});
}
docRef()
})
}
async function requestPermission() {
await Notification.requestPermission()
.then((permission) => {
if (permission === 'granted') {
console.log('Notification permission granted.')
callToken()
}
})
}
const goTo = useNavigate();
useEffect(() => {
onAuthStateChanged(auth, (data) => {
if(!data) {
goTo('/login')
}
else {
currentBalance();
requestPermission()
}
})
})
}
Please know I imported all required modules.
I want to build my next js project in which i am using
https://www.npmjs.com/package/#react-oauth/google
but when I build it i get the following :
this is layout.js and in _app.js I have all the components wrapped in GoogleOAuthProvider
import { GoogleLogin } from '#react-oauth/google';
import {FcGoogle} from "react-icons/Fc"
import { useGoogleLogin } from '#react-oauth/google';
export default function Layout({ children }) {
const client_id = ""
const responseGoogle = (response) => {
console.log(response);
}
CUTTED (NOT RELEVANT)
const login = useGoogleLogin({
onSuccess: codeResponse => {
const { code } = codeResponse;
console.log(codeResponse)
axios.post("http://localhost:8080/api/create-tokens", { code }).then(response => {
const { res, tokens } = response.data;
const refresh_token = tokens["refresh_token"];
const db = getFirestore(app)
updateDoc(doc(db, 'links', handle), {
refresh_token : refresh_token
})
updateDoc(doc(db, 'users', useruid), {
refresh_token : refresh_token
}).then(
CUTTED (NOT RELEVANT)
)
}).catch(err => {
console.log(err.message);
})
},
onError: errorResponse => console.log(errorResponse),
flow: "auth-code",
scope: "https://www.googleapis.com/auth/calendar"
});
return (
<>
CUTTED (NOT RELEVANT)
</>
)
}
Everything works perfect in dev mode but it does not want to build
I've faced this issue too. So I use 'GoogleLogin' instead of 'useGoogleLogin', then you can custom POST method on 'onSuccess' property.
import { GoogleLogin, GoogleOAuthenProvider} from '#react-oauth/google';
return(
<GoogleOAuthProvider clientId="YOUR CLIENT ID">
<GoogleLogin
onSuccess={handleLogin}
/>
</GoogleOAuthProvider>
The async function will be like...
const handleLogin = async = (credentialResponse) => {
var obj = jwt_decode(credentialResponse.credential);
var data = JSON.stringify(obj);
console.log(data);
const data = {your data to send to server};
const config = {
method: 'POST',
url: 'your backend server or endpoint',
headers: {},
data: data
}
await axios(config)
}
Spending whole day, this solve me out. Just want to share.
You have to wrap your application within GoogleOAuthProvider component. Please keep in mind that you will need your client ID for this.
import { GoogleOAuthProvider } from '#react-oauth/google';
<GoogleOAuthProvider clientId="<your_client_id>">
<SomeComponent />
...
<GoogleLoginButton onClick={handleGoogleLogin}/>
</GoogleOAuthProvider>;
In my React application I use the context API to store the user information through the useContext hook:
const AuthContext = createContext<AuthContextType>(null!);
const useAuth = () => useContext(AuthContext);
function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User>();
// Implementations of values
const value = useMemo(() => ({ user, login, logout }), [user]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export { AuthProvider, useAuth };
Accessing the auth information works all fine and dandy in the components:
export default function CoolComponent() {
const auth = useAuth();
if (auth.user) {
// Do something
}
return <div>Hello {auth.user}</div>;
}
The thing is that my jwt-token is stored in the user object and I need it for my API calls in my service, but hooks are not allowed outside functional components. Can I circumvent this in a clever way? Some things that I can think of is to pass the token on every call to the service (not very DRY) or save the token in localStorage and then retrieve it from there in the service, but it seems unnecessary to store the same information in two different places?
Update:
Now with the service code:
const baseUrl = environment.apiUrl;
function getToken() {
// This is what I would like to get some help with
}
const headers = {
...(getToken() && { Authorization: `Bearer ${getToken()}` }),
"Content-Type": "application/json",
};
function getAllProjects(): Promise<IProject[]> {
return fetch(`${baseUrl}projects`, {
headers,
}).then((response) => response.json());
}
function createProject(project: CreateProjectDTO): Promise<IProject> {
return fetch(`${baseUrl}projects`, {
method: "POST",
headers,
body: JSON.stringify(project),
}).then((response) => response.json());
}
// + many more
export { getAllProjects, createProject };
Calling the service in a component:
useEffect(() => {
const fetchProjects = async () => {
setIsLoading(true);
try {
const allProjects = await getAllProjects();
setProjects(allProjects);
} catch (error) {
// Handle error
} finally {
setIsLoading(false);
}
};
fetchProjects();
}, []);
The React documentation says that you cannot call hooks inside JavaScript functions.
What can you do?
Use custom hooks. rename functions as useCreateProject and return your function. Then you will be able to call useAuth inside your custom hook:
const useCreateProject =() =>{
const {user} = useAuth();
function createProject(project: CreateProjectDTO): Promise<IProject> {
return fetch(`${baseUrl}projects`, {
method: "POST",
headers,
body: JSON.stringify(project),
}).then((response) => response.json());
}
return createProject
}
Then call it like this:
const createProject = useCreateProject()
useEffect(() => {
const create = async () => {
setIsLoading(true);
try {
await createProject()
} catch (error) {
// Handle error
} finally {
setIsLoading(false);
}
};
create();
}, []);
But my advice is to store the token on localStorage or in cookies. Context data will be lost when user refreshes page. However, if that is not case for you, you can continue using context.
I am setting up passwordless Auth in my Expo app using the Firebase SDK. I've gotten to the point where emails are being sent to the user's desired address with a redirect link back to the app. When the user clicks the link, they are indeed redirected but they are not being authenticated. I am receiving a generic error in the console :
ERROR: [Error: An internal error has occurred.]
But I know that my credentials are passing through properly as I have logged them out when the function runs:
isSignInWithEmailLink:true, url: exp://10.0.0.27:19000?apiKey=AIzaSyAmpd5DdsjOb-MNfVH3MgF1Gn2nT3TBcnY&oobCode=7FJTfBjM28gkn6GfBSAdgAk7wOegg9k4D5poVcylhSYAAAF8BO5gHQ&mode=signIn&lang=en
I am calling useEffect on this function:
useEffect(() => {
signInWithEmailLink();
}, []);
Send Link To Email (WORKING)
const sendSignInLinkToEmail = (email) => {
return auth
.sendSignInLinkToEmail(email, {
handleCodeInApp: true,
url: proxyUrl,
})
.then(() => {
return true;
});
};
User clicks on a link from the email to redirect to the app to Authenticate (NOT WORKING)
const signInWithEmailLink = async () => {
const url = await Linking.getInitialURL();
if (url) {
handleUrl(url);
}
Linking.addEventListener('url', ({ url }) => {
handleUrl(url);
});
};
(RETURNING ERROR)
const handleUrl = async (url) => {
const isSignInWithEmailLink = auth.isSignInWithEmailLink(url);
console.log('isSignInWithEmailLink: ', isSignInWithEmailLink, 'url', url);
if (isSignInWithEmailLink) {
try {
await auth.signInWithEmailLink(email, url);
} catch (error) {
console.log('ERROR:', error);
}
}
};
Have you enabled email sign in in your firebase console?
Are you storing the email in localStorage? It looks undefined in your logic.
Your listener should be in the useEffect hook.
I've code my code working looking like this:
const handleGetInitialURL = async () => {
const url = await Linking.getInitialURL()
if (url) {
handleSignInUrl(url)
}
}
const handleDeepLink = (event: Linking.EventType) => {
handleSignInUrl(event.url)
}
useEffect(() => {
handleGetInitialURL()
Linking.addEventListener('url', handleDeepLink)
return () => {
Linking.removeEventListener('url', handleDeepLink)
}
}, [])
You should use the onAuthStateChanged within useEffect rather than try and log the user in at that point in time. useEffect is used when you need your page to re-render based on changes.
For example:
useEffect(() => {
// onAuthStateChanged returns an unsubscriber
const unsubscribeAuth = auth.onAuthStateChanged(async authenticatedUser => {
try {
await (authenticatedUser ? setUser(authenticatedUser) : setUser(null));
setIsLoading(false);
} catch (error) {
console.log(error);
}
});
// unsubscribe auth listener on unmount
return unsubscribeAuth;
}, []);
You should invoke the user sign in method through other means such as a button to sign in, or validate user credentials at some other point within your app.
custom function:
const onLogin = async () => {
try {
if (email !== '' && password !== '') {
await auth.signInWithEmailAndPassword(email, password);
}
} catch (error) {
setLoginError(error.message);
}
};
Source: https://blog.jscrambler.com/how-to-integrate-firebase-authentication-with-an-expo-app
I'm using testcafe in an Electron-React app trying to integrate some basic e2e tests.
The test works, however it's not relevant.
I would like to know i can pass the login page and have extra clicks on the other pages.
App.e2e.js
import { Selector } from 'testcafe';
import { getPageTitle, getPageUrl, fixture, test, login } from './helpers';
const assertNoConsoleErrors = async browser => {
const { error } = await browser.getBrowserConsoleMessages();
await browser.expect(error).eql([]);
};
fixture`Electron Client`.page('../../app/app.html').afterEach(assertNoConsoleErrors);
test('should atempt login without credentials', async browser => {
await login({
email: 'name#name.com',
password: '123456',
browser,
});
await browser
.click('button[type=submit]')
.expect(getPageUrl())
.contains('/login');
const email = Selector('[name="email"]');
const password = Selector('[name="password"]');
await browser.expect(email.value).eql('name#name.com');
await browser.expect(password.value).eql('123456');
});
helpers.js
import { ClientFunction } from 'testcafe';
export const getPageTitle = ClientFunction(() => document.title);
export const fixture = (...args) => global.fixture(...args);
export const test = (...args) => global.test(...args);
export const getPageUrl = ClientFunction(() => window.location.href);
export const login = async ({ email, password, browser }) => {
await browser.typeText('[data-test="email"]', email);
await browser.typeText('[data-test="password"]', password);
await browser.click('button[type=submit]');
};
You can use the User Roles API to meet your requirements.
See an example below.
const loginAsTestUser = Role('../../app/app.html', async t => {
await t
.typeText('[data-test="email"]', email)
.typeText('[data-test="password"]', password)
.click('button[type=submit]');
});
fixture`Electron Client`
.page('../../app/app.html')
.beforeEach(async t => {
await t.useRole(loginAsTestUser);
});
.afterEach(assertNoConsoleErrors);
test('should atempt login without credentials', async browser => {
await browser
.click('button[type=submit]')
.expect(getPageUrl())
.contains('/login');
const email = Selector('[name="email"]');
const password = Selector('[name="password"]');
await browser.expect(email.value).eql('name#name.com');
await browser.expect(password.value).eql('123456');
});