auth/popup-closed-by-user error 2 seconds after signInWithPopup - firebase-authentication

I am using firebase auth to authenticate users and do things on their behalf. I had this working before, and am having trouble figuring out what's going on now.
I'm using the example from the documentation almost unchanged:
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { initializeApp } from "firebase/app";
const firebaseConfig = {
// ...
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
export function signIn() {
const provider = new GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
console.log('before sign in');
signInWithPopup(auth, provider)
.then((result) => {
console.log('success!');
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
const user = result.user;
console.log(token, user);
}).catch((error) => {
console.error(error.code);
});
}
document.addEventListener('click', ()=>signIn());
When I click in the page, this is what happens:
The authentication popup opens and the 'before sign in' log is triggered
Wait for ~2 seconds
signInWithPopup throws a auth/popup-closed-by-user error

The issue was related to these HTTP headers I configured on my server:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
I added these when I was experimenting with SharedArrayBuffer. Removing them fixed the issue.

For me, I had this error on iOS Emulator if I tried to put this into the Info.plist
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>

Related

How to consume Next.JS Rest Endpoints secured with Amplify from a React Native app

Background:
My current stack is a Next server to use as an admin portal and REST API for a Mobile App running with Expo - React Native. The Next Server is currently hosted as a Lambda#Edge.
I have secured both the Next server and the React Native app with AWS Amplify's withAuthenticator wrapper. (I also tried specific auth packages like Next-Auth and Expo's auth package)
Problem:
However, I can't figure out how to add the Auth info (Access_token) to my REST Requests from Mobile app -> Next Server
I tried adding the tokens as bearer headers to the API without luck after that I was fairly sure it all has to be set up and sent via cookies.
BUT I am stuck on how to actually implement these cookies properly. I was hoping the endpoints:[] config could be used to set up my own domain to post to and handle the cookies. Reading the request on the server showed that it contained no Auth info when posted with this method.
Likewise using RTK Query (Preferably I add all the Auth to this instead of Amplify's API setup) I don't have the correct info to make an Authorized api request
Here are some snippets of the working page Authentication for both apps
API Endpoint /api/version:
import type { NextApiRequest, NextApiResponse } from 'next'
import { withSSRContext } from 'aws-amplify'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data | Error>,
) {
const { Auth } = withSSRContext({req})
try {
const user = await Auth.currentAuthenticatedUser()
return res.status(200).json({
version: '1.0.0',
user: user.username,
})
} catch (err) {
console.log(err)
return res.status(200).json({
message: 'Unauthenticated',
})
}
}
Mobile App Config:
import {
useAuthenticator,
withAuthenticator,
} from '#aws-amplify/ui-react-native'
import { Amplify, Auth } from 'aws-amplify'
import awsconfig from './aws-exports'
Amplify.configure({
...awsconfig,
API: {
endpoints: [
{
name: 'MyApi',
endpoint: 'http://NextIP:NextPort/api/',
},
],
},
})
Auth.configure(awsconfig)
export default withAuthenticator(App)
Mobile Screen:
import { API } from 'aws-amplify'
function getData() {
const apiName = 'MyApi'
const path = '/version'
const myInit = {
headers: {}, // OPTIONAL
}
return API.get(apiName, path, myInit)
}
export default function ModalScreen() {
// Get token / Cookie for auth
// const { data, isLoading, error } = useGetApiVersionQuery(null) // RTK Query
getData() // Amplify
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response)
})
return ( <></>)}
I found a solution, however, could not get the Next-Auth middleware to fire when the token was sent using the Bearer token in headers. Which is my ideal way of handling the routes.
I wrapped the getToken({req}) call so that if there is no JWT Web token it would try encode the token separately
Lastly ChatGpt somehow got me onto the package aws-jwt-verify which has everything you need to verify a token generated by aws-amplify/auth, in my case from react-native.
components/utils/auth.utils.ts
import { NextApiRequest } from 'next'
import { CognitoJwtVerifier } from 'aws-jwt-verify'
import { getToken } from 'next-auth/jwt'
// Verifier that expects valid token:
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.COGNITO_USERPOOL_ID ?? '',
tokenUse: 'id',
clientId: process.env.COGNITO_CLIENT_ID ?? '',
issuer: process.env.COGNITO_ISSUER ?? '',
})
export async function getMobileToken(req: NextApiRequest) {
let token = null
try {
token = await getToken({ req })
} catch (error) {
console.log('Could not get JWT Web Token')
}
try {
if (!token)
token = await getToken({
req,
async decode({ token }) {
if (!token) return null
const decoded = await verifier.verify(token)
return decoded
},
})
} catch (error) {
return null
}
console.log('Mobile Token:', token)
return token
}

Google email offline access using react native expo app

I am creating one app using react native expo, which allow end user to login by their google account , and then applicaton try to save the access_token so that server based applicatin can use this to send the email on their behalf ,
But when using google sing in , i am not getting refresh token and not able to send the email ,
Here is code example which i am using
I tried below method to get the access request
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId: "XXXXXXX",
androidClientId:"XXXXXXX",
iosClientId:"XXXXXXX"
});
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
const sendNotification=useNotification()
//console.log(sendNotification)
useEffect(() => {
if (response?.type === "success") {
const { id_token } = response.params;
const auth = getAuth();
const credential = GoogleAuthProvider.credential(id_token);
signInWithCredential(auth, credential);
let decoded = jwt_decode(id_token);
socialLogin(decoded)
}
}, [response]);
And on server using this code to sending email
const { google } = require('googleapis');
const path = require('path');
const fs = require('fs');
const credentials = require('./credentials.json');
// Replace with the code you received from Google
const code = 'XXXXXXX';
//const code="XXXXXXX"
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
oAuth2Client.getToken(code).then(({ tokens }) => {
console.log('first')
const tokenPath = path.join(__dirname, 'token.json');
fs.writeFileSync(tokenPath, JSON.stringify(tokens));
console.log('Access token and refresh token stored to token.json');
}).catch(err=>console.log(err));
async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: YOUR_CLIENT_ID_HERE,
scopes: ["profile", "email"],
});
if (result.type === "success") {
onSignIn(result);
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
return { error: true };
}
}
Well, I tried to create an application with Google login. To use the Google Sign-In method in a React Native Expo app, you will need to perform the following steps:
Set up a project in the Google Cloud Console and obtain a configuration file for your app.
Install the expo-google-sign-in package in your React Native app.
Import the GoogleSignIn object from the expo-google-sign-in package and use the initAsync method to initialize the Google Sign-In process.
Use the GoogleSignIn.askForPlayServicesAsync method to check if the device has the required Google Play Services installed.
Use the GoogleSignIn.signInAsync method to prompt the user to sign in with their Google account.
Once the user has signed in, you can use the accessToken and refreshToken properties of the returned object to make authorized requests to the Google APIs.
The code lines for the above steps are:
import { GoogleSignIn } from 'expo-google-sign-in';
// Initialize the Google Sign-In process
await GoogleSignIn.initAsync({
// Your config. values
});
// Check if the device has the required Google Play Services installed
const isPlayServicesAvailable = await GoogleSignIn.askForPlayServicesAsync();
if (!isPlayServicesAvailable) {
console.error('Google Play services are not available on this device.');
return;
}
// Prompt the user to sign in with their Google account
const { accessToken, refreshToken } = await GoogleSignIn.signInAsync();

AuthSession proxy error when trying to implement Expo AuthSession

I'm trying to get my expo AuthSession to work (sign up with google) in my react native app. I have been following this clip (https://www.youtube.com/watch?v=YX7IWOQIKA0) on youtube but get an error as soon as i try to navigate to the login screen.
The error message i get is:
Cannot use the AuthSession proxy because the project full name is not defined. Prefer AuthRequest (with the useProxy option set to false) in combination with an Expo Development Client build of your application. To continue using the AuthSession proxy, specify the project full name (#owner/slug) using the projectNameForProxy option.
Notes:
The app is working overall but breaks completely when i try to navigate to the LoginScreen where the code below exists. I have checked that the "https://auth.expo.io/#owner/slug" in the Google Cloud Credentials is correct. Have i missed to add something that i should've? I should also add that i am aiming to get this to work on both IOS and Android devices.
My relevant code to the problem is in my LoginScreen.tsx and it looks like this:
Imports:
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';
WebBrowser.maybeCompleteAuthSession();
GoogleAuthRequest:
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: '*THE CLIENT ID FROM GOOGLE CLOUD CREDENTIALS IS PASTED HERE*',
iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
webClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
});
Code that runs when pressing "continue with google":
const signUpGoogleHandler = async () => {
const response = await promptAsync();
if (response.type === 'success') {
const {access_token } = response.params;
console.log('res params ', access_token);
}
console.log('test123');
const provider = new GoogleAuthProvider();
await signInWithRedirect(auth, provider);
getRedirectResult(auth)
.then((result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
const user = result.user;
}).catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
const email = error.customData.email;
const credential = GoogleAuthProvider.credentialFromError(error);
})
}
How my Client ID for my Web application is setup in Google Cloud (the Authorized redirect URIs is written as https://auth.expo.io/#myusername/slugname):
Client ID for Web application

React Native Google Sign In User Birthday Access Problem

I am trying to get user birthday when user is registered with Google Sign In and want to display his/her info in the profile page.
Google Sign In was implemented via firebase.
And then, I went to google developer console and made
Added People Apis and then go to OAuth consent screen
Select External
Added App domain, Authorized domains, Developer contact information
Added birthday scope
Added test users
Save and Back to Dashboard
Birthday info is set as public
The problem is still my test users cannot login to Google. It says "Access Denied. your app did not complete the google verification process. The app is currently being tested. Only approved test users can access the app."
I can only login with my developer account.
And when I logged in, in the console, I can see the birthday scope is added in scopes array. However the birthday info is still not in my user object.
I use "#react-native-google-signin/google-signin": "^6.0.1" package.
Can someone help me please ?
Do I need to verify the domain/owner to be able to see birthday info ?
Or the package does not support this info ?
Why my test users cannot login even though I added them ?
My code is below
export const auth = {
initGoogleSignIn: function () {
GoogleSignin.configure({
scopes: [
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.gender.read',
'https://www.googleapis.com/auth/plus.login',
],
// scopes: ['https://www.googleapis.com/auth/plus.login'],
webClientId: Config.GOOGLE_WEB_CLIENT_ID,
offlineAccess: false,
});
},
};
import auth from '#react-native-firebase/auth';
export const googleLogin = () => {
return async dispatch => {
try {
await GoogleSignin.hasPlayServices({showPlayServicesUpdateDialog: true});
const isSignedIn = await GoogleSignin.isSignedIn();
if (isSignedIn && Platform.OS === 'android') {
await GoogleSignin.revokeAccess();
}
const {idToken} = await GoogleSignin.signIn();
const token = await GoogleSignin.getTokens();
dispatch(handleSocialSignup(COMMON.GOOGLE, token?.accessToken));
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
const userSignIn = auth().signInWithCredential(googleCredential);
Alert.alert(JSON.stringify(userSignIn));
userSignIn.then(user => Alert.alert(user)).catch(err => console.log(err));
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log('Cancelled by user');
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
} else {
Sentry.captureException(error);
Sentry.captureMessage(strings.common.undefinedError);
console.log('some other error happened', error);
dispatch(showSnackbar(strings.common.undefinedError));
}
return false;
}
const [googleUserName, setGoogleUserName] = useState('');
const getGoogleUserName = async () => {
const currentUser = await GoogleSignin.getCurrentUser();
setGoogleUserName(currentUser);
console.log('currentUser', currentUser);
};
useEffect(() => {
getGoogleUserName();
}, []);
console.log('googleUserName', googleUserName);

OctoKit with Auth0 (Github Login) in NextJS

I am building a Next JS app that has Github Login through Auth0 and uses the Octokit to fetch user info / repos.
In order to get the IDP I had to setup a management api in auth0. https://community.auth0.com/t/can-i-get-the-github-access-token/47237 which I have setup in my NodeJs server to hide the management api token as : GET /getaccesstoken endpoint
On the client side : /chooserepo page, I have the following code :
const chooserepo = (props) => {
const octokit = new Octokit({
auth: props.accessToken,
});
async function run() {
const res = await octokit.request("GET /user");
console.log("authenticated as ", res.data);
}
run();
And
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, params }) {
let { user } = getSession(req);
console.log("user from get session ", user);
let url = "http://localhost:4000/getaccesstoken/" + user.sub;
let data = await fetch(url);
let resData = await data.text();
return {
props: { accessToken: resData }, // will be passed to the page component as props
};
},
});
However, I keep getting Bad credentials error. If I directly put the access token in the Octokit it seems to work well, but doesn't work when it's fetching the access token from the server.
It seems like Octokit instance is created before server side props are sent. How do I fix it ?
I figured out the error by comparing the difference between the request headers when hardcoding and fetching access token from server. Turns out quotes and backslashes need to be replaced (and aren't visible when just console logging)