How to send Authorization Header through Swagger UI using FastAPI? - python

In the frontend, I have the following JS function:
export const uploadFormData = async (
token: string,
email: string,
formInfo: Array<Object>,
): Promise<any> => {
const formData = new FormData();
formData.append('email', email);
formData.append('form_info', JSON.stringify({ formInfo }));
return fetch(
`${process.env.ENDPOINT}/upload_form_data/`,
{
method: 'POST',
headers: {
Authorization: `Token ${token}`,
},
body: formData,
},
).then((response) => {
console.log(response.body?.getReader());
if (response.status === 404) {
throw Error('Url not found');
}
if (response.status === 422) {
throw Error('Wrong request format');
}
if (response.status !== 200) {
throw Error('Something went wrong with uploading the form data.');
}
const data = response.json();
return {
succes: true,
data,
};
}).catch((error) => Promise.reject(error));
};
which sends a POST request to the following endpoint in the FastAPI backend:
#app.post("/api/queue/upload_form_data/")
async def upload_form_data(
email: str = Body(...),
form_info: str = Body(...),
authorization: str = Header(...),
):
return 'form data processing'
However, it keeps throwing the following errors:
In the frontend:
POST http://localhost:8000/api/queue/upload_form_data/ 422 (Unprocessable Entity)
Uncaught (in promise) Error: Wrong request format
In the backend:
POST /api/queue/upload_form_data/ HTTP/1.1" 400 Bad Request
In Swagger UI (response body):
{
"detail": [
{
"loc": [
"header",
"authorization"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
What is wrong with the request that is causing these errors?

In Swagger/OpenAPI specification, Authorization is a reserved header, along with Accept and Content-Type headers as well for Swagger's built-in authentication/authorization functionality—see Swagger documentation; hence, they are not allowed to be defined. If you are using Swagger, you can't have Authorization defined along with your endpoint's parameters, as it will be ignored when submitting the request through Swagger UI, and you'll get a 422 Unprocessable Entity error with a body message saying that the authorization header is miising (just like the error posted in your question).
Solutions
If you don't need Swagger UI for testing your application, you can leave it as is and keep using JavaScript Fetch API, passing the Authorization in the headers. Also, note that you don't really have to define any Header parameters in your endpoint, as you can always access them through the Request object, for instance:
from fastapi import Request
#app.post('/')
def main(request: Request):
token = request.headers.get('authorization')
return token
If you do need this to work with Swagger UI as well, one solution would be to rename the authorization Header parameter to something else, e.g., token: str = Header(...). Then, inside your endpoint check if the API key is in either the token or request.headers.get('authorization')—if both result to None, then it means no Authorization header was provided. Otherwise, use FastAPI's HTTPBearer, which would allow you to click on the Authorize button on the top right hand corner of your screen in Swagger UI autodocs at /docs, where you can type your API key in the Value field. This will set the Authorization header in the request headers. Example:
from fastapi.security import HTTPBearer
security = HTTPBearer()
#app.get('/')
def main(authorization=Depends(security)):
return authorization.credentials
Alternatively, you could use APIKeyHeader
from fastapi.security.api_key import APIKeyHeader
from fastapi import Security
api_key = APIKeyHeader(name='Authorization')
#app.get('/')
def main(token = Security(api_key)):
return token

Related

Getting 401 error : "invalid_client" in django-oauth-toolit

I am trying to have an Ouath2 access token based authentication. I am using django-oauth-toolkit for that. I have registered my app on http://localhost:8000/o/applications.
However, When I tried to hit the URL http://localhost:8000/o/token/ from the react-app . I got 401.
Here is my the useEffect hook that I used for calling that URL :
useEffect(() => {
// axios.get("api/tests")
// .then((res)=>{
// setUsers(JSON.stringify(res.data))
// })
fetch("http://localhost:8000/o/token/", {
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: client_id,
client_secret: client_secret
}),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": `Basic ${btoa(client_id+":"+client_secret)}`}
,
method: "POST"
}).then((res)=>{
console.log(res)
})
}, [])
Will look forward to some comments and solutions.
I got the solution. Actually I was Using the encrypted version of the client secret. I forgot to copy it before saving. Once it is saved the value is encrypted. So it's better to copy it somewhere prior saving it.

flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header

Server-side flask
#project_ns.route('/projects')
class ProjectsResource(Resource):
#project_ns.marshal_list_with(project_model)
#jwt_required()
def get(self):
"""Get all projects """
user_id = User.query.filter_by(username=get_jwt_identity()).first() # Filter DB by token (username)
projects=Project.query.filter_by(user_id=user_id)
#projects = Project.query.all()
return projects
client-side reactjs
const getAllProjects=()=>{
const token = localStorage.getItem('REACT_TOKEN_AUTH_KEY');
console.log(token)
const requestOptions = {
method: 'GET',
headers: {
'content-type': 'application/json',
'Authorization': `Bearer ${JSON.parse(token)}`
},
body: "Get projects listed"
}
fetch('/project/projects', requestOptions)
.then(res => res.json())
.then(data => {
setProjects(data)
})
.catch(err => console.log(err))
}
I specified header on the client side and the error still occurs:
flask_jwt_extended.exceptions.NoAuthorizationError: Missing Authorization Header
my flask versions are as follows:
Flask==2.0.1
Flask-Cors==3.0.10
Flask-JWT-Extended==4.2.3
Flask-Migrate==3.1.0
Flask-RESTful==0.3.9
flask-restx==0.5.0
Flask-SQLAlchemy==2.5.1
I have tried many options and the issue is still persisting. Would love to come to a resolution. Thanks in advance!
I haven't used these libraries before, but it sounds like it might possibly be an issue with the Flask-RESTful library?
https://stackoverflow.com/a/52102884/2077884
https://github.com/vimalloc/flask-jwt-extended/issues/86#issuecomment-335509456
https://github.com/vimalloc/flask-jwt-extended/issues/86#issuecomment-444983119
If you're not getting a value for console.log(token), I would start there. And maybe check to see if you can decode the token details via https://jwt.io/ to ensure that the value you have for token is valid.

Is there a way to call a Cloud Function through Cloud Task using form-data?

I have a Cloud Function(Python - Flask) that is expecting to receive a form-data with two files, it works well when I request it directly, but using Cloud Task it's not reaching in there. Here is what I get: ImmutableMultiDict([])
ps: Using Python for the Cloud Function and Node.JS for the Cloud Task.
Tried to Google, and searched over here and have no answers, even the docs don't say anything about it. Any tips?
that's the code that I'm using to call Cloud Task:
const auth = new GoogleAuth({})
const client = new CloudTasksClient({ credentials: credentials });
const formData = new FormData();
formData.append('pdf_file', 'x');
const task = {
httpRequest: {
httpMethod: 'POST',
url,
body: formData,
headers: {
"Content-Type": "multipart/form-data"
},
oidcToken: {
serviceAccountEmail: "x#appspot.gserviceaccount.com"
}
}
}
const request = { parent: parent, task: task, auth: auth }
const [ response ] = await client.createTask(request);
console.log(response);
console.log(`created task ${response.name}`);
that's the code for my Cloud Function, that I'm using as a test:
import functions_framework
#functions_framework.http
def main(request):
if request.method != 'POST':
print('ERROR: its not a post request')
return {'error': 'Invalid method'}, 400
print(request.form)
return 'ok'

Fetch content from API

My frontend (ReactJS) is located at localhost:3000 which sends a request to my backend (localhost:5090/api/fetch/<name>)
frontend-snippet:
handleSubmit(event) {
//alert('A name was submitted: ' + this.state.value);
fetch('http://127.0.0.1:5090/api/fetch/' + this.state.value,
{
mode: 'no-cors'
}).then((response) => console.log(response));
event.preventDefault();
}
the backend receives the request:
127.0.0.1 - - [22/Oct/2020 10:54:44] "GET /api/fetch/212 HTTP/1.1" 200 -
and responses with {"stock": name}
Python
def get(self, name):
print(name)
return {"stock": name}
however, i'm unable to get the response.
What am I missing, that I can actualy see the response data of {"stock": <name>}?
Sending a request with no-cors returns an opaque type object.
Updating my backend to allow all sources
What happens with cross-origin requests from frontend JavaScript is that browsers by default block frontend code from accessing resources cross-origin. If Access-Control-Allow-Origin is in a response, then browsers will relax that blocking and allow your code to access the response.
from flask_cors import CORS
...
CORS(app, resources={r"/api/*": {"origins": "*"}})
and then removing the mode to cors in frontend
fetch('http://127.0.0.1:5090/api/fetch/' + this.state.value,
{
}).then(response => response.json())
.then(response => {
console.log(response)
})
.catch(error => {
console.log('Error ' + error);
})
successfully displays the data
(response)
{stock: "111"}
Can you try like this
.then(response => response.json())
.then(result => {
console.log(result);
})
})
.catch(error => {
console.log('Error ' + error);
})
code in Ejaz's answer should work just fine, But I'd recommned you to read the official documentation for fetch api
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
You cant just console.log the response from fetch api it just returns a promise,
you cant print response.body either as it returns a readable stream, Use interface methods on body to read the stream into completion.
You can call response.text() or response.json() depending on type of data your api returns.
Using fetch api, you have to deal with promises, I'll try to refactor your handleSubmit function using async/await, much cleaner way to deal with promises.
async handleSubmit(event) {
event.preventDefault();
try{
let response = await fetch('http://127.0.0.1:5090/api/fetch/'+this.state.value, {
mode: 'no-cors'});
console.log(await response.json());
}
catch(error => console.log('Error ' + error));
}

send metatags through Google Cloud Signed URL with Django

I'm using signed url to get or post/put video fileq on my google storage. My modules refers on signed_url V4 and it's working quite well.
I wanted to add some metadata tags to my requests in order to manage more efficiently the charges related to GCS. But since I added those headers, the requests failed returning a cors policy error : (I have shortened the signature in the block above to make it more readable)
Access to XMLHttpRequest at 'https://test-dev-slotll.storage.googleapis.com/uploads/dave/FR/eaa678c9/2020/9/785f/f45d3d82-785f_full.mp4?X-
Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=dev-storage%40brilliant-tower-264412.iam.gserviceaccount.com%2F20200926%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20200926T093102Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host%3Bx-goog-meta-account&x-goog-signature=6fbb27e[...]bd0891d21' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
and the detailed message:
<Error>
<Code>MalformedSecurityHeader</Code>
<Message>Your request has a malformed header.</Message>
<ParameterName>x-goog-meta-account</ParameterName>
<Details>Header was included in signedheaders, but not in the request.</Details>
</Error>
The cors are conigured to allow almost everything on my bucket :
[{"maxAgeSeconds": 3600, "method": ["*"], "origin": ["*"], "responseHeader": ["*"]}]
and here is the Python/Django function
def _get_signed_url(self, http_method="GET"):
"""' create a signed url with google storage
create a 'GET' request by default,
add method='w' or 'put' to create a 'PUT' request
get_signed_url('w')->PUT / get_signed_url()->GET
"""
if http_method.lower() in ["w", "put", "post"]:
http_method = "PUT"
else:
http_method = "GET"
signed_url = generate_signed_url(
settings.GS_BUCKET_NAME,
self.file.name,
subresource=None,
expiration=3600,
http_method=http_method,
query_parameters=None,
headers={'x-goog-meta-language':'french','x-goog-meta-account':'david',},
)
return signed_url
As I wrote it above, method get_signed_url() is copied from signed_url V4
if i replace headers = {'x-goog-meta-language':'french','x-goog-meta-account':'david',},
by hearders = {} or headers = None (as it was previously, it works fine
last, when I click on the link given by the signed-url, I got an error message:
The signed url as well as blob uploading or downloading are working fine without the headers for months but I do not see why the server is responding that the header meta tags are malformed...
I will appreciate any help
thanks !
I was getting the same error message when I was performing a GET request on the pre-signed urls from GCP. Adding the content-type: "application/octet-stream" solved it for me.
ajaxCall_getFile = $.ajax({
xhr: function() {
var xhr = new window.XMLHttpRequest();
return xhr;
},
type: 'GET',
url: <PRE-SIGNED URL>,
contentType: "application/octet-stream",
processData: false,
success: function(file_data){
alert("file downloaded");
},
error: function(err) {
alert('Download failed, please try again.');
}
});
As #Sideshowbarker suggests it in his comments, the problem came from the client.
The signed url was used to send files to storage through ajax but no custom header were added to the ajax.
By specifying the headers in the ajax, the PUT request of a signed url with custom metadata works well.
function ajaxSendToStorage(uuid, url) {
// Sending ONE file to storage
var file = getFileById(uuid);
$.ajax({
method: "PUT",
contentType: file.type,
processData: false,
dataType: "xml",
crossDomain: true,
data: file,
url: url,
headers: {"x-goog-meta-account":"david","x-goog-meta-language": "fr"},
success: function() {},
complete: function() {},
});
}

Categories

Resources