Verify graphql query - graphql

I'm building a simple platform using graphql as api gateway and a frontend that send some queries to this api, I'm blocked on how can I validate a query before run it to avoid malicious query to be ran. I was thinking to use persistgraphql but I just noticed that is now archived so I'm not sure if it's a good idea to use it, the second problem is that the api and the frontend are in 2 different repo so I didn't find yet a solution to whitelisting the query in the frontend and use this whitelist in the api...what's the best solution to whitelist a query with graphql?

If your concern is limiting access to certain fields based on who is making the request, then you should implement some kind of authorization strategy. You can populate the context with information about the logged in user and then use this information inside your resolvers for the fields you want to protect to determine whether the value of the field should be returned or not.
const resolvers = {
User: {
somePrivateField: (user, args, ctx) => {
// Make sure the request is from a logged in user and the user making the
// request is the same as the requested user OR the user is an admin
if (ctx.user && ( ctx.user.id === user.id || ctx.user.isAdmin )) {
return user.somePrivateField
}
// throw an error or just return null or undefined to resolve the field to
// null in the event authorization fails
}
}
}
More sophisticated strategies are possible using directives or existing libraries like graphql-shield.
Of course, certain fields that may exist on your database model -- like passwords -- should probably never be exposed in your API in the first place.

Related

Authorize based on field value in another document in AppSync GraphQL custom authentication via Lambda Resolver

I am new to Amplify Datastore & AppSync w/ GraphQL, but in Firestore, you can write an auth rule like: allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true [https://firebase.google.com/docs/firestore/security/rules-conditions#access_other_documents] -> which would grab the document at /users/{id}/ and I can access the admin field to write a security rule logic.
How do you do the same in a Lambda function resolver?
I am aware that the solution may involve a Lambda resolver (https://stackoverflow.com/a/68581796/9824103) but I cannot find any reference to reading a specific document and doing logic to authorize or deny an operation based on a field value in a document. I am only asking how to do this specific thing. Thank you!
I followed https://docs.amplify.aws/cli/graphql/authorization-rules/#custom-authorization-rule to create a custom authorization rule via adding the #rule directive: type MyModel #model #auth(rules: [{ allow: custom }]) and.. although the lambda function isn't really getting called when I try to write a listMyModel or createMyModel (any hints as to why that would be great), I am focused on writing the lambda function to read query a document and check a certain field to meet my custom auth condition.
fyi, I am using Flutter based amplify-cli.

How to run a query with Apollo GraphQL?

I am trying to figure out how to write a query in Apollo GraphQL.
I have a schema and have run the application in development mode. I have authenticated through the front end.
I expect that I should be able to follow this documentation and query the user.
I can see from the studio, that the Me query should be capable of checking for my first name (which I can see is recorded in the database), but when I press run in Apollo Studio, I get a null response to the query.
Is there an assumed step to get this working that needs to be taken before queries can be run? It gets worse when I try to do a query on the users table generally. That returns a not authenticated error (I have authenticated in the local environment in the dev app).
I'm struggling to connect the dots between the documentation that shows how this is expected to run queries and the starting point. I suspect that these documents have been prepared with the expectation that users know something fundamental about how to engage with them. I'm looking for disclosure as to what those assumptions might be. I can see from this question that there is a need for an authorisation header, (although my error is to do with authentication rather than authorisation). However, in my studio, the headers tab is empty. How do I populate it and what do I use to populate it?
I can see from the Apollo dev tool that it is trying to use a logged in query. I don't understand what drives this query in the Apollo Studio. Inside the localhost web app (which is running), I am logged in. When I try and run that query in the dev tools, the isLoggedIn (name of the query) is underlined, with an error explanation appearing that says:
Cannot query field "isLoggedIn" on type "Query".
The response shows:
{
"data": {}
}
I am lost for a starting point to find something to try and solve.
I think, based on a comment in this Odyssey tutorial, that the sandbox does not know how to connect to my psql data (not sure about this, but how could it know what queries I have, and not know which data has been stored in the attributes on the schema?). My env variables include my psql attributes and my prisma migrate is up to date. How can I let the sandbox know where the data is stored?
I am trying to learn using this boilerplate repo.
For my next attempt, I tried using the login mutation to generate a token, that I could try adding to the header. I don't know if it needs to be added under the name 'authorization' or 'token', so I made headers with both attribute names and added the same token to each of them.
I tried running the me and user query again, and get a mouthful of gibberish in the response.
The link in the response text goes to a page that has the following error message:
> <Error> <Code>NoSuchKey</Code> <Message>The specified key does not
> exist.</Message> </Error>
When I try going through the process of adding an APOLLO_KEY to my env variables and starting the server, I get an error that says "Unable to reach server". When I run the diagnose script on that error, I get:
Could not find any problems with the endpoint. Would you please to let
us know about this at explorer-feedback#apollographql.com 🙏
I created a new api key and tried again and am able to connect. I am able to run a login mutation and can return my first name inside that mutation, but I cannot do it from the me or user query - those queries still return the unauthenticated error response.
I have tried adding the authorization token to the header field both with and without "", and I have tried labelling that attribute as each of authorization, Authorization, token and then each of those inside "". None of them seems to make any difference to whether I can run a query. How can I find the name of the header token that Apollo Studio Explorer will accept?
I also tried the syntax suggested in this post, which is key Authorization and value "Bearer token" (there are double quotation marks around that string and a space between the word Bearer (capitalised) and the token string). There are no curly braces. That doesn't work either.
I have also tried expressing it as shown in this page of the Apollo documentation, which I think means that the key of the header value should be Authorization and the value should be the word Bearer, immediately followed by the token string generated in the output of the Login migration, inside {{ }}. When I try this, I get the same response as each of the other attempts described above.
There is a difference in the responses though, I get an unauthenticated response on the user query, and a null response on the me query.
One final strange observation: the studio returns the above error and null responses, but if I use the apollo client dev tools in the browser console, I can run the same Me query and get the result.
The user query still returns an unauthenticated error when I run it in the dev tools.
I'd also note that I can ask for the firstName attribute, inside the Login mutation, and receive them back in that response. However, I can't access them inside a Me query itself.
The next thing I investigated was how the resolver was managing the data. The boilerplate includes a resolver with:
import { AuthenticationError } from "apollo-server-express"
import { createMethodDecorator } from "type-graphql"
import { ResolverContext } from "../resolverContext"
export function UseAuth(roles?: string[]): any {
return createMethodDecorator<ResolverContext>(async ({ context: { req } }, next) => {
const argRoles = roles || []
if (req?.currentUser) {
if (argRoles.length === 0) return next()
if (argRoles.includes(req.currentUser.role)) return next()
throw new AuthenticationError("Not authorized")
} else {
throw new AuthenticationError("Not authenticated")
}
})
}
I wondered if maybe the role wasn't being considered. But I can see that it is inside the login mutation, but is not in a query.
Is there a 'for dummies' guide to getting started with apollo graphql?
I hope this spares someone some angst.
The format that works in Apollo Studio Explorer is
Key: Authorization
Value: Bearer[space][token]
There are no curly braces and no quotation marks in any of this. See this post for more discussion about this.

Microsoft Graph API - 'AuthenticationError ' (HTTP 400) on calls for GET /users/<user_id>/mailFolders/inbox/messageRules

We're running into an error with a specific Graph API endpoint, so any help or advice here is appreciated!
Problem:
We're seeing unexpected HTTP 400 errors on calls to fetch specific users' inbox rules, with code AuthenticationError.
Request:
https://graph.microsoft.com/v1.0/users/<USER_ID>/mailFolders/inbox/messageRules
Response:
{
"error": {
"code":"AuthenticationError",
"message":"Error authenticating with resource",
"innerError": {
"request-id":"44bbcef3-b8cd-4da0-978e-99955e55f3a7",
"date":"2020-05-19T14:55:52"
}
}
}
Note that we're only calling this method for non-external users where userType is member and onPremisesSyncEnabled is null. We're also supplying each user's ID as a parameter, rather than their user principal name.
Also - this example was pulled from our application, which is able to make calls to other Graph API endpoints without issue, including calls to fetch Users, Groups, Domains/Organizations, etc. This method does work for most of our clients but is failing in a few particular cases.
Here is a list of application scopes granted for the client:
AccessReview.Read.All
AuditLog.Read.All
Contacts.Read
Directory.Read.All
EduAdministration.Read.All
Group.Read.All
IdentityRiskEvent.Read.All
IdentityRiskyUser.Read.All
MailboxSettings.Read
Member.Read.Hidden
ProgramControl.Read.All
Reports.Read.All
SecurityEvents.Read.All
Sites.Read.All
User.Read.All
If there's any additional information we can provide to make this easier to troubleshoot, just let me know.
Thanks!

How to always return Graphql field?

I would like to always return a field when certain requests are made so that additional actions can be performed by the client.
For example if user registration has been successful I want to show flash message. But it has to be handled by the backend and not the front end.
I could "require" clients to always request specific field that would hold the needed information but it could be easily overlooked so I'd like to return data whenever needed.
Is this possible?
There is nothing in the GraphQL spec that allows particular fields to be required in the same sense that arguments can be required. This can be handled inside the resolver, however. For example, if you have a query called getFoos, and you want to require the clients to always request the field foo when fetching that query, your resolver for getFoos could look like this:
function (obj, args, ctx, info) {
const fields = info.fieldNodes[0].selectionSet.selections.filter(s => s.name.value)
if (!fields.includes('foo')) return Promise.reject(new Error('Missing field foo!'))
// continue resolving the query as normal
}
Each resolver takes as its fourth parameter an info object that contains detailed information about the request, including what fields were requested. So inside your resolver, just verify that the field was included, and if it's missing, return a rejected Promise instead. This will cause your query to fail anytime that particular field is missing.

Simple Authorization in MVC3 with Forms Authentication

I'm trying to do what should be a simple thing in MVC3.
I've got an application that uses forms authentication to authenticate users with a 3rd party SSO. The SSO, on successful login, posts back to a specific controller action on my application. I then call FormsAuthentication.SetAuthCookie(user,false);.
I'm trying to implement some level of authorization. Simply, a user can exist in a number of different roles, e.g. Admin and Developer. Some controller actions should only be available to certain roles. Details of which roles a user belongs to is obtained by making a call to another external API, which returns a simple JSON response indicating.
In theory, this should be as simple as doing something like this after I set the FormsAuthentication cookie:
string[] rolelist = GetRoleListForUserFromAPI(User.Identity.Name);
HttpContext.User = new GenericPrincipal(User.Identity, rolelist);
However, I can't call this directly after calling SetAuthCookie, because HttpContext.User isn't anything meaningful at this point.
I could try setting this on every request, but ever request to my app would mean a roundtrip API call.
The most promising approach I've seen so far is to create a custom Authorization attribute and override OnAuthorization to do something like this:
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (<some way of checking if roles have already been set for this user, or role cache has timed out>)
{
string[] rolelist = GetRoleListForUserFromAPI(filterContext.HttpContext.User.Identity.Name);
filterContext.HttpContext.User = new GenericPrincipal(filterContext.HttpContext.User.Identity,rolelist);
}
}
I could then use [MyCustomAuthorization(Roles="Admin")] in front of controller actions to make the magic happen.
However, I've no idea how to detect whether or not the current HttpContext.User object has had its roles set, or whether it was set over a certain time ago and another API trip is needed.
What's the best approach for this?
Another way would be to store the roles in the UserData property of the FormsAuthentcationTicket. This could be done with comma delimited string.
http://msdn.microsoft.com/en-us/library/system.web.security.formsauthenticationticket.formsauthenticationticket
Then on AuthenticateRequest method, you could pull the ticket back, grab the roles data and assign it to the current user using a generic principal.
You should override PostAuthenticateRequest
protected void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
string[] rolelist = GetRoleListForUserFromAPI(User.Identity.Name);
HttpContext.User = new GenericPrincipal(User.Identity, rolelist);
}
}
It's invoked after forms authentication is finished with it's processing.
http://msdn.microsoft.com/en-us/library/ff647070.aspx
Update
I had the wrong method signature (just checked in one of my own applications).
My first thought is that you should investigate implementing a custom role provider. This might be overkill but seems to fit in with the role-based plumbing.
More info from MSDN here.
Much to the aghast of some, the session object ISNT a bad idea here.
If you use temp data, you already take a hit for the session.
Storing this data in the cookie, well - Forms auth tokens have already been exploited in the POET vulnerability from a year and a half ago, so in that case someone could've simply formed their own cookie with the "admin" string in it using that vulnerability.
You can do this in post authenticate as #jgauffin mentioned.
If the session state isn't available there you can use it then in Application_PreRequestHandlerExecute and check it there.
If you want to check if session state is available in either see my code at:
How can I handle forms authentication timeout exceptions in ASP.NET?
Also whenever using forms auth and sessions, you always want to make sure the timeouts are in sync with each other (again the above code)

Resources