FastApi 422 Unprocessable Entity, on authentication, how to fix? - javascript

Cannot understand even if i delete all inside function and just print something still got this error, but when i use fastapi docs, and try signing with that, it work.
#auth_router.post('/signin')
async def sign_in(username: str = Form(...), password: str = Form(...)) -> dict:
user = await authenticate_user(username, password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid username or password',
)
user_obj = await User_Pydantic.from_tortoise_orm(user)
user_token = await generate_token(user_obj)
return {
'access_token': user_token,
'token_type': 'bearer',
}
Before i use OAuth2PasswordRequestForm, when got 422 error, try another way.
my model is tortoise orm, and when need i convert it to pydantic model,
in docs all is work.
JS
handleEvent(signinform, 'submit', e => {
e.preventDefault();
if(!isEmpty(signinform)){
signInUsername = getElement('input[name="username"]', signinform).value;
signInPassword = getElement('input[name="password"]', signinform).value;
recaptchaV3 = getElement('[name="g-recaptcha-response"]').value;
if(recaptchaV3){
signInData = new FormData();
signInData.append('username', signInUsername);
signInData.append('password', signInPassword);
isLogened = request('POST', '/signin', signInData);
if(isLogened){
log(isLogened);
}
} else{
alert('Reload Page');
}
}
})
authenticate_user func
async def authenticate_user(username: str, password: str):
user = await User.get(username=username)
if not user or not user.verify_password(password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid username or password',
)
return user
My request function
const request = (method, url, data = null) => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onerror = function () {
console.log(xhr.response);
};
xhr.onload = () => {
if (xhr.status === 200) {
return resolve(JSON.parse(xhr.responseText || '{}'))
} else {
return reject(new Error(`Request failed with status ${xhr.status}`))
}
}
if (data) {
xhr.send(JSON.stringify(data))
} else {
xhr.send()
}
})
}

Although you did not publish the error, who's purpose is to tell you the problem, I'm fairly sure the problem lies in the way you perform the request.
The line
xhr.setRequestHeader('Content-Type', 'application/json')
means that you are sending json data, which is not accepted by the authentication form of openapi. Also, you are stringifying the data into json which, again, is not an accepted format.
Thus, changing the content type to www-form-urlencoded and adding a FormData object to your request's body, will make it work.
You can see it in the github discussions below
https://github.com/tiangolo/fastapi/issues/2740
https://github.com/tiangolo/fastapi/issues/1431

Ensure that you have provided content type in your request,
xhr.setRequestHeader('Content-Type', 'application/json')
If it's there then verify the format of your input. Here, you have declared two varibales - signInUsername and signInPassword.
Important: You might provided a default value for these files in FastAPI. In that case, if you provided an empty content or null as the attribute values then the fastapi will throw the above error.
Ensure that the data that you are sending to the server is correct.

Related

Is it possible to access token in service-worker fetch request headers?

I want to send a GET request including a token "Authorization" header to my nodejs server. For this, I use this function client-side:
// Do a secured GET API request and return response object
async function getJSON(url) {
try {
// Send request
const res = await fetch(url, {
headers: { Authorization: localStorage.getItem('token') }
});
// If something went wrong
if (!res.ok) {
// If token is invalid
if (res.status === 401) {
// Logout
localStorage.removeItem('token');
location.replace(`/?msg=${dict.get('expired-session')}`);
}
// Return an object with error message and status code
return { error: res.statusText, status: res.status };
}
// Else return response object
else return await res.json();
} catch (err) {
// Return an object with error message
return { error: err.message };
}
}
It works fine, but I use a service worker to cache requests as they are made and I don't want API requests to be cached, so I though I could just check for the "Authorization"'s presence:
// Fetch event
self.addEventListener('fetch', e => e.respondWith(respond(e)));
async function fetchAndCache(req, cache_name) {
const { url, headers } = req;
console.log({ url, headers });
// Fetch request
const fetch_res = await fetch(req);
const is_get = req.method === 'GET';
const is_api = req.headers.Authorization;
const is_cahing_domain = cache_domains.some(domain => req.url.includes(domain));
if (is_cahing_domain && is_get && !is_api) {
// Open cache and save a cloned result
const cache = await caches.open(cache_name);
cache.put(req, fetch_res.clone());
}
return fetch_res;
}
async function respond(e) {
if (!use_cache) return fetch(e.request);
// Try to get response from cache
const cached_res = await caches.match(e.request);
// If response is found, return it
if (cached_res) return cached_res;
// If request is not found, try to fetch it
return await fetchAndCache(e.request, 'main');
}
Unfortunately, the logs show empty headers:
Even though the server does get the token and the cached (since the condition does not work) request also includes it:
I searched for a few hours, tried every solutions in similar questions (here and here) but none worked. Please help.
I don't know about Authorization header. But the right way to print headers is via method 2. It seems like you are using method 1.
console.log("Try 1 to print headers.");
console.log(request.headers);
console.log("Try 2 to print headers.")
for (const pair of event.request.headers.entries()) {
console.log(pair[0]+ ': '+ pair[1]);
}
Console logs
Headers {} does not mean that the header is empty. It means that it contains an object which is not printed in detail in your console.
You can access the Authorization header as follow:
console.log('AuthorizationHeader is:',headers.get('Authorization'))

Generic fetch replay to handle token refresh

I am looking for a generic way to replay a fetch if I get a 401 response.
My application is a SPA using OIDC. Our frontend developers utilise fetch, and a ServieWorker injects the access_token into the AJAX request before sending the request to the API(s). There are times when a fetch occurs but access_token is expired. When that happens, I want to use the refresh_token to get a new access_token and then replay the fetch, returning the replayed fetch in the Promise. Ideally, this would be something the frontend developers would not even know is happening.
Meaning that a UI developer will code something like what's below (remember, the access_token is injected via ServiceWorker):
fetch("https://backend.api/user/get/1")
.then(resp =>
{
console.log("user information is XYZ. Raw response:", resp);
})
When really what's happening in the background is:
[Initial request] > [Expired token response] > [Request new token] > [Initial request replayed]
I've experimented with overriding the fetch method with what's below, but I can't figure out a generic way to recreate/clone the original fetch:
window.fetch = new Proxy(window.fetch, {
apply(fetch, that, args) {
// Forward function call to the original fetch
const result = fetch.apply(that, args);
// Do whatever you want with the resulting Promise
result.then(resp =>
{
if(resp.status == 400 || resp.status == 401)
{
let rt = getRefreshToken();
return fetch("https://idaas.provider/get/new/token", {
"method": "POST",
"body": new URLSearchParams({
grant_type: "refresh_token",
refresh_token: rt,
client_id: client_id_str
})
});
}
}).then(resp =>
{
let new_token = resp.new_token;
send_new_token_to_service_worker(new_token);
return new_token
}).then(tok =>
{
// How do I replay the original request?
})
return result;
}
});
The goal is to simplify what the UI developers need to handle. I want them focused on UX and have this sort of error handling done in the background.
Note: If necessary, I would be open to not using fetch and instead utilising a wrapper method. Obviously, because of the code already written surrounding fetch, the new method would need to accept the same arguments and produce the same return.
window.fetch = new Proxy(window.fetch, {
apply(fetch, that, args) {
let fetchApi = [
fetch(args.shift())
];
let fetchToken = [
fetch("https://idaas.provider/get/new/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: getRefreshToken(),
client_id: client_id_str,
})
}).then(token => send_new_token_to_service_worker(token)),
];
return Promise.allSettled(fetchApi).then(results => {
let rejected = results
.map(result => result.status)
.includes("rejected");
if (rejected) {
return Promise.all([...fetchToken, ...fetchApi]).then(results => results.at(1));
} else {
return results.at(0).value;
}
});
},
});
Usage fetch("https://your-api").then(resp => resp);

AJAX Receiving multiple NodeJS Responses for Single Request

I am building a WebApp that includes heavy AJAX calling from the frontend and NodeJS Express at the backend. My Frontend Code looks like this-
This is the global AJAX function I am using in my all projects-
function _ajax(params = {}, next = null, url = '', method = 'post', carry = null) {
params = this._isObjectValid(params, 1, -1) ? params : {};
for (let key in params) {
if (params.hasOwnProperty(key)) {
const checkObject = params[key];
if (typeof checkObject === 'object' || Array.isArray(checkObject)) {
params[key] = JSON.stringify(checkObject);
}
}
}
const httpRequest = new XMLHttpRequest();
httpRequest.open(method, url, true);
httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
const p = Object.keys(params).map(function (value) {
return encodeURIComponent(value) + '=' + encodeURIComponent(params[value]);
}).join('&');
if (next) {
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
}
httpRequest.send(p);
}
This is the global Click Event Binding function
function _click(next, target, node, extra = null) {
node.onclick = (event) => {
next(event, target, extra);
};
return node;
},
This is my AJAX Request
_click(
() => {
_ajax(
{mod: 'login'},
(request) => {
console.info(request.status);
console.info(request.response);
},
'http://localhost:1008/'
)
}, null, buttonSubmit);
My Backend Code for Handling Post AJAX Requests is:
app.post('*', async (req, res) => {
console.info('POST RECEIVED');
const params = req.body;
console.info(params);
await posts(req, res, params, dbo);
});
export async function posts(req, res, params, dbo) {
if (res) {
const mod = params.mod;
switch (mod) {
case 'user':
await sendPass(res, 'User Will', null);
break;
default:
await send(res, 'Invalid Module Call', null);
}
}
}
export function send(res, message, data, result = false) {
res.send({result: result, message: message, data: data});
res.end();
}
export function sendError(res, message, data) {
send(res, message, data, false);
}
export function sendPass(res, message, data) {
send(res, message, data, true);
}
Now in any other server like PHP or .NET, my web app is getting exactly one response from the server when I click the button, but for NodeJS I am getting three responses while I am processing the AJAX request only once-
This is repeating for every AJAX Request. So If I'm processing 3 AJAX Requests, my Web App is receiving 9 responses. I tried to search on the internet on this but can't find much. Since this scenario is not repeating on any other Server except NodeJs, so it may be not a problem from JavaScript event binding or ajax processing, or a browser issue.
Any suggestion will be appreciable. Thanks in advance.
I found the bug in the global AJAX function. I need to detect the status of readyState of httpRequest. I'm posting an updated version of the function:
Before:
httpRequest.onreadystatechange = function () {
next(httpRequest, carry);
};
After:
/**
* readyState values
* 0: request not initialized
* 1: server connection established
* 2: request received
* 3: processing request
* 4: request finished and response is ready
*/
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4)
next(httpRequest, carry);
};
Surprisingly, it was not creating problems in other servers like WAMP or .NET for reasons I still don't know.

Unsupported_grant_type error using react native's fetch() when doing POST request

I'm not sure what is wrong with my request format, I tried dozens of format from people around SO and other places but nothing works for me. Here's a snippet of my code:
This is the content of the function that creates the endpoint, header, and body then pass it to an all purpose function on another file called fetchAPI.
var endpoint = "oauth/token";
let userData = {
"username" : user[0], //MemberCenterLanding
"password" : user[1], //testpass
"grant_type" : 'password'
}
let header = {
"Authorization":"Basic TWVtYmVyQ2VudGVyTGFuZGluZzp0ZXN0cGFzcw==",
"Content-Type": "application/x-www-form-urlencoded",
};
return dispatch => {
return fetchAPI(endpoint,'POST', header, userData)
.then((json) => { //not relevant after this point
and this is the fetchAPI function.
export function fetchAPI(endpoint, method, header, data) {
let url = 'http://10.64.2.149:8081/' + endpoint;
//let url ='http://secure.cbn.net.id:8080/'+ endpoint;
let options = {
method: method,
headers: header,
data: JSON.stringify(data)
}
return fetch(url, options)
.then(response => {
return response.json()
.then((json) => {
alert("request content:\n"+ JSON.stringify(options, undefined, 2) + "\n\nurl: \n"+ url + "\n\nresponse status: \n"+ response.status + "\n\nJSON content:\n"+JSON.stringify(json,undefined,2));
if (response.status === 200 || response.status === 201) {
return json;
} else if (response.status === 408) {
throw('Request Timeout');
}
else if (response.status === 400){
throw ('Bad request'); //the request always end up here
}
else {
if (json.errors) {
console.log("json error");
throw(json.errors);
} else {
console.log("unknown error");
throw('unknown error');
}
}
})
})
.catch(error => {//not relevant after this point
And as you can see I printed the inside of the request options for ease of debugging so I know exactly what's happening along with the url, response status and returned JSON file:
I have tried different formats for everything and the only constant I can find is that the body a.k.a data part of the option that contains the username, password and grant-type is wrong. because changing anything else doesn't affect anything.
Everything always result in response status = 400 and JSON content: "unsupported_grant_type".
Though some posts suggest that it might not have anything to do with the grant type at all but I tried exactly the same input in postman and it works.
I even tried non-stringified version of data, FormData version, as well as the normal string sequence build using array.join() method, but nothing's fruitful.
Current suspicion:
Maybe the api doesn't deal with input from react native somehow
I completely misunderstood the requirement for the request format (I've checked multiple times though even tried copying the
code generated from postman directly)
Theres supposed to be some sort of setup/config need to be done for react native to work with api (though I've never encountered such article before)
I don't know where else to look, any insight would be highly appreciated :)

Wrap javascript fetch to add custom functionality

I would like to know if it is possible to do this, because I'm not sure if I'm wrong or if it isn't possible. Basically, what I want to do is to create a wrap function for native fetch javascript function. This wrap function would implement token validation process, requesting a new accessToken if the one given is expired and requesting again the desired resource. This is what I've reached until now:
customFetch.js
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}
consumer.js
const getResourcePrivate = () => {
const url = MAIN_URL + '/resource';
customFetch(url, {
method: 'get'
},{
url: AUTH_SERVER_TOKEN,
unauthorizedRedirect: AUTH_URI,
tokenName: TOKEN_NAME
}).then((json) => {
const resource = json ? json.resource : null;
if (resource) {
console.log(resource);
}
else {
console.log('No resource has been provided.');
}
});
}
I'll try to explain a little better the above code: I want to make transparent for users the token validation, in order to let them just worry about to request the resource they want. This approach is working fine when the accessToken is still valid, because the return request instruction is giving to the consumer the promise of the fetch request.
Of course, when the accessToken has expired and we request a new one to auth server, this is not working. The token is refreshed and the private resource is requested, but the consumer.js doesn't see it.
For this last scenario, is it possible to modify the flow of the program, in order to refresh the accessToken and perform the server call to get the private resource again? The consumer shouldn't realize about this process; in both cases (accessToken is valid and accessToken has expired and has been refreshed) the consumer.js should get the private requested resource in its then function.
Well, finally I've reached a solution. I've tried to resolve it using a Promise and it has work. Here is the approach for customFetch.js file:
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
const requestResource = (resolve) => {
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken(resolve);
}
else {
resolve(d.json());
}
});
}
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = (resolve) => {
window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
if (d.status === 401) {
// Unauthorized
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
requestResource(resolve);
}
else {
console.log('no token has been sent');
return null;
}
});
}
let promise = new Promise((resolve, reject) => {
requestResource(resolve);
});
return promise;
}
Basically, I've created a Promise and I've called inside it to the function which calls to server to get the resource. I've modified a little the request(now called requestResource) and refreshAccessToken in order to make them parametrizable functions. And I've passed to them the resolve function in order to "resolve" any function once I've received the new token.
Probably the solution can be improved and optimized, but as first approach, it is working as I expected, so I think it's a valid solution.
EDIT: As #Dennis has suggested me, I made a mistake in my initial approach. I just had to return the promise inside the refreshAccessToken function, and it would worked fine. This is how the customFetch.js file should look (which is more similar to the code I first posted. In fact, I've just added a return instruction inside the function, although removing the start and end brackets would work too):
// 'url' and 'options' parameters are used strictely as you would use them in fetch. 'authOptions' are used to configure the call to refresh the access token
window.customFetch = (url, options, authOptions) => {
const OPTIONS = {
url: '',
unauthorizedRedirect: '',
storage: window.sessionStorage,
tokenName: 'accessToken'
}
// Merge options passed by user with the default auth options
let opts = Object.assign({}, OPTIONS, authOptions);
// Try to update 'authorizarion's header in order to send always the proper one to the server
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${opts.storage.getItem(opts.tokenName)}`;
// Actual server request that user wants to do.
const request = window.fetch(url, options)
.then((d) => {
if (d.status === 401) {
// Unauthorized
console.log('not authorized');
return refreshAccesToken();
}
else {
return d.json();
}
});
// Auxiliar server call to get refresh the access token if it is expired. Here also check if the
// cookie has expired and if it has expired, then we should redirect to other page to login again in
// the application.
const refreshAccesToken = () => {
return window.fetch(opts.url, {
method: 'get',
credentials: 'include'
}).then((d) => {
// For this example, we can omit this, we can suppose we always receive the access token
if (d.status === 401) {
// Unauthorized and the cookie used to validate and refresh the access token has expired. So we want to login in to the app again
window.location.href = opts.unauthorizedRedirect;
}
return d.json();
}).then((json) => {
const jwt = json.token;
if (jwt) {
// Store in the browser's storage (sessionStorage by default) the refreshed token, in order to use it on every request
opts.storage.setItem(opts.tokenName, jwt);
console.log('new acces token: ' + jwt);
// Re-send the original request when we have received the refreshed access token.
return window.customFetch(url, options, authOptions);
}
else {
console.log('no token has been sent');
return null;
}
});
}
return request;
}

Categories