How to query a Strapi backend using GraphQL as an authenticated user? - strapi

Currently, I'm able to just run a query as a public user and Strapi fetches me the results. However, I want to completely block all query access to public users and only allow it for authenticated users (preferably just one specific user).
I know I can block query access in the Roles & Permissions plugin and I also know that one could just create a new user with its own password in the Content Types -> Users screen. In fact, I already have, it's called web. Now, how do I execute queries in my /graphql/ endpoint as this particular user?

The GraphQL endpoint is not managed via a route but via a middleware.
So the policy system is not applied.
You will not be able to remove access to this endpoint.
but you can disable the GraphQL Playground GET /graphql by updating the GraphQL config files. Here is the documentation to do that https://strapi.io/documentation/3.0.0-beta.x/guides/graphql.html#configurations
If you want to restrict access to the GraphQL endpoint I suggest you to create a new middleware that will check if the triggered endpoint is /graphql and check if the authenticated user is the one you want.
Here is the documentation to create a middleware https://strapi.io/documentation/3.0.0-beta.x/advanced/middlewares.html
Your middleware will look to something like that
module.exports = strapi => {
return {
initialize() {
strapi.app.use(async (ctx, next) => {
const handleErrors = (ctx, err = undefined, type) => {
if (ctx.request.graphql === null) {
return (ctx.request.graphql = strapi.errors[type](err));
}
return ctx[type](err);
};
// check if it's a graphql request
if (ctx.request.url === '/graphql' && ctx.request.method === 'POST') {
if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
try {
// get token data
const { id } = await strapi.plugins[
'users-permissions'
].services.jwt.getToken(ctx);
if (id === undefined) {
throw new Error('Invalid token: Token did not contain required fields');
}
// check if the id match to the user you want
if (id !== 'my-user-id') {
return handleErrors(ctx, 'You are not authorized to access to the GraphQL API', 'unauthorized');
}
} catch (err) {
return handleErrors(ctx, err, 'unauthorized');
}
} else {
// if no authenticated, return an error
return handleErrors(ctx, 'You need to be authenticated to request GraphQL API', 'unauthorized');
}
}
await next();
});
}
};
};
This code will restrict to my-user-id the access to your GraphQL API.
To be authenticated you will have to send the JWT in the header. Please follow the documentation here to learn about it https://strapi.io/documentation/3.0.0-beta.x/guides/authentication.html

Related

How to use TeamsFx useGraph hook to get meeting infos

I would like to use TeamsFx React package to call MS Graph Api.
I tried to do separated component
import { useContext } from "react";
import { useGraph } from "#microsoft/teamsfx-react";
import { TeamsFxContext } from "../Context";
import { TeamsFxProvider } from "#microsoft/mgt-teamsfx-provider";
import { Providers, ProviderState } from "#microsoft/mgt-element";
import * as microsoftTeams from "#microsoft/teams-js";
export function MeetingContext(props: { showFunction?: boolean; environment?: string }) {
const { teamsfx } = useContext(TeamsFxContext);
const { loading, error, data, reload } = useGraph(
async (graph, teamsfx, scope) => {
// Call graph api directly to get user profile information
let profile;
try {
profile = await graph.api("/me").get();
} catch (error) {
console.log(error);
}
// Initialize Graph Toolkit TeamsFx provider
const provider = new TeamsFxProvider(teamsfx, scope);
Providers.globalProvider = provider;
Providers.globalProvider.setState(ProviderState.SignedIn);
let photoUrl = "";
let meeting =null;
try {
const photo = await graph.api("/me/photo/$value").get();
photoUrl = URL.createObjectURL(photo);
microsoftTeams.getContext(async (context)=>{
console.log(context);
try {
meeting = await graph.api(`/me/onlineMeetings/${context.meetingId}`).get();
} catch (error) {
console.error(error);
}
})
} catch {
// Could not fetch photo from user's profile, return empty string as placeholder.
}
console.log(meeting);
return { meeting };
},
{ scope: ["User.Read","User.Read","OnlineMeetingArtifact.Read.All"," OnlineMeetings.Read"], teamsfx: teamsfx }
);
return (
<>
</>
)
}
When I debug my interpreter stops on
profile = await graph.api("/me").get();
Then it does not pass after.
I would like also to know what should I put in scope field ?
Should I put the authorisations listed here ?
Should I also Add them in registered app in Azure Portal ?
Update:
I'm getting response from
Failed to get access token cache silently, please login first: you need login first before get access token.
I'm using the teams toolkit and I'm already logged in . I don't know what Should I do to be considered as logged in ?
Update :
I have updated my app api authorisations in azure portal now I'm not getting anymore this error.
But I'm getting a new error :
meeting = await graph.api(`/me/onlineMeetings/${context.chatId}`).get();
"Invalid meeting id
19:meeting_MzU0MzFhYTQtNjlmOS00NGI4LTg1MTYtMGI3ZTkwOWYwMzk4#thread.v2."
I'll post a new question about this as it not the original problem
You can get the details of an online meeting using videoTeleconferenceId, meeting ID, or joinWebURL.
Instead of ChatID you have to use meeting ID or you have to use filter with videoTeleconferenceId or joinWebURL.
The invalid meeting ID error get because
chatID is not the correct parameter for this graph API.
You can refer below API for getting meeting information.
To get an onlineMeeting using meeting ID with delegated (/me) and app (/users/{userId}) permission:
GET /me/onlineMeetings/{meetingId}
GET /users/{userId}/onlineMeetings/{meetingId}
To get an onlineMeeting using videoTeleconferenceId with app permission*:
GET /communications/onlineMeetings/?$filter=VideoTeleconferenceId%20eq%20'{videoTeleconferenceId}'
To get an onlineMeeting using joinWebUrl with delegated and app permission:
GET /me/onlineMeetings?$filter=JoinWebUrl%20eq%20'{joinWebUrl}'
GET /users/{userId}/onlineMeetings?$filter=JoinWebUrl%20eq%20'{joinWebUrl}'
Ref Doc: https://learn.microsoft.com/en-us/graph/api/onlinemeeting-get?view=graph-rest-1.0&tabs=http

IAM Role vs IAM User Can't Call Cognito ListUsers

I'm working on a serverless project, when I invoke local and use the credentials in my ~/.aws/credentials which correspond to a user with an Administrator policy the code executes correctly without any security issues. When I run the lambda with the assumed role, it gives the following error:
UnrecognizedClientException: The security token included in the request is invalid.
If I hardcode the credentials of my admin user and run it in lambda, it works fine. So obviously there is some issue with my IAM role that the lambda assumes when making a call to Cognito to ListUsers. I have given that IAM role an administrator policy, still gives the same exception, what is going on with the role vs user and why can't the role call cognito ListUsers?
Is there a trust relationship needed? Is there anything additional that a role would need versus a user that has the same access policy? This is driving me crazy
var params = {
UserPoolId: process.env.userPoolId,
AttributesToGet: [
'email',
'sub'
],
Filter : 'email ^= \"' + email + '\"'
};
return new Promise((resolve, reject) => {
AWS.config.update({
region : process.env.AWS_REGION,
accessKeyId : process.env.AWS_ACCESS_KEY_ID,
secretAccessKey : process.env.AWS_SECRET_ACCESS_KEY
});
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
cognitoidentityserviceprovider.listUsers(params, (err, data) => {
if (err) {
console.error(err);
reject(err);
} else {
var users = [];
for (const cognitoUser of data.Users) {
var user = {};
for (const attribute of cognitoUser.Attributes) {
switch(attribute.Name) {
case 'sub':
user.id = attribute.Value;
break;
case 'email':
user.email = attribute.Value;
break;
default:
}
}
users.push(user);
}
resolve(users);
}
});
});
Alright, so when you use STS Assume Role, SessionToken is not optional and is needed to be included. This is the answer.

Keystone.JS API User Authentication (not Admin-UI)

I would like to restrict my GraphQL API with User Authentication and Authorization.
All Keystone.JS documentation is talking about AdminUI authentication, which I'm not interested in at the moment.
Facts:
I want to have some social logins (no basic email/password)
I want to use JWT Bearer Tokens
Other than that you can suggest any possible way to achieve this.
My thoughts were:
I could have Firebase Authentication (which can use Google Sign-in, Apple Sign-in etc.) be done on the client-side (frontend) which would then upon successful authentication somehow connect this to my API and register user (?).
Firebase client SDK would also fetch tokens which I could validate on the server-side (?)
What is troubling is that I can't figure out how to do this in a GraphQL environment, and much less in a Keystone-wrapped GraphQL environment.
How does anyone do basic social authentication for their API made in Keystone?
Keystone authentication is independent of the Admin-UI. If you are not restricting your list with proper access control the authentication is useless. Default access is that it is open to all.
you can set default authentication at keystone level which is merged with the access control at list level.
Admin Ui Authentication
Admin UI only supports password authentication, meaning you can not go to /admin/signin page and authenticate there using other authentication mechanism. The Admin Ui is using cookie authentication. cookies are also set when you login using any other login method outside of admin-ui. This means that you can use any means of authentication outside of admin-ui and come back to admin ui and you will find yourself signed in.
Social Authentication:
Social authentication is done using passportjs and auth-passport package. there is documentation to make this work. Single Step Account Creation example is when you create user from social auth automatically without needing extra information (default is name and email). Multi Step Account Creation is when you want to capture more information like preferred username, have them accept the EULA or prompt for birthdate or gender etc.
JWT
I dont believe Keystone does pure JWT, all they do is set keystone object id in the cookie or the token is a signed version of item id (user item id) which can be decrypted only by the internal session manager using cookie secret.
Using Firebase to authenticate user
this is the flow of authentication after you create a custom mutation in keystone graphql.
client -> authenticate with Firebase -> get token -> send token to server -> server verifies the token with firebase using admin sdk -> authenticate existing user by finding the firebase id -> or create (single step) a user or reject auth call (multi step) and let client send more data like age, gender etc. and then create the user -> send token
here is the example of phone auth I did, you can also use passport based firebase package and implement your own solution.
keystone.extendGraphQLSchema({
mutations: [
{
schema: 'authenticateWithFirebase(token: String!): authenticateUserOutput',
resolver: async (obj, { token: fireToken }, context) => {
const now = Date.now();
const firebaseToken = await firebase.auth().verifyIdToken(fireToken);
const { uid, phone_number: phone } = firebaseToken;
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserFromId($phone: String!, $uid: String!) {
firebaseUser: allUsers(where: { phone: $phone, firebaseId:$uid }) {
id
name
phone
firebaseId
}
}`,
variables: { phone, uid },
});
if (errors || !data.firebaseUser || !data.firebaseUser.length) {
console.error(errors, `Unable to find user-authenticate`);
throw errors || new Error('unknown_user');
}
const item = data.firebaseUser[0];
const token = await context.startAuthedSession({ item, list: { key: 'User' } });
return { item, token };
},
},
{
schema: 'signupWithFirebase(token: String!, name: String!, email: String): authenticateUserOutput',
resolver: async (obj, { token: fireToken, name, email }, context) => {
const firebaseToken = await firebase.auth().verifyIdToken(fireToken);
const { uid, phone_number: phone } = firebaseToken;
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserFromId($phone: String!, $uid: String!) {
firebaseUser: allUsers(where: { phone: $phone, firebaseId:$uid }) {
id
name
phone
firebaseId
}
}`,
variables: { phone, uid },
});
if (errors) {
throw errors;
}
if (data.firebaseUser && data.firebaseUser.length) {
throw new Error('User already exist');
}
const { errors: signupErrors, data: signupData } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
mutation createUser($data: UserCreateInput){
user: createUser(data: $data) {
id
name
firebaseId
email
phone
}
}`,
variables: { data: { name, phone: phone, firebaseId: uid, email, wallet: { create: { walletId: generateWalletId() } }, cart: { create: { lineItems: { disconnectAll: true } } } } },
});
if (signupErrors || !signupData.user) {
throw signupErrors ? signupErrors.message : 'error creating user';
}
const item = signupData.user;
const token = await context.startAuthedSession({ item, list: { key: 'User' } });
return { item, token };
},
},
],
})

How do I do mutations? Application is being blocked because of JWT tokens is protecting the route

I'm to new GraphQL and I'm wondering how I can stay logged in when I want to do mutations as a logged in user in the GraphQL playground. How would I stayed logged in?
const getLoggedInUser = async req => {
const token = req.headers["x-token"];
if (token) {
try {
return await jwt.verify(token, process.env.JWT_SECRET);
} catch (e) {
throw new AuthenticationError(AUTHORISATION_MESSAGES.SESSION_EXPIRED);
}
}
return token;
};
Base on your code, you should pass the token in HTTP header like this
{
“x-token”: “<put ur token here>”
}

loopback modify access token

I want to add more details to the access token which is generated on calling the user login method. I have extended the builtin user model with my own Customer model. My login method looks like below:
getToken(userData, data, callback) {
const Customer = app.models.Customer;
Customer.login(
{ email: userData.email, password: userData.password },
function (error, accessToken) {
if (error) {
callback(error);
} else {
callback(null, accessToken);
}
}
)
}
I want to add data to the accessToken on login. How do I achieve that ? Basically I want to add more information to accessToken other than userId. I also want to retain userId in the token.
Thanks

Resources