Why is my user unauthorized to execute a graphql mutation with the aws-amplify api and cognito authentication? - authorization

I have a page on my Vuejs site that allows a user to update their profile details including, first name, last name, email address and phone number. When the user submits a form, there is a call to the aws-amplify api's endpoint graphqlOperation().
API.graphql(graphqlOperation(updateUser, { input: newUserDetails }))
where the updateUser mutation is defined as follows...
export const updateUser = /* GraphQL */ `
mutation UpdateUser(
$input: UpdateUserInput!
$condition: ModelUserConditionInput
) {
updateUser(input: $input, condition: $condition) {
id
userName
firstName
lastName
email
phoneNumber
userDetails
userNotes
role
_version
_deleted
_lastChangedAt
createdAt
updatedAt
}
}
and newUserDetails is as follows...
const newUserDetails = {
id: 17347,
firstName: 'Sally',
lastName: 'Smith',
email: 'Sallysmith#gmail.com',
phone: '+19827772222',
}
I am using AWS Amplify/Cognito for authentication.
Why does submitting this form result in a 401 unauthorized error?
If this error is due to a mistake in my Cognito user group permissions, please link documentation explaining how to allow this user group to call this mutation function.

If you have multiple auth modes configured then you need to specify Cognito:
const newArtist = await API.graphql({
query: updateUser,
variables: {input: user},
authMode: 'AMAZON_COGNITO_USER_POOLS'
});

Related

Not authorized to access mutation in GraphQl

I am new to graphql. In my Vuejs application, which uses AWS Amplify, I am trying to save a user to my database after a new user signs up for an account. I took care of the AWS Amplify flow to add this new user to our Cognito pool. I wrote this code to store the user data in our database.
<template>
<div>
A page for testing graphQl queries and mutations
<button #click.prevent="addUser()">Execute query</button>
</div>
</template>
<script>
import { API } from 'aws-amplify'
export const createUser = `
mutation createUser {
createUser(input: {
firstName: "Tina",
email: "Tina#gmail.com"
}) {
id firstName
}
}
`
export default {
name: 'TestGraphQL',
data() {
return {
data: null,
}
},
methods: {
addUser() {
console.log('executing query')
API.graphql({ query: createUser }).then(
(response) => (this.data = response.data.createUser),
).catch(error => console.log('error: ' + error))
},
},
}
</script>
As you can see, for now I have hardcoded some user data in the function just to make sure I can add a user to the database. After this works, I plan to modify the mutation code to allow input variables that contain real user data.
I have run into an issue with authorization. Here is the error message I am seeing:
"Not Authorized to access createUser on type User"
If I log into AWS Appsync > Queries with this user account, I can execute this same mutation and see the same error.
No authorization should be required to add a user to the database. How do I modify my schema or mutation code to not require authorization?
This documentation describes the effect of the #auth decorator. https://docs.amplify.aws/lib/datastore/setup-auth-rules/q/platform/js
But where can I find this file in my repository? Or is this a file that I can edit through the AWS dashboard? I think it is called the 'schema'.

Scopes being ignored by Azure AD with React Native integration

I'm creating an app that login with Azure AD and the same token returned by Azure AD should be used to consume API's.
I've followed all the steps describe at this article with success and it's working.
My problem is about generate the access-token and refresh-token with a custom scope that allows the app to call the api.
I've tried to react-native libraries and each one has a different problem (probably my code has a problem, not the libraries).
First I've tried react-native-azure-auth and when I've defined the following scope:
scope: ['openid', 'profile', 'User.Read', 'offline_access', 'api://multiplan_broker_dev/read']
When defining this scope, it returns a token only valid for the scope api://multiplan_broker_dev/read and doesn't return a refresh_token (seems it's ignoring the offline_access scope)
You can see the whole returned object here and the main code for this implementation here
The main piece of code is below:
const CLIENT_ID = '75a43463-c3b4-4e31-b740-3a5a1858XXXX';
const TENANT_ID = '862085e1-045e-4d8c-832f-96837b0XXXXX';
const azureAuth = new AzureAuth({
clientId: CLIENT_ID,
tenant: TENANT_ID,
});
export default class Auth0Sample extends Component {
constructor(props) {
super(props);
this.state = { accessToken: null, user: '' , mails: [], userId: ''};
}
_onLogin = async () => {
try {
let tokens = await azureAuth.webAuth.authorize({
scope: ['openid', 'profile', 'User.Read', 'offline_access', 'api://multiplan_broker_dev/read'],
login_hint: 'rioa#XXX.com'
})
console.log('CRED>>>', tokens)
this.setState({ accessToken: tokens.accessToken });
let info = await azureAuth.auth.msGraphRequest({token: tokens.accessToken, path: 'me'})
console.log('info', info)
this.setState({ user: info.displayName, userId: tokens.userId })
} catch (error) {
console.log('Error during Azure operation', JSON.stringify(error))
}
};
So I've tried to another library (react-native-app-auth) and it returns the refresh_token, but ignores completely the scopes I'm asking for.
You can see the returned object here and main code here
What I'm doing wrong?
P.S: The solution can be for any library, I don't have preference.
Thanks
The scope is not correct. Your scope contains both Microsoft Graph API endpint and your own api endpoint. As far as I know we can only ask for the token for one resource at one time.
You can change the scope as below:
scope: ['openid', 'offline_access', 'api://multiplan_broker_dev/read']

How do I retrieve updated user attributes from AWS Cognito after calling Auth.updateUserAttributes

I am having trouble getting the up-to-date user information from AWS Cognito either at SignIn or after updating the user attributes.
I am using aws-amplify for authentication, and sign up and login work just fine.
The problem arises on my profile page. I allow people to sign up for notifications as well as change other data in their profile. I have verified that the change is made successfully on the backend as well as in the console by logging the response.
However, when I call Auth.currentAuthenticatedUser(), I get the previously cached information.
How do I retrieve the updated user attributes after receiving a successful response?
async () => {
const { email, given_name, family_name, preferred_username, "custom:notifyPredictRemind": customNotifyPredictRemind, "custom:notifyPredictResult": customNotifyPredictResult } = this.state;
let params = { email, given_name, family_name, preferred_username, "custom:notifyPredictRemind": customNotifyPredictRemind, "custom:notifyPredictResult": customNotifyPredictResult }
let updateResult = await Auth.updateUserAttributes(user, params) // returns SUCCESS
let user = await Auth.currentAuthenticaterUser()
}
I would expect that calling currentAuthenticaterUser at the end to return the updated user information. Any ideas how I can do that? Thanks.
Figured this out. There is a parameter is called bypassCache that you can pass to currentAuthenticatedUser() that will refresh the user's info. Useful for inital login and on updates to the Cognito profile.
async () => {
const { email, given_name, family_name, preferred_username, "custom:notifyPredictRemind": customNotifyPredictRemind, "custom:notifyPredictResult": customNotifyPredictResult } = this.state;
let params = { email, given_name, family_name, preferred_username, "custom:notifyPredictRemind": customNotifyPredictRemind, "custom:notifyPredictResult": customNotifyPredictResult }
let updateResult = await Auth.updateUserAttributes(user, params) // returns SUCCESS
let user = await Auth.currentAuthenticaterUser({bypassCache: true}) // UPDATED, NOW WORKS
}

FeatherJS - Get user information with hook?

So im trying out FeatherJS and i was able to register a new user, request a token and also request protected data (using Authorization in the header).
Very important: I am using HTTP Rest API only. The docs seem to point often times to the client feathers module, which i don't use.
So currently i have a super simple setup, i have a message service with a text. One before hook to process the message. Here i want to get back the user information:
module.exports = function (options = {}) {
return async context => {
const text = context.data.text
const user = context.params.user;
context.data = {
text,
userId: user._id
}
return context;
};
};
this doesn't work. In my mdb i only get back:
{
"_id": "5c35ce18523501803f6a8d8d",
"text": "123",
"createdAt": "2019-01-09T10:34:00.774Z",
"updatedAt": "2019-01-09T10:34:00.774Z",
"__v": 0
}
i've tried to add the token, that i always submit when i post a message via Authorization, like so:
module.exports = function (options = {}) {
return async context => {
const text = context.data.text
const user = context.params.user;
const token = context.params.accessToken
context.data = {
text,
userId: user._id,
tokId: token
}
return context;
};
};
but it seems like i always just get the same result back like shown above.
Any ideas how i can get the user information back of the current user by using the accessToken?
Never used FeathersJS before, so just trying to understand the ecosystem and how to approach this in FeathersJS.
Any advice is appreciated! Thanks in advance everyone!
Not quite sure what exactly went wrong, but i got it now working by just creating a new project.
Now i did recreate this project actually before and got the issue as above , but this time it somehow worked.
For anyone who wants to know the steps i did to 'fix' it:
1.Create a new folder
2. feathers generate app
3. feathers generate authentication
4. feathers generate service (name for the service: messages)
5. feathers generate hook (name: process-msg, before hook, model: messages)
6. Add this to the hook process-msg:
module.exports = function (options = {}) {
return async context => {
const user = context.params.user;
const text = context.data.text;
context.data = {
userId: user.email,
text,
dateTime: new Date().getTime()
}
return context;
};
};
Use postman, register a new account then authenticate to get the token. Save token and add it as Authoriztation Header inside Postman. You should then get also back the user email from the user that is registered, simply because of the token that was added to the Authorization Header.
Greetings!
go to authentication.js and find app.service definition. Right in there, create an after hook and add the details you want the client to receive
app.service('authentication').hooks({
before: {
...//as you currently have it
},
after: {
create: {
hook => {
// hook.result.accessToken is already provided
delete hook.params.user.password
hook.result.user = hook.params.user;
hook.result.token_type = 'Bearer';
hook.result.exp = 3600;
}
}
}
})
I hope this helps
So, if I understand it correctly, you want to get the user object from the hook?
You can just use const user = context.user;to accomplish this.

Auth0 callback URL mismatch

I am doing LinkedIn authentication with auth0 in a react app. I have set localhost:3000/upload in callback urls in settings, hopping that after users login at localhost:3000/login, they would be redirected to localhost:3000/upload. However, I always get this error: url localhost:3000/login is not in the list of callback urls. Why would auth0 expect to return to the page where you just logged in after logging in. Shouldn't it be some different url. It just does not make sense to me.
Edit:
export default class AuthService {
constructor(clientId, domain) {
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
params: {responseType: 'code'}
}
};
this.lock = new Auth0Lock(clientId, domain, options)
// Add callback for lock `authenticated` event
this.lock.on('authenticated', this._doAuthentication.bind(this))
// binds login functions to keep this context
this.login = this.login.bind(this)
this.loggedIn = this.loggedIn.bind(this)
}
_doAuthentication(authResult){
// Saves the user token
console.log(authResult);
this.setToken(authResult.idToken)
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
console.log('Error loading the Profile', error)
} else {
console.log(profile)
}
})
}
//....
Please ensure two things:
1). In your react app code
responseType: 'code'
2). On the Auth0 dashboard, under Settings -> Allowed Callback URLs put your callback entry (localhost:3000/upload) - which I think you have done but just in case.
Let me know if you are still having problems.
Make sure that there is no special hidden characters or space between the commas between the URLs when you paste it into the Auth0 Setting site. I didn't realise about this util I put every urls into Vim to check and see that there are such above cases
In the call to AuthProvider, make sure to use to same callback url as the one in Auth0 settings:
const uri='http://localhost:3000/upload';
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={uri}>
To cause a redirect to a different URL after a successful authentication, you need to provide the redirectUrl to Lock, like this:
// Configure Auth0
const options = {
allowedConnections: ['linkedin'],
auth: {
responseType: 'code',
redirectUrl: 'http://localhost:3000/upload'
}
};
this.lock = new Auth0Lock(clientId, domain, options)
(Also notice that the responseType option goes under auth, not under auth.params.)
If you do the redirect, you won't reach the event handler you defined in your login page. You will need to either add an event handler in your destination page (and use responseType:token) or handle authentication results in your server code (this is what you will normally be doing if you are requesting a responseType: code).
the reason why you should set the callback Url in auth0 settings, because any one can use your client id and send request to google or linkedin, get the response to anywhere they set. but with this setting only you can access that response.
once your app is authorized to pull the data from linkedin, linkedin will send the data to where you specified. you should create a page to handle the response from Linkedin server. Let's name that page callback.js and this will be an example of response object.
accessToken: "hNuPLKTZHiE9_lnED0JIiiPNjlicRDp"
appState: null
expiresIn: 7200
idToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRXdSVUl5TURVeE4wSkJPRFZEUlRKRU1EVkZNemsxTXpNNU5VTXlNRGt6T0VWQlJqUkZRUSJ9.eyJodHRwOi8vbG9jYWxob3N0OjMwMDAvcm9sZSI6InNpdGVPd25lciIsImdpdmVuX25hbWUiOiJvbWFyIiwiZmFtaWx5X25hbWUiOiJpYm8iLCJuaWNrbmFtZSI6Im9tYXJpYm8xOTgyIiwibmFtZSI6Im9tYXIgaWJvIiwicGljdHVyZSI6Imh0dHBzOi8vbGg1Lmdvb2dsZXVzZXJjb250BQUFBQUkvQUFBQUFBQUFBQUEvQUNIaTNyLTEwLTEyVDIyOjU4OjAxLjgzM1oiLCJpc3MiOiJodHRwczovL3BvcnRmb2xpby15aWxtYXouYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTE0MDY0NTA2ODI2OTgwNTA5ODY3IiwiYXVkIjoiUEdVY242RjRRS21PRkJhb1k0UFdCeWpjVzIyT09vNGMiLCJpYXQiOjE1NzA5MjEwODIsImV4cCI6MTU3MDk1NzA4MiwiYXRfaGFzaCI6InN0R1l5SnJaMHNnbVYzSWNLWjlPeFEiLCJub25jZSI6InRrOV95b096enRmVThVVjFVMlVFR3IyMW5ORW5abjk4In0.TYS7mM8N2d7jEHFdWQGTSeAAUaDt4-0SMUG3LrcQ1r3xzY0RMGsUsEszj5xqk1GE0cIlFS10xCOYKsuHSwsFLomC1EbLjntjkledHtfD0MW84cMoXN6a-x-1-bNwl3lMYJ98qklTrNvTvkQJ6DWhei3hJ8rs8dnbNyCfckNVU6ptJU-9ef1DwWfHRomW5LQ6WSDRHZScW697gdgBEMU-Nd2SddyHhQe0kVh6lKdcbnskEAyCJLE07jfM40RQI_8LJouFcpoyImcXSDZlKv90tYfVDq9_TwE3GNaSz5I5snn0457oCgz0vuX0JoCUiaDuTIX7XiyXnozW_DxGMuhk4w"
idTokenPayload: {http://localhost:3000/role: "siteOwner", given_name: "me", family_name: "you", nickname: "nck", name: "nm", …}
refreshToken: null
scope: null
state: "xkEbffzXbdOYPLkXOUkrQeb0Jysbnlfy"
tokenType: "Bearer"
//THIS CODE IS FOR NEXT.JS9
//auth.js
class Auth0 {
constructor() {
this.auth0 = new auth0.WebAuth({
domain: "portfolio-ys.auth0.com",
clientID: "PGUWJQKmOFBaoY4PWByjcW22OOo4c",
redirectUri: "http://localhost:3000/callback",
responseType: "token id_token",
scope: "openid profile"
});
this.handleAuthentication = this.handleAuthentication.bind(this);
}
//there are too many methods are defined here i put only relevant ones
handleAuthentication() {
return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => {
console.log(authResult);
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
resolve();
} else if (err) {
reject(err);
}
});
});
}
setSession function is where you set the cookies based on response object. I use js-cookie package to set the cookie.
setSession(authResult) {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
Cookies.set("user", authResult.idTokenPayload);
Cookies.set("jwt", authResult.idToken);
Cookies.set("expiresAt", expiresAt);
}
}
const auth0Client = new Auth0();
export default auth0Client;
callback.js
import React from "react"
import auth0Client from "./auth0"
import {withRouter} from "next/router"
class Callback extends React.Component{
async componentDidMount(){
await auth0Client.handleAuthentication()
this.props.router.push('/')
}
render() {
return (
<h1>verifying logging data</h1>
)
}
}
export default withRouter(Callback) //this allows us to use router
I had similar issue "callback URL mismatch" and resolved it by running the application over https with a trusted certificate.
Here is a snippet from Auth0 applications settings section about callback URL, which says "Make sure to specify the protocol (https://) otherwisw the callback may fail in some cases."
If you're using the Android(Kotlin) SDK of auth0, I noticed that during runtime, the requested URL is being changed. e.g. app://{your_auth0_domain}/android/{package_name}/callback://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback
Originally URL was
app://{your_auth0_domain}/android/{package_name}/callback
and SDK is appending "://{your_auth0_domain}/android/app://{your_auth0_domain}/android//callback" this extra part.
Solution: Either put the same URL in auth0 setting dashboard as it showing in your logs
or
WebAuthProvider
.login(account)
.withScheme("app") // instead of complete URL, put only the remaining part from the URL,
.start(this, object : Callback<Credentials, AuthenticationException> {}
I hope it will definitely help android/app developer.