-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
throwing AuthenticationError should set the response status to 401 Unauthorized #1709
Comments
I have the same issue, but slightly different. I am using apollo-server-lambda and it returns status code 400 on throwing AuthenticationError. Instead, it should return 401. |
@dandv thanks for opening this! How are you wanting to use the 401, specifically? Why wouldn't the errors extension be enough? From a design perspective, I have a couple thoughts. Since AuthenticationErrors can be thrown at any level, within any resolver, it wouldn't be great to have the whole operation scrapped for a single field's sake. It would remove the ability for partially-valid operations. Also, if we changed the status for AuthorizationErrors, I'd imagine we'd need to change codes for any other kinds of error as well. |
One use case is that some 3rd party GraphQL clients are tripped by the 200 OK and don't look in the response object for an
Agree. This would bring apollo-server in line with standard HTTP error codes returned by REST APIs.
I haven't run into that use case yet, but for the more typical use case where an operation needs to be entirely denied if unauthenticated or unauthorized, I think the dev should have the option to throw a genuine (401/403) auth* error. For partial validations, perhaps pass a flag somehow, or don't throw but return an |
@JakeDawkins, what's the rational behind HTTP status codes in Is it that errors thrown at a point (include execution) where partial responses (data field may be available) are possible should return status code 200? And errors that make impossible to have partial responses (before execution) should generate an HTTP error status code? If that's the rational, i think it's ok and makes sense that By the way, any reason why the response when a query validation fails is in this form:
Shouldn't be:
? |
@JakeDawkins So in our case, we switched to using error extension codes, and I agree that apollo shouldn't generally handle all kinds if response codes. However our legacy app versions, still depend on a 401 being returned. I think one valid improvement would be to at least return 401, where 400 is currently returned, when the context creation fails, since this is where global authentication logic lives, and it already returns 400, so apollo might as well return 401, if it knows that a authentication error occurred from the extension code. apollo-server/packages/apollo-server-core/src/runHttpQuery.ts Lines 126 to 142 in 34322a6
I created PR #2093, to give an idea, how this could work. It's not ready to merge, but I could provide tests etc., if this is something you would consider. |
I don't think this is a great idea. As outlined by @JakeDawkins:
If I request multiple fields and just one leads to an authentication error, it would be better to return the partial results instead of erroring the whole thing. This is in line with the GraphQL spec:
As per RFC 7235
But it has been applied, just partially. So returning a 401 would be non-RFC compliant. |
Hi @danilobuerger ... I get your point. Actually, this was a separate issue #2093. And in this issue, the problem was specific to just throwing an authentication error during context creation. Makes sense? |
Sorry... it wasn't a separate issue... it was just a separate PR... I'll create a separate issue because I think that limiting the scope of the change is the right way to go about it. |
Ok... I've created #2275 to reflect the reduced scope. I'd suggest this one is closed as it seems it shouldn't be accepted. |
Exactly. This just throws a 401 where GraphQL already doesn’t return any data but just a 400 |
I commented in #2275 and quite extensively on #2269 (comment). The gist of those updates was:
For more details, please check the linked PRs and issues above. StrawpersonI'm thinking that we could cater to the needs of the few who would like to apply more heavy-handed "Forbidden"/"Unauthorized" determinations at the HTTP level by:
Roughly, I believe this would allow the following: const server = ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart: () => ({
didEncounterErrors(errors, { response: { http } } ) {
if (http && errors.some(err => err.name === "AuthenticationError")) {
http.status = 401;
}
}
})
}
]
}); Thoughts? |
But the current implementation does throw 400 errors, when context creation fails. If the query fails globally during context creation and throws a 4XX error, it should throw the correct 4XX error. If the query fails locally in the resolver than it needs to return 200 and things need to be handled with error codes. |
Agreed. From and end-developer's perspective, it just seems very counter-intuitive to, for example, validate user input in a resolver and throw an By contrast, query parsing errors correctly return 400 Bad Request. A mechanism to opt into returning a specific error code would be great. |
**Note:** This currently only covers error conditions. Read below for details. This commit aims to provide user-configurable opt-in to mapping GraphQL-specific behavior to HTTP status codes under error conditions, which are not always a 1:1 mapping and often implementation specific. HTTP status codes — traditionally used for a single resource and meant to represent the success or failure of an entire request — are less natural to GraphQL which supports partial successes and failures within the same response. For example, some developers might be leveraging client implementations which rely on HTTP status codes rather than checking the `errors` property in a GraphQL response for an `AuthenticationError`. These client implementations might necessitate a 401 status code. Or as another example, perhaps there's some monitoring infrastructure in place that doesn't understand the idea of partial successes and failures. (Side-note: Apollo Engine aims to consider these partial failures, and we're continuing to improve our error observability. Feedback very much appreciated.) I've provided some more thoughts on this matter in my comment on: #2269 (comment) This implementation falls short of providing the more complete implementation which I aim to provide via a `didEnounterErrors` life-cycle hook on the new request pipeline, but it's a baby-step forward. It was peculiar, for example, that we couldn't already mostly accomplish this through the `willSendResponse` life-cycle hook. Leveraging the `willSendResponse` life-cycle hook has its limitations though. Specifically, it requires that the implementer leverage the already-formatted errors (i.e. those that are destined for the client in the response), rather than the UN-formatted errors which are more ergonomic to server-code (read: internal friendly). Details on the `didEncounterErrors` proposal are roughly discussed in this comment: #1709 (comment) (tl;dr The `didEncounterErrors` hook would receive an `errors` property which is pre-`formatError`.) As I opened this commit message with: It's critical to note that this DOES NOT currently provide the ability to override the HTTP status code for "success" conditions, which will continue to return "200 OK" for the time-being. This requires more digging around in various places to get correct, and I'd like to give it a bit more consideration. Errors seem to be the pressing matter right now. Hopefully the `didEncounterErrors` hook will come together this week.
Does anyone find a solution for this? I'm facing the same problem my server sending 400 error on authentication instead of 401 |
As of the latest 2.6.0 alpha, which was just published on the This can be accomplished using a plugin (defined in import { AuthenticationError } from 'apollo-server-errors';
import { ApolloServer } from 'apollo-server';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
{
requestDidStart() {
return {
didEncounterErrors({ response, errors }) {
if (errors.find(err => err instanceof AuthenticationError)) {
response.http.status = 401;
}
}
}
},
},
],
}); For more information on the plugins, check out the deploy preview on the #2008 PR. |
Just to re-iterate what I've mentioned in the past. Some tooling and existing patterns may necessitate specific HTTP patterns which rally around a single HTTP status code. The
GraphQL gives us the luxury of being free from that constraint and allowing for partial successes and failures mixed within the same response. This is a lovely benefit, but it does require developers to start thinking about the HTTP status codes in terms of transport errors, and not necessarily associating them to user errors or data validation errors. That may seem counter-intuitive to you @dandv, but I'd claim it's not intuitive to fail an entire GraphQL request, which can and should be fetching as many fields as it can in one request, because of user-input (or lack of authentication) which violated one of those field's desires. Perhaps your concern might be partially because of past requirements and habits? I know that some tooling and infrastructure (e.g. reporting) relies on HTTP status codes, but it's certainly worth considering how those might need to be updated over time (in our minds, in our clients and in our tooling) to reflect the multi-status nature of GraphQL since, longer term, the results are of a great benefit to client consumers! |
I believe that #1709 (comment) should provide the facilities to set this programmatically for those that require this functionality and was officially released into the final release of Apollo Server 2.6.0. |
The workaround provided in #1709 (comment) doesn't quite do it for me. It seems to me that my
I thought I could fix that by simply adding a Also, I had to check Here's my plugin attempt: const apolloServer = new ApolloServer({
// [...]
plugins: [
{
requestDidStart() {
return {
didEncounterErrors({ response, errors }) {
if (errors.find(err => err instanceof AuthenticationError || err.originalError instanceof AuthenticationError)) {
response!.data = undefined
response!.http!.status = 401
}
},
}
},
},
],
}) @abernix - any ideas? |
I hacked together a workaround by making const apolloServer = new ApolloServer({
// [..]
formatResponse(body: GraphQLResponse) {
if (body.errors && body.errors.find(err => err.extensions && err.extensions.code === 'UNAUTHENTICATED')) {
return {
...body,
data: undefined,
}
}
return body
},
plugins: [
{
requestDidStart() {
return {
didEncounterErrors({ response, errors }) {
if (errors.find(err => err instanceof AuthenticationError || err.originalError instanceof AuthenticationError)) {
response!.data = undefined
response!.http!.status = 401
}
},
}
},
},
],
}) |
@KATT your workaround isn't working for me - Are you doing something else to force your server/context function to return a response early, rather than return an error at all? |
In my case I was able to set the response status to 401 (and the data to undefined) and even so the service returned 200. I was not able to get a 401 sent to the client |
We are having the same issue. Would love to create 401s but its always 200s we get. |
I did not manage to get the
Maybe this helps someone. |
@MM3y3r - THANK YOU!!!! |
This behavior is incredibly problematic. Not returning 401 and not being able to reasonably patch it is simply broken from an http point of view. Partials I agree and am all for general but that does not make sense when you are rejecting the request wholistically because they aren't authenticated. Ie.. from lookup in context. Apollo Server plugin infrastructure doesn't hook in to that point as it is resolved before |
The trick seems to be what @MM3y3r identified. Apparently you have to set data to undefined first, even though that seems to have been left out of the conversation on the feature when it was added. Can't believe sometimes it requires source-diving through their massive codebase to figure out why the things don't work the way the docs or emoji-rich medium blog posts say they do. |
Alternatively, to handle from client-side: const errorLink = onError(errorResponse => {
if (errorResponse?.graphQLErrors?.some(e => e.extensions?.code === 'UNAUTHENTICATED')) {
// handle the auth error
}
})
client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
// ...
// Can also check graphQLErrors in each individual query/mutation |
There should be an easy way to chose how to catch these errors and chose how we want to deal with them in the network, we already have a way to format the errors how we like, why don't we have access to the http there to return whatever status code we feel like? |
@Jorgechen the client solution is not viable in combination with the retry link. This is due to the fact that retryLink works at Network Error level and onError only makes the network & gql errors present. However, if you want to trigger the retryLink to let's say refreshToken, then you'd have to raise an exception. |
For anyone considering @MM3y3r's solution, keep in mind it doesn't work if you're trying to sever the request before it processes. For example, it will still run through all your resolvers |
Is there a doc reference to this ?, If I throw any error while context creation I am always getting HTTP 400, and also not able to modify it using the Plugin mechanism specified in the docs. |
This is not configurable in Apollo Server at the moment, but I just implemented a supported and documented way to control this for Apollo Server 4: #6857 |
apollo-server correctly returns a a "400 (Bad request)" error if the query fails to validate, but if the code throws an AuthenticationError per the docs, the status remains 200.
https://launchpad.graphql.com/z5n9190vz7
The text was updated successfully, but these errors were encountered: