Google login in Reactnative App with Strapi - react-native

I want to implement google login in react native app using strapi. Is there any proper documentation or steps to follow? I didn't understand how to do with strapi. I have found an example with react js.

Here is how to do it:
First of all, install google-signin package and do the necessary setup changes:
#react-native-google-signin/google-signin
Once you are sure that you have finished the configuration, you can perform login like below:
try {
await GoogleSignin.hasPlayServices();
await GoogleSignin.signIn();
const { accessToken } = await GoogleSignin.getTokens();
const resp = await axios.get(`/auth/google/callback?access_token=${accessToken}`);
if (resp.status !== 200) {
//Handle fail case
return;
}
const data = resp.data
// Handle the data and do your stuff like navigate to the home screen.
} catch (error: any) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} 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 {
// some other error happened
}
}

Related

SvelteKit + Hooks and MSAL.js using Azure AD B2C results in non_browser_environment

I am new to SvelteKit and i am trying to use MSAL.js with SvelteKit, the issue is i want to implement something similar to an AuthGuard/HttpInterceptor which checks to see if the user is still logged in as they navigate around the SPA or call the external API.
I am using the OAuth 2.0 authorization code flow in Azure Active Directory B2C
within in my auth.ts file i have the following code
let accountId: string = "";
const signIn = () => {
try {
msalInstance.loginRedirect(loginRequestWithApiReadWrite);
} catch (error) {
isAuthenticated.set(false);
console.warn(error);
}
}
// This captures the response from using the redirect flow to login
await msalInstance.handleRedirectPromise()
.then(response => {
if (response) {
if (response.idTokenClaims['tfp'].toUpperCase() === b2cPolicies.names.signUpSignIn.toUpperCase()) {
handleResponse(response);
}
}
})
.catch(error => {
console.log(error);
});
async function handleResponse(response: msal.AuthenticationResult) {
if (response !== null) {
user.set(response);
isAuthenticated.set(true);
setAccount(response.account);
} else {
selectAccount();
}
}
function selectAccount() {
const currentAccounts = msalInstance.getAllAccounts();
if (currentAccounts.length < 1) {
return;
} else if (currentAccounts.length > 1) {
const accounts = currentAccounts.filter(account =>
account.homeAccountId.toUpperCase().includes(b2cPolicies.names.signUpSignIn.toUpperCase())
&&
account.idTokenClaims?.iss?.toUpperCase().includes(b2cPolicies.authorityDomain.toUpperCase())
&&
account.idTokenClaims.aud === msalConfig.auth.clientId
);
if (accounts.length > 1) {
if (accounts.every(account => account.localAccountId === accounts[0].localAccountId)) { console.log("Multiple accounts belonging to the user detected. Selecting the first one.");
setAccount(accounts[0]);
} else {
console.log("Multiple users accounts detected. Logout all to be safe.");
signOut();
};
} else if (accounts.length === 1) {
setAccount(accounts[0]);
}
} else if (currentAccounts.length === 1) {
setAccount(currentAccounts[0]);
}
}
// in case of page refresh
selectAccount();
function setAccount(account: msal.AccountInfo | null) {
if (account) {
accountId = account.homeAccountId;
}
}
const authMethods = {
signIn,
getTokenRedirect
}
In a +page.svelte file i can then import the authMethods no problem, MSAL redirects me to the microsoft sign in page, i get redirected back and can then request an access token and call external API, great all is well.
<script lang='ts'>
import authMethods from '$lib/azure/auth';
<script>
<button on:click={authMethods.signIn}>Sign In</button>
However, the issue i am having is trying to implement this so i can check to see if the user is logged in against Azure B2C using a hook.server.ts file automatically. I would like to check a variable to see if the user is authenticated and if they arnt the hooks.server will redirect them to signUp by calling the authMethod within the hook, and the user will be automatically redirected to the sign in page.
In the hooks.server.ts i have the following code:
export const handle: Handle = (async ({ event, resolve }) => {
if (isAuthenticated === false) {
authRedirect.signIn();
msalInstance.handleRedirectPromise().then((response) => {
if (response) {
console.log('login with redirect succeeded: ', response)
isAuthenticated = true;
}
}).catch((error) => {
console.log('login with redirect failed: ', error)
})
}
const response = await resolve(event);
return response;
}) satisfies Handle;
When i navigate around the SvelteKit SPA, MSAL.js keeps throwing the error below, which i know is because i am running the code from the server flow rather than in the browser, so it was my understanding that if i implement the handleRedirectPromise() in both the auth.ts file and hooks.server.ts this would await the response from the signIn event and so long as i got a response i can then set isAuthenticated to true.
errorCode: 'non_browser_environment',
errorMessage: 'Login and token requests are not supported in non-browser environments.',
subError: ''
Are you required to use the MSAL library? I have got it working with https://authjs.dev/. I was using Active Directory -https://authjs.dev/reference/oauth-providers/azure-ad but there is also a flow for B2C https://authjs.dev/reference/oauth-providers/azure-ad-b2c which I haven't tried.
Then in the hooks.server.js you can do something like the below.
import { sequence } from '#sveltejs/kit/hooks';
import { redirect } from '#sveltejs/kit';
import { SvelteKitAuth } from '#auth/sveltekit';
import AzureADProvider from '#auth/core/providers/azure-ad';
import {
AZURE_AD_CLIENT_ID,
AZURE_AD_CLIENT_SECRET,
AZURE_AD_TENANT_ID
} from '$env/static/private'
const handleAuth = SvelteKitAuth({
providers: [
AzureADProvider({
clientId: AZURE_AD_CLIENT_ID,
clientSecret: AZURE_AD_CLIENT_SECRET,
tenantId: AZURE_AD_TENANT_ID
})
]
});
async function isAuthenticatedUser({ event, resolve }) {
const session = await event.locals.getSession();
if (!session?.user && event.url.pathname !== '/') {
throw redirect(302, '/');
} else if (session?.user && event.url.pathname === '/') {
throw redirect(302, '/dashboard');
}
const response = await resolve(event);
return response;
}
export const handle = sequence(handleAuth, isAuthenticatedUser);

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);

React Native Facebook User Photos is Denied

I have a react native app and i can login with facebook. However I can't get the users photo. First of all FB hash key is correct and my app is in live mode. I sent the app to APP REVIEW and the photos are always denied by team and they are telling me they can't get the photos of the users. I use "react-native-fbsdk-next": "^4.3.0" and we use our own api url for photos, not using Graph Api of FB. There is [user_photos] as well beside public_profile. Does anyone know the reason for this ? After I login to Facebook, i try to upload photo via FB and it displays a pop up saying " facebook photos permission is denied. This permission allows your app to read photos of Facebook". Why facebook team denies user photo access ? what else should do to make it work ? My login code implementation is below. I could not find anything on Google regarding this kind of issue. Please help
export const facebookLogin = snackBarBottomMargin => {
return async dispatch => {
try {
const result = await LoginManager.logInWithPermissions([
'public_profile',
'user_photos',
]);
if (!result.isCancelled) {
const data = await AccessToken.getCurrentAccessToken();
if (data && data.accessToken) {
await storage.storeData(
PREFERENCES.FB_ACCESS_TOKEN,
JSON.stringify(data),
);
return data;
} else {
console.log('Facebook result fetch token error cancelled');
return false;
}
} else {
console.log('Login cancelled');
return false;
}
} catch (error) {
dispatch(
showSnackbar(strings.login.facebookLoginError, snackBarBottomMargin),
);
return false;
}
};
};
export function handleFetchFBPhotos(accessToken, after) {
return async dispatch => {
function onSuccess(success) {
dispatch(fetchMediaSuccess(success));
console.log('success', success);
return success;
}
function onError(error) {
dispatch(fetchMediaFailed(error));
console.log('error', error);
return error;
}
try {
dispatch(fetchMediaRequest(true));
const config = {
baseURL: Config.FACEBOOK_BASE_URL,
params: {
type: 'uploaded',
fields: 'images',
after,
},
headers: {Authorization: `Bearer ${accessToken}`},
};
const response = await axios.get(FACEBOOK_PHOTOS, config);
if (response.data && response.data.data) {
console.log('response.data', response.data);
console.log('response.data.data', response.data.data);
console.log('onSuccess(response.data)', onSuccess(response.data));
return onSuccess(response.data);
}
} catch (error) {
const errorObj = getErrorResponse(
error,
Config.FACEBOOK_BASE_URL + FACEBOOK_PHOTOS,
);
console.log('onError(errorObj.message)', onError(errorObj.message));
return onError(errorObj.message);
}
};
}

React native google signin is asking for google drive permission

I have made a app with google signin but google asks for google drive permissions. how can i remove google drive permissions in the app? i don't want to show a popup that is asking for google drive permissions.
firebaseGoogleLogin = async () => {
try {
// add any configuration settings here:
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
this.setState({ userInfo: userInfo, loggedIn: true });
// create a new firebase credential with the token
const credential = firebase.auth.GoogleAuthProvider.credential(userInfo.idToken, userInfo.accessToken)
// login with credential
const firebaseUserCredential = await firebase.auth().signInWithCredential(credential);
} catch (error) {
console.log(error)
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (f.e. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
} else {
// some other error happened
}
}
}
I had the same problem and I realized you have to remove scope property from the google configure method.
GoogleSignin.configure({
scopes: ['https://www.googleapis.com/auth/drive.readonly'], // what API you want to access on behalf of the user, default is email and profile
webClientId: '--------------- your web client id ---------',
});

Implementing authentication flow with react-navigation and redux

I do't know what is happening here. I made simple app with 2 screens, on login success i want to change screen on HomePage. I am using redux, redux-saga and axios with react-native
My submit function on login screen is:
async submit() {
if(this.state.email.length == 0 || this.state.password.length == 0) {
alert("Filed is required")
} else {
if(typeof this.state.email !== 'undefined') {
if(!this.state.email.match(/[^#]+#[^.]+\..+/)) {
alert("E-mail format wrong!")
} else {
console.log('ok auth');
let user = {
email: this.state.email,
password: this.state.password
}
this.props.login(user)
}
}
}
}
Login saga write token in AsyncStorage, on login success and it works fine
export function* loginUser(action) {
const response = yield call(login_api, action.payload)
if(!response || !response.data) {
console.log('wrong login 1: ', response);
//status 422 Unprocessable Entity
return yield put(login_failure('Internal server error for login user'))
}
if(response.status === 200) {
deviceStorage.saveItem('token', response.data.token)
return yield put(login_success(response.data, response.data.token))
} else {
console.log('wrong login 2');
return yield put(login_failure('Error for login user'))
}
}
On login success, reducer returns token i Login.js and then I want to redirect in HomePage. I want to logout user every time when he comes on login page, because I use componentDidMount()
async componentDidMount() {
await deviceStorage.deleteToken('token')
}
componentWillReceiveProps(nextProps) {
console.log('enxtProps: ', nextProps.token.length);
if(nextProps.token.length > 0) {
//TOKEN EXISTS!!! length = 365
this.props.navigation.navigate('HomePage')
} else {
console.log('Error login');
}
this.setState({
token: nextProps.token
})
}
this.props.navigation.navigate('HomePage') in componentWillReceiveProps doesn't redirect on HomePage.
Can someone tell me why?
I have a couple of suggestions for your code.
In Login-Saga wait until token saved
deviceStorage.saveItem('token', response.data.token)
change to
yield deviceStorage.saveItem('token', response.data.token)
Don't use componentWillReceiveProps. It is marked as unsafe in react & remove after version 17. Use componentDidUpdate as below.
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props)
if (this.props.token !== prevProps.token && this.props.token.length > 0) {
this.props.navigation.navigate('HomePage')
} else {
console.log('Error login');
}
}