When I do Google authentication on Android, the idToken becomes NULL - expo

I do have one problem.
I have searched for events with similar problems, but none of them have led to a solution.
When I do Google authentication on Android, the idToken becomes NULL.
Are there any comrades who have solved this problem?
import * as GoogleSignIn from 'expo-google-sign-in';
await GoogleSignIn.initAsync({
clientId: String(Config.IOS_CLIENT_ID),
});
const isAvailability = await GoogleSignIn.getPlayServiceAvailability();
if (isAvailability) {
const { type, user } = await GoogleSignIn.signInAsync();
console.log(user?.auth);
// {"idToken": null, "refreshToken": null}
}
How to get idToken from Expo GoogleSignIn API (expo v32)?

Try to use webClientId instead of clientId in initAsync
It worked for me.
await GoogleSignIn.initAsync({
webClientId: String(Config.WEB_CLIENT_ID),
});

Related

Nuxt Vuex Helper not sending Client Cookies to API

Okay, I have the bad feeling that I'm missing a key concept in what I'm doing. Hope someone can help me out with a hint.
I'm using Nuxt and Vuex Store Modules. Every fetch a Module Action does is wrapped in a helper Function (saveFetch) that I imported to decrease repetitive code, like this:
export const actions = {
async sampleAction(context, data){
...
await saveFetch(context, 'POST', '/pages', data)
...
}
}
The helper simple checks if the users accessToken is still valid, refreshes it if not and then sends the request:
export const saveFetch = async (context, method = 'POST', path, data) => {
const accessTokenExpiry = context.rootGetters['auth/getAccessTokenExpiry']
let accessToken = context.rootGetters['auth/getAccessToken']
// If the client provides an accessToken and the accessToken is expired,
// refresh the token before making the "real" fetch.
if (accessToken && accessTokenExpiry < new Date() && path !== '/auth/refresh-token') {
if (process.client) {
// Works fine
await window.$nuxt.$store.dispatch('auth/refreshToken')
} else {
// This is where the trouble starts
await context.dispatch('auth/refreshToken', null, { root: true })
}
accessToken = rootGetters['auth/getAccessToken']
}
return fetch(path, {
method,
headers: { ... },
body: JSON.stringify(data),
}
}
If the accessToken is expired the helper function dispatches a Vuex Action to refresh it. This works well on the client side, but not if that process happens on the server side.
The Problem that's coming up on the server side is, that the user has to provide a refreshToken to get a refreshed accessToken from the API. This refreshToken is stored as a HttpOnly Cookie in the Client. When logging the Nuxt request details on the API side of things I noticed, that Nuxt is not sending that cookie.
My current workaround looks like this:
export const actions = {
async refreshToken(context){
...
let refreshToken
if (process?.server && this?.app?.context?.req?.headers?.cookie) {
const parsedCookies = cookie.parse(
this.app.context.req.headers.cookie
)
refreshToken = parsedCookies?.refreshToken
}
const response = await saveFetch(context, 'POST', '/auth/refresh-token', {
refreshToken,
})
...
}
...
}
If on server side, access the req object, get the cookies from it and send the refreshToken Cookie Content in the requests body.
This looks clearly bad to me and I would love to get some feedback on how to do this better. Did I maybe miss some key concepts that would help me not get into this problem in the first place?

AWS Amplify uses guest credentials, not authenticated creds, in API requests

I am using the AWS Amplify library with MobileHub.
I have a Cognito User Pool connected, and an API Gateway (which communicates with Lambda functions). I'd like my users to sign before accessing resources, so I've enabled "mandatory sign-in" in MobileHub User Sign-In page, and the Cloud Logic page.
Authentication works fine, but when I send a GET request to my API, I receive this error:
"[WARN] 46:22.756 API - ensure credentials error": "cannot get guest credentials when mandatory signin enabled"
I understand that Amplify generates guest credentials, and has put these in my GET request. Since I've enabled "mandatory signin", this doesn't work.
But why is it use guest credentials? I've signed in -- shouldn't it use those credentials? How do I use the authenticated user's information?
Cheers.
EDIT: Here is the code from the Lambda function:
lambda function:
import { success, failure } from '../lib/response';
import * as dynamoDb from '../lib/dynamodb';
export const main = async (event, context, callback) => {
const params = {
TableName: 'chatrooms',
Key: {
user_id: 'user-abc', //event.pathParameters.user_id,
chatroom_id: 'chatroom-abc',
}
};
try {
const result = await dynamoDb.call('get', params);
if (result.Item) {
return callback(null, success(result.Item, 'Item found'));
} else {
return callback(null, failure({ status: false }, 'Item not found.'));
}
} catch (err) {
console.log(err);
return callback(null, failure({ status: false }), err);
}
}
And these small helper functions:
response.js:
export const success = (body, message) => buildResponse(200, body, message)
export const failure = (body, message) => buildResponse(500, body, message)
const buildResponse = (statusCode, body, message=null) => ({
statusCode: statusCode,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify({
...body,
message: message
})
});
dynamodb.js:
import AWS from 'aws-sdk';
AWS.config.update({ region: 'ap-southeast-2' });
export const call = (action, params) => {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}
I'm following the guide "serverless-stack" and was prompt with the same warning message, I was logging in correctly and logging out correctly and did not understand why the warning message.
In my case, in the Amplify.configure I skip to add the identity pool id, and that was the problem, User pools and federated identities are not the same.
(English is not my native language)
Have you tried checking why your SignIn request is being rejected/error prone?
Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));
// If MFA is enabled, confirm user signing
// `user` : Return object from Auth.signIn()
// `code` : Confirmation code
// `mfaType` : MFA Type e.g. SMS, TOTP.
Auth.confirmSignIn(user, code, mfaType)
.then(data => console.log(data))
.catch(err => console.log(err));
You can try this, then it would be easier for you to debug.
From the suggestions on the aws-amplify issues tracker, add an anonymous user to your cognito user pool and hard code the password in your app. Seems like there are other options but this is the simplest in my opinion.
You have to use your credentials at each request to use AWS Services :
(sample code angular)
SignIn :
import Amplify from 'aws-amplify';
import Auth from '#aws-amplify/auth';
Amplify.configure({
Auth: {
region: ****,
userPoolId: *****,
userPoolWebClientId: ******,
}
});
//sign in
Auth.signIn(email, password)
Request
import Auth from '#aws-amplify/auth';
from(Auth.currentCredentials())
.pipe(
map(credentials => {
const documentClient = new AWS.DynamoDB.DocumentClient({
apiVersion: '2012-08-10',
region: *****,
credentials: Auth.essentialCredentials(credentials)
});
return documentClient.query(params).promise()
}),
flatMap(data => {
return data
})
)

aws-amplify Authentication...how to access tokens on successful Auth.signIn?

I'm trying to figure out how to access the accessToken, refreshToken, and idToken that I receive back from aws-amplify using the Auth library.
example in docs: https://aws.github.io/aws-amplify/media/authentication_guide.html
example of my usage:
const user = await Auth.signIn(email, password);
user has a bunch of properties that are inaccessible including everything I need. In the docs, it's unclear how to get to these properties because the examples all log the result. Any ideas?
Auth.currentSession().then(res=>{
let accessToken = res.getAccessToken()
let jwt = accessToken.getJwtToken()
//You can print them to see the full objects
console.log(`myAccessToken: ${JSON.stringify(accessToken)}`)
console.log(`myJwt: ${jwt}`)
})
Auth.currentSession() will return a CognitoUserSession containing accessToken, idToken, and refreshToken.
The CognitoUserSession is actually the following: CognitoUserSession {idToken: CognitoIdToken, refreshToken: CognitoRefreshToken, accessToken: CognitoAccessToken, clockDrift: 0}
Accessing pairs within that object can be achieved through straightforward dot notation at this point.
Example: Retrieve the accessToken and log to console
Auth.currentSession().then(data => console.log(data.accessToken));
The result will be a CognitoAccessToken in the form CognitoAccessToken { jwtToken: '', payload: ''}
If you just want the jwtToken within the CognitoAccessToken, it's just dot notation all the way down (with log to console example):
Auth.currentSession().then(data => console.log(data.accessToken.jwtToken));
Note: This method also refreshes the current session if needed (reference).
I believe you can do
Auth.currentCredentials(credentials => {
const tokens = Auth.essentialCredentials(credentials);
})
where essentialCredentials will return all of the tokens
Hope this helps.
Angular 9, getting JWT token from current session :
import Auth from '#aws-amplify/auth';
Auth.currentSession().then(data => console.log("JWT", data.getAccessToken().getJwtToken()));
For those in search of the AWSCredentials:
const checkCognitoUserSession = async () => {
const getAwsCredentials = await Auth.currentCredentials();
const awsCredentials = await Auth.essentialCredentials(getAwsCredentials);
// accessKeyId, secretAccessKey, sessionToken post login
return { awsCredentials };
};
Retrieve current session using aws-amplify
Auth.currentSession() returns a CognitoUserSession object which contains JWT accessToken, idToken, and refreshToken.
Auth.currentSession()
.then((data) => console.log(data))
.catch((err) => console.log(err));
aws-amplify Docs currentSession

Expo, React Native, Share/upload picture to Facebook (story on my own page)

I am really lost here, I have an Expo React-Native application. the user can login with Facebook credentials.
loginWithFacebook = async () => {
const { type, token } = await Expo.Facebook.logInWithReadPermissionsAsync(FACBOOK_APP_ID, {
permissions: ['public_profile', 'email'],
});
if (type === 'success') {
this.callGraph(token);
} else {...});
}
};
and
callGraph = async (token) => {
const response = await fetch(`https://graph.facebook.com/me?access_token=${token}&fields=id,name,email,picture`);
const responceJSON = JSON.stringify(await response.json());
try {
await AsyncStorage.multiSet([['FBResponse', responceJSON], ['FBToken', token]]);
} catch (error) {...}
};
now I have the Token and info of the loged in user. now I get a picture from cameraRoll
pickImage = async () => {
const result = await Expo.ImagePicker.launchImageLibraryAsync({...});
};
which gives the URI of the image at the device, also I am using RNFetchBlob to again have the image as base64.
but I am really confused that how should I share/upload this picture to Facebook. Should I eject from Expo install FBSDK and do it from Android folder? or I can do it with the Graph API? or I can Link it to Expo?
I could not find a single example, just simple react-native-share samples are what I see everywhere.
Facebook integration for Expo only accepts read permissions and one cannot login with publish_actions it seems the community is still working on it. and for using fbsdk you should either eject from Expo or ask for permissions via the Expo WebBrowser integration.

How can I force a cognito token refresh from the client

I am using aws amplify and I know that the tokens get automatically refreshed when needed and that that is done behind the scenes.
What I need to do is change a custom attribute on the user in the cognito user pool via a Lambda backend process. This I can do, and it is working. However, the web client user never sees this new custom attribute and I am thinking the only way they can see it is if the token gets refreshed since the value is stored within the JWT token.
The correct solution as of 2021 is to call:
await Auth.currentAuthenticatedUser({bypassCache: true})
Here is how you can update tokens on demand (forcefully)
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
Like it's said here:
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
The access token and ID token are good for 1 hour. With Amplify you can get the info about the session using currentSession or currentUserInfo in Auth class to be able to retrieve information about tokens.
Undocumented, but you can use the refreshSession method on the User. Your next call to currentAuthenticatedUser and currentSession will have updated profile attributes (and groups)
User = Auth.currentAuthenticatedUser()
Session = Auth.currentSession()
User.refreshSession(Session.refreshToken)
#andreialecu wrote the correct answer. For full code to get the JWT:
static async amplifyRefresh() {
try {
const currentUser = await Auth.currentAuthenticatedUser({ bypassCache: true })
const currentSession = await Auth.currentSession()
const jwt = currentSession.getIdToken().getJwtToken()
// do what you want
} catch (error) {
console.log("error refreshing token: ", error)
throw error
}
}