I'd like to notify my client that there was a login failure by returning the error code 401.1 from my Flask based REST API.
Flask is fine with something like this:
return {"message": "Invalid username or password"}, 401
How do I return a HTTP 401.1 instead of a 401?
You can't out of the box. The Werkzeug response object used in the Flask stack will always treat the status code as an integer.
That's because the HTTP RFCs are very clear on what values are permitted for a status code:
The status-code element is a 3-digit integer code describing the result of the server's attempt to understand and satisfy the client's corresponding request.
Please tripple-check your requirements with your client before you go break the RFC.
The addition of a .x sub-status number is a Microsoft IIS extension that is used only internally to the server. The server sends a 401 status, but logs a detail substatus code in the logs and lets you vary the custom error message based on the sub-code. So the end user sees 401.1 Logon failed in the error page served, but the status line is still set to HTTP/1.1 401 Unauthorized. See this post by a Microsoft engineer:
Please note that there is no such thing as a 401.1 when it comes to the HTTP headers themselves. (Watch using a network sniffer to confirm for yourself).
When you see a 401.1 in IE, what's really happening is that IIS is sending down a HTTP/401 error code in that HTTP Result, but the body text provides the additional information you're seeing that claims it's really a 401.1
If you absolutely must send such a status (and break RFC compliance), you'd have to subclass the Response class used by Flask:
from flask import Response
from werkzeug.http import HTTP_STATUS_CODES
class ResponseWithSubstatus(Response):
@property
def status_code(self):
"""The HTTP Status code as number"""
return self._status_code
@status_code.setter
def status_code(self, code):
self._status_code = code
try:
main_code = code
if isinstance(code, str) and '.' in code:
main_code = int(code.partition('.')[0])
self._status = '%s %s' % (code,
HTTP_STATUS_CODES[main_code].upper())
except (ValueError, KeyError):
self._status = '%s UNKNOWN' % code
@property
def status(self):
"""The HTTP Status code"""
return self._status
@status.setter
def status(self, value):
self._status = value
self._status_code = self._status.split(None, 1)[0]
try:
self._status_code = int(self.status_code)
except ValueError:
pass
then use that class as the response_class attribute on your Flask() object:
from flask import Flask
app = Flask(...)
app.response_object = ResponseWithSubstatus
The above changes remove the requirement that status_code is an integer.
Note that it may still be possible for your WSGI server to choke on this! Different WSGI servers may handle a non-conformant status line better or worse.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With