updateUserAttributes not working even after giving aws.cognito.signin.user.admin Oauth permission - react-native

I have been working with AWS cognito and trying to update the preferred_username attribute.
I tried the Auth.updateUserAttributes call but in vain.
Here's the whole code :
Auth.updateUserAttributes(user, {
preferred_username: "new_username",
})
.then((data) => {
setUser(data);
console.log("updateUserAttributes succ", data);
})
.catch((error) => {
console.log("updateUserAttributes err", error);
});
})
.catch((r) => console.log("getUser err", r));
I have also checked the app client settings for read/write permission on the preferred_username attribute.
Still I get this error :
updateUserAttributes err Object {
"code": "NotAuthorizedException",
"message": "Access Token does not have required scopes",
"name": "NotAuthorizedException",
}
I tried on other attributes as well, like website but I get the same response.
Thanks.
Update
It seems user from external identity providers can't update their attributes here.
Can someone confirm or deny this ?

Related

Google Identity Services : How to refresh access_token for Google API after one hour?

I have implemented the new Google Identity Services to get an access_token to call the Youtube API.
I try to use this on an Angular app.
this.tokenClient = google.accounts.oauth2.initTokenClient({
client_id: googleApiClientId,
scope: 'https://www.googleapis.com/auth/youtube.readonly',
callback: (tokenResponse) => {
this.accessToken = tokenResponse.access_token;
},
});
When I call this.tokenClient.requestAccessToken(), I can get an access token and use the Youtube API, that works.
But after one hour, this token expires. I have this error : "Request had invalid authentication credentials."
How can I get the newly refreshed access_token transparently for the user ?
There are two authorization flows for the Google Identity Services (GIS) library:
The implicit flow, which is client-side only and uses .requestAccessToken()
The authorization code flow, which requires a backend (server-side) as well and uses .requestCode()
With the implicit flow (which is what you are using), there are no refresh tokens. It is up to the client to detect tokens aging out and to re-run the token request flow. Here is some sample code from google's examples for how to handle this:
// initialize the client
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: '', // defined at request time in await/promise scope.
});
// handler for when token expires
async function getToken(err) {
if (err.result.error.code == 401 || (err.result.error.code == 403) &&
(err.result.error.status == "PERMISSION_DENIED")) {
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
await new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
resolve(resp);
};
tokenClient.requestAccessToken();
} catch (err) {
console.log(err)
}
});
} else {
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
throw new Error(err);
}
}
// make the request
function showEvents() {
// Try to fetch a list of Calendar events. If a valid access token is needed,
// prompt to obtain one and then retry the original request.
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => getToken(err)) // for authorization errors obtain an access token
.then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err)); // cancelled by user, timeout, etc.
}
Unfortunately GIS doesn't handle any of the token refreshing for you the way that GAPI did, so you will probably want to wrap your access in some common retry logic.
The important bits are that the status code will be a 401 or 403 and the status will be PERMISSION_DENIED.
You can see the details of this example here, toggle to the async/await tab to see the full code.
To refresh the access token in a transparent way for the end-user you have to use the Refresh Token, This token will also come in the response to your call.
With this token, you can do a POST call to the URL: https://www.googleapis.com/oauth2/v4/token with the following request body
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token
refresh token never expires so you can use it any number of times. The response will be a JSON like this:
{
"access_token": "your refreshed access token",
"expires_in": 3599,
"scope": "Set of scope which you have given",
"token_type": "Bearer"
}
#victor-navarro's answer is correct, but I think the URL is wrong.
I made a POST call to https://oauth2.googleapis.com/token with a body like this and it worked for me:
client_id: <YOUR_CLIENT_ID>
client_secret: <YOUR_CLIENT_SECRET>
refresh_token: <REFRESH_TOKEN_FOR_THE_USER>
grant_type: refresh_token

Invalid CSRF when logging in to keystone

I'm entirely new to coding. I've looked around a bit, but not found anything relevant.
When logging into keystone to view our mongoDB database I get an error message saying:
Something went wrong; please refresh your browser and try again.
Doing that does not help. Neither does deleting the browser history or attempting from another lap top.
Looking at the javascript console in the browser, the error states invalid csrf.
I think this is the relevant source code in the keystone folder:
handleSubmit (e) {
e.preventDefault();
// If either password or mail are missing, show an error
if (!this.state.email || !this.state.password) {
return this.displayError('Please enter an email address and password to sign in.');
}
xhr({
url: `${Keystone.adminPath}/api/session/signin`,
method: 'post',
json: {
email: this.state.email,
password: this.state.password,
},
headers: assign({}, Keystone.csrf.header),
}, (err, resp, body) => {
if (err || body && body.error) {
return body.error === 'invalid csrf'
? this.displayError('Something went wrong; please refresh your browser and try again.')
: this.displayError('The email and password you entered are not valid.');
} else {
// Redirect to where we came from or to the default admin path
if (Keystone.redirect) {
top.location.href = Keystone.redirect;
} else {
top.location.href = this.props.from ? this.props.from : Keystone.adminPath;
}
}
});
},
How can I go about solving this / debugging the error? Thanks for any help!
This usually happens when session affinity fails. Are you using default in-memory session management? Maybe, try using a database for maintaining session state.
If you use MongoDB, Try the following config setting
'session store': 'mongo',
See 'session store' section under http://keystonejs.com/docs/configuration/#options-database for more details.

Email only authentication with Vue.js and Vuex on Firebase

I want user to be automatically authenticated (temporarily) on Firebase just by sending Email then be redirected to a welcome page asking to complete the auth process by following a link received by email.
The first part is ok, I can authenticate by just inserting email and generating a random password like the following (Vuex store action):
this.$store.dispatch('userSignUp', { email: this.email, password: this.generatePassword })
which is called by component method button v-on:click="userSignUp
Vuex action is like :
userSignUp ({commit}, payload) {
commit('setLoading', true)
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', firebaseUser)
commit('setLoading', false)
commit('setError', null)
router.push('/welcome')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
}
So far so good, the user put the email, an helper function this.generatePassword generate a random password and the user is logged in and created on firebase.
Now this user is logged in, is on a welcome page, but it doesn't know his own random password (because I don't want to).
I want this to be one shot login and if the user want to come back, has to follow the link sent by email by Firebase.
There is a firebase function [sendPasswordResetEmail][1], which seems right for the case but I connot find the way to make it working.
I did Vuex action like before :
export const actions = {
sendPasswordReset ({commit}, payload) {
commit('setLoading', true)
firebase.auth().sendPasswordResetEmail(payload.email)
.then(firebaseUser => {
commit('setUser', firebaseUser)
commit('setLoading', false)
commit('setError', null)
router.push('/welcome')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
router.push('/error')
})
},
...
which is called by component method button v-on:click="userSignUp
methods: {
userSignUp () {
this.$store.dispatch('userSignUp', { email: this.email, password: this.generatePassword })
this.$store.dispatch('sendPasswordReset', { email: this.email })
}
},
I only get response code
{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalid",
"message": "EMAIL_NOT_FOUND"
}
],
"code": 400,
"message": "EMAIL_NOT_FOUND"
}
}
while the Request Payload seems ok anyway :
{requestType: "PASSWORD_RESET", email: "luca.soave#gmail.com"}
email
:
"luca.soave#gmail.com"
requestType
:
"PASSWORD_RESET"
Any idea ?
The provider you're using is called the password provider. As its name implies it is heavily dependent on the user having (and knowing) a password. Since you are looking for passwordless authentication, I'd recommend against using the email+password provider as the basis.
Instead consider implementing a custom authentication provider. While this involves a few more components, it is not as difficult as you may think. You'll need to run trusted code, which you can do either on a server you already have, or on Cloud Functions. In either of those cases, you'll use one of the Admin SDKs to implement the sensitive parts of the authentication flow.
A quick list of steps that I think you'll need:
Create an endpoint (e.g. a HTTP triggered Cloud Function) for the user to request an authentication email.
Implement the code for this endpoint to:
Generate a random one-time code in there, which you're going to send to the user. Firebase Authentication calls this the out-of-band (or OOB) code, since it's sent to the user on a different medium than your app.
Store this code and the user's email address somewhere where only your server-side code can read it, e.g. in the Firebase Database or Cloud Firestore.
Send an email to the user, with the code or a link to a page that includes the code and their email address.
Create an endpoint (e.g. again a HTTP function, or web page) where the user enters (e.g. by clicking on a link in the email) the OOB code and their email address.
Compare the code the user entered, to the one you stored before.
If the codes match, generate a custom token for the user and send it to them.
The user/app now signs into Firebase with the custom token.

Retrieve Google+ activity list of an user

I need to get the list of activities of an user in Google+. My coding platform is node.js Express framework and I'm using google-api-nodejs-client package.
var googleapis = require('googleapis');
var auth = new googleapis.OAuth2Client();
var accessToken="XXXXXX......";
googleapis
.discover('plus', 'v1')
.execute(function (err, client) {
if (err) {
console.log('Problem during the client discovery.', err);
return;
}
auth.setCredentials({
access_token: accessToken
});
client
.plus.people.get({ userId: 'me' })
.withAuthClient(auth)
.execute(function (err, response) {
console.log('My profile details------>', response);
});
client
.plus.activities.list({
userId: 'me',
collection: 'public',
maxResults: 100
})
.withAuthClient(auth)
.execute(function (err, response) {
console.log('err----------------------------->',err);//EDIT
console.log('activities---------------------->', response.items);
});
});
I got my profile details. But the activity is returning value: null. I checked my Google+ page to make sure that I have public posts. Also, I shared some posts to 'public' myself. Please help me find the bug in my code.
EDIT
Actually, there is an error. I found it by logging the value of err object in console as advised by Ryan Seys.
err--------------->
{
"error": {
"errors": [
{
"domain": "global",
"reason": "insufficientPermissions",
"message": "Insufficient Permission"
}
],
"code": 403,
"message": "Insufficient Permission"
}
}
It would help if you provide the value of the err object but here's a few thoughts:
Do you have Google+ API turned on for your project? See https://console.developers.google.com/ and the APIs and auth section of your project to enable the API.
Are you requesting the right scopes for user profile data. See https://developers.google.com/apis-explorer/#p/plus/v1/plus.activities.list to try out the request. Click the OAuth button on that page to see the different types of scopes you may like to try requesting from the user. Some of the scopes I see right now are:
https://www.googleapis.com/auth/plus.login (Know your basic profile info and list of people in your circles).
https://www.googleapis.com/auth/plus.me (Know who you are on Google)
https://www.googleapis.com/auth/userinfo.email (View your email address)
https://www.googleapis.com/auth/userinfo.profile (View basic information about your account)
Try adding an empty body field to the API request. This is a caveat of the current API client and some requests require you to enter a default empty {} after the parameter object.
client
.plus.activities.list({
userId: 'me',
collection: 'public',
maxResults: 100
}, {}) // <--- see the extra {} here!
.withAuthClient(auth)
.execute(function (err, response) {
console.log('activities---------------------->', response.items);
});
I think the problem is that you are specifying an empty fields parameter to client.plus.activities.list() instead of not providing a fields parameter at all. This tells it to return no fields for the results. Since the fields parameter is optional, you can omit it completely.
Try something like:
client
.plus.activities.list({
userId: 'me',
collection: 'public',
maxResults: 100
})

FB.login returns Invalid OAuth access token

I've been developing an app for the past few weeks and up until now there have been no issues. Just a couple days ago a strange bug has started occurring:
My application uses the PHP SDK and implements the Javascript SDK for user authorization. The user is allowed to roam the application freely, but when they click on a video, FB.login is called to request permissions from the user and get an access token.
jQuery Code
FB.login(function(response) {
if (response.authResponse) {
//Set global vars
fb_uid = response.authResponse.userID;
fb_token = response.authResponse.accessToken;
//If user has already authorized the app
if (response.status === 'connected') {
//Create the user record
$.ajax(site_url + '/facebook/create_fb_user', {
type: 'POST',
dataType: 'json',
data: {fb_uid: fb_uid, token: fb_token},
success: function (data) {
user = data.resp.fb_user;
viewVideo(item);
}
});
};
};
}, {scope: "publish_stream"});
PHP Code
try {
$this->_fb->setAccessToken($this->request->post('token'));
$data = $this->_fb->api("/me");
$model = new Model_Fbuser;
$model->data = array(
'fb_uid' => $data['id'],
'fb_token' => $extended_token
);
$resp = $model->update();
return $this->render_json(array(
'success' => TRUE,
'resp' => $resp
));
} catch (Exception $e) {
return $this->render_json(array(
'success' => FALSE,
'error' => $e->getMessage(),
'token' => $this->request->post('token')
));
}
The first time the user does this, the FB.login call returns a valid access token, the PHP SDK is able to set the token, and everything works as expected.
However, should the user revoke the application's access in their App Settings, and then return to the application, they are presented with the FB.login once more, but this time, the call returns the same access token they were previously given, which has already had its access revoked. Trying to set the access token with the PHP SDK throws the exception: "Invalid OAuth access token."
Yet if I then check the token in the Facebook debugger, is says it is valid.
Edit:
Further investigation reveals that the user is issues the same access token every time in the same session. If the user logs out, then logs back in, then they will receive a new valid token. But if they try to get a new token without logging out first, Facebook reissues them the same invalid one. When trying to use this access token to query information about the user, this is the response:
{"error":{"type":"OAuthException","message":"Error validating access token: The session was invalidated explicitly using an API call."}}
If you get the "Invalid OAuth access token" error in this way, I usually call FB.logout() and then immediately call FB.login() in the callback.
FB.logout(function(response) {
// user is now logged out
FB.login(...)
});
Not ideal, but the best way to fix such a use-case.