Implementing authentication flow with react-navigation and redux - react-native

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

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 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 sign in stuck on white loading screen

I am trying to use #react-native-google-signin/google-signin to login on react-native but after clicking to login button and selecting my account (on an real android device), the following blank loading screen opens and nothing else happens. This is how I am trying to achieve the login.
const SignupContainer = ({ navigation }) => {
useEffect(async () => {
GoogleSignin.configure({
webClientId:
'<webClientId>',
offlineAccess: true, // if you want to access Google API on behalf of the user FROM YOUR SERVER
forceCodeForRefreshToken: true, // [Android] related to `serverAuthCode`, read the docs link below *.
iosClientId:
'iosClientId',
})
await isSignedIn()
}, [])
const signInWithGoogle = async () => {
try {
console.log('hello 1')
await GoogleSignin.hasPlayServices()
console.log('hello 2')
const { user } = await GoogleSignin.signIn()
console.log('hello 3')
console.log({user: user})
loginWithGoogle({ user })
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log('User Cancelled the Login Flow')
} else if (error.code === statusCodes.IN_PROGRESS) {
console.log('Signing In')
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
console.log('Play Services Not Available or Outdated')
} else {
console.log('Some Other Error Happened')
}
}
}
}
Hello 1 and Hello 2 get logged but Hello 3 is never reached.
This is the screen I get stuck on

React-Native-FBSDK login doesn't return email

I'm trying to use the default <'LoginButton ... > for login in the app through Facebook login, but I can't manage to get the user's email.
This is my button:
<LoginButton
publishPermissions={["email"]}
onLoginFinished={
(error, result) => {
if (error) {
alert("Login failed with error: " + error.message);
} else if (result.isCancelled) {
alert("Login was cancelled");
} else {
alert("Login was successful with permissions: " + result.grantedPermissions)
}
}
}
onLogoutFinished={() => alert("User logged out")}
/>
And this is how i try to get the user's details:
async FBGraphRequest(fields, callback) {
const accessData = await AccessToken.getCurrentAccessToken();
console.log("token= ", accessData.accessToken )
// Create a graph request asking for user information
const infoRequest = new GraphRequest('/me', {
accessToken: accessData.accessToken,
parameters: {
fields: {
string: fields
}
}
}, this.FBLoginCallback.bind(this));
// Execute the graph request created above
new GraphRequestManager().addRequest(infoRequest).start();
}
async FBLoginCallback(error, result) {
if (error) {
this.setState({
showLoadingModal: false,
notificationMessage: "facebook error"
});
} else {
// Retrieve and save user details in state. In our case with
// Redux and custom action saveUser
this.setState({
id: result.id,
email: result.email,
name: result.name
});
console.log("facebook login",result)
}
}
The console.log("facebook login",result) line returns me only the account name and id, but there is no field for te email...
What am I doing wrong?
PS.: I've also tryed to use a "custom function", but it doesn't work too (for the email, the login worked and i get only the user details like name and id):
async facebookLogin() {
// native_only config will fail in the case that the user has
// not installed in his device the Facebook app. In this case we
// need to go for webview.
let result;
try {
this.setState({showLoadingModal: true});
LoginManager.setLoginBehavior('NATIVE_ONLY');
result = await LoginManager.logInWithReadPermissions(['public_profile', 'email']);
} catch (nativeError) {
try {
LoginManager.setLoginBehavior('WEB_ONLY');
result = await LoginManager.logInWithReadPermissions(['email']);
} catch (webError) {
// show error message to the user if none of the FB screens
// did not open
}
}
console.log("facebook result 1: ", result)
// handle the case that users clicks cancel button in Login view
if (result.isCancelled) {
this.setState({
showLoadingModal: false,
notificationMessage: I18n.t('welcome.FACEBOOK_CANCEL_LOGIN')
});
} else {
// Create a graph request asking for user information
this.FBGraphRequest('id, email, name', this.FBLoginCallback);
}
}
.
.
.
<LoginButton
publishPermissions={["email"]}
onPress={
this.facebookLogin()
}
onLogoutFinished={() => alert("User logged out")}
/>
this are the field request by the app. I need to insert also the user's Email:
!!!RESOLVED!!!
the <'LoginButton ...> props for the permission is "permissions", not "readPermission"...
so the button code is:
<LoginButton
permissions={['public_profile', 'email', 'user_birthday', ]}
onClick={this.facebookLogin}
/>
// imports
import {
Settings,
AccessToken,
LoginManager,
AuthenticationToken,
Profile,
GraphRequest,
GraphRequestManager,
} from 'react-native-fbsdk-next';
//put this lines in useEffect
Settings.setAppID('2920461228193006');
Settings.initializeSDK();
LoginManager.setLoginBehavior('web_only');
// put this method on button press
LoginManager.logInWithPermissions(['public_profile', 'email'])
.then(async data => {
if (!data.isCancelled) {
console.log(data, 'this is data');
if (Platform.OS === 'ios') {
let token =
await AuthenticationToken.getAuthenticationTokenIOS();
console.log(token, 'ios token');
} else {
let token = await AccessToken.getCurrentAccessToken();
console.log(token, 'android token');
}
const infoRequest = new GraphRequest(
'/me?fields=email,name,first_name,last_name',
null,
(err, res) => {
console.log({err, res}, 'this is');
if (Object.keys(res).length != 0) {
doSocialLogin({
registerBy: 2,
token: res.id,
user: {
firstName: res.first_name,
email: res.email,
lastName: res.last_name,
},
});
}
},
);
new GraphRequestManager().addRequest(infoRequest).start();
}
})
.catch(err => {
console.log(err, 'this is fb error');
});

Vuejs Router guard works unexpected

I have router which work with errors and can't understand how to fix it.
This global router which should check jwt token expiration and handle routing. Everything worked fine before adding some functionality like isActivated account. So now I need to check if User has token and if User account is activated.
1) If user has token it should make next() otherwise next("/login") (redirect)
2) If user has token but his account is not activated yet (first time login), it should redirect on Setup page next("/setup") until he submits some information.
So this is my guard
router.beforeEach((to, from, next) => {
const token = localStorage.getItem("token");
const tokenExp = parseInt(localStorage.getItem("tokenExp"))
const isActivated = localStorage.getItem("isActivated")
const now = new Date().getTime() + 129600000
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
console.log("first")
if (requiresAuth && !token) {
next('/login');
} else if (requiresAuth && token) {
if (now > tokenExp) {
axios.post("/user/t/r", token)
.then(e => {
const token = e.headers['authorization'].replace("Bearer ", "");
localStorage.setItem("token", token);
localStorage.setItem("tokenExp", (new Date().getTime() + 172800000).toString())
if (isActivated === 'true') {
next()
} else {
next("/setup")
}
})
.catch(e => {
localStorage.removeItem("token")
localStorage.removeItem("tokenExp")
localStorage.removeItem("fullName")
localStorage.removeItem("role")
next('/login')
})
} else {
console.log("second")
if (isActivated === 'true') {
console.log("third")
next();
} else {
console.log("fourth")
next("/setup")
}
}
} else {
next();
}
})
And this is my console.log with error when I login:
You are infinitely redirecting to /setup, You code on first run hits "fourth" then sends the user to /setup where that before call is run again and your infinite loop starts.
You need to stop calling next('/setup') or next('/login') if the user is already on that page.
You need to make use of router.currentRoute in order to check you are not going to redirect to page they are already on.
https://router.vuejs.org/api/#router-currentroute