Call an URL with Auth Token with Fetch API - javascript

I'm using the Fetch API to Login to my Webapp using Baisc Authentication. After a successful login the server returns a token as a json object. I am then using this token to make requests to other API Endpoints. Those endpoints just show a webpage if the request contains a valid token.
Everything works fine but nothing shows up in the browser after I make a successful call with the fetch API..
If I call the API endpoint in a REST Client it returns the html which seems to be fine. The problem seems to be that the browser should call the url instead of just fetch the html..
Here is my code. I am getting the "Success"-Alert - so everything seems to work fine. But I need the browser to show the result as a new page (some kind of a direct call of the url with the token in the header).
function login(event) {
event.preventDefault();
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
let url = URL + 'login';
let headers = new Headers();
headers.append('Authorization', 'Basic ' + btoa(username + ":" + password));
fetch(url, {method:'GET',
headers: headers,
})
.then(function(response)
{
if (response.ok) {
return response.json();
} else {
let error = document.getElementById("login-error");
error.innerHTML = "Username/Password incorrect.";
}
})
.then(function(json)
{
if (typeof(json) !== "undefined") {
startWebapp(json.token);
}
})
.catch(error => console.error('Error:', error));
}
function startWebapp(token) {
let url = URL + 'webapp/overview';
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + token);
fetch(url, {method:'GET',
headers: headers,
})
.then(function(response) {
alert("Success!");
return response;
});
}
How can I achieve that the browser actually calls the url with the API token and opens it if the fetch is successful?

For anyone searching for a solution:
This is actually not possible with JavaScript nor the fetch API. For me the (easiest) solution is to save the token in a cookie. The server then searches for a token in the cookie and uses it for authentication/authorization. This works pretty well and I don't need to send the token on every request.
Hope that helps.

Related

Google Drive API thumbnailLink CORS

I'm trying to render the thumbnailLink image returned from the Google Drive API's /files endpoint. I'm doing this in an app running from localhost.
The docs mention this:
If the file isn't shared publicly, the URL returned in Files.thumbnailLink must be fetched using a credentialed request.
How do I build this "credentialed request"? I tried:
Adding my OAuth access token as an access_token query parameter. This gives me a 403 response (same as without the access_token param).
fetch()ing the image as a blob with the header { Authorization: 'Bearer ' + accessToken }. This request fails with a CORS error.
My OAuth access token has the following scopes:
https://www.googleapis.com/auth/drive.readonly
https://www.googleapis.com/auth/userinfo.email
EDIT: Minimal examples:
// This gives me a 403 response
function ThumbnailUsingUrl({ thumbnailLink }) {
const thumbnailUrl = new URL(thumbnailLink);
const searchParams = new URLSearchParams(thumbnailUrl.search);
searchParams.set("access_token", "my_access_token");
thumbnailUrl.search = searchParams.toString();
return <img src={thumbnailUrl.toString()} />;
}
// This gives me a CORS error
function ThumbnailUsingBlob({ thumbnailLink }) {
const [blob, setBlob] = useState(null);
useEffect(() => {
(async () => {
const response = await fetch(thumbnailLink, {
headers: {
Authorization: `Bearer <my_access_token>`,
},
});
setBlob(await response.blob());
})();
}, []);
if (!blob) return null;
return <img src={URL.createObjectURL(blob)} />;
}

issue with making a call using fetch and jwt

*My goal here is to get the location of bikes from a bike-sharing company's API.
I did Steps 1 and 2 using Postman. but ill try to integrate it into my code once I get the hang of it.
The first step is to verify your email and generate an Auth token. This requires only a verifiable email address. Make a POST request to https://web.spin.pm/api/v1/magic_links with the body:
{"email": "sampleemail#gmail.com"}
From there, you will need to find the token within your email. This token needs to be sent with a POST request to
https://web.spin.pm/api/v1/auth_tokens with the body:
{
"grant_type": "magic_link",
"magic_link": {
"email": "<email>",
"token": "<token>"
}
}
This request returns a JSON that looks like this: {"jwt":"eyJ0eXAiOiJ.....cXVLw","refreshToken":"2cb07....bab5030","existingAccount":false}
To get the position of vehicles so a GET-Request to https://web.spin.pm/api/v3/vehicles?lng=-77.0146489&lat=38.8969363&distance=&mode= User Header Authorization: Bearer to Authenticate and use the jwt-Token we got from the Auth request.
You will get something like this as return JSON {"vehicles":[{"lat":37.69247,"lng":-122.46595,"last4":"3595","vehicle_type":"bicycle","batt_percentage":null,"rebalance":null}, … ]}
Step 3 is done using (async/awit function) using fetch where I am having the problem with. I copy-pasted the jwt in my .env file and set up the proper headers.
I get a 401 response when making the call. when I tested step 3 using postman everything seems to work fine.
I have attached a screenshot of the error in this post. Hopefully its more clear, Thanks in advance.
const fetch = require("node-fetch");
require('dotenv').config();
async function getBikes()
{
const lat = '38.897574612438575';
const lng = '-77.01855164084469';
const api_url = `https://web.spin.pm/api/v3/vehicles?lng=${lng}&lat=${lat}&distance=&mode=`;
const jwt_key = process.env.BERER_KEY;
try{
const config = { method: 'GET',
headers: {json: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer'+ jwt_key
} },
rejectUnauthorized: false
};
const response = await fetch(api_url,config );
const data = await response.json(); //response.json() //headers //.jwt; //response.json()
if (response.ok)
{
console.log("STATUS CODE IS: "+response.status);
console.log('My JWT:', response);
return data;
}
else{
console.log("something went wrong ");
console.log("STATUS CODE IS: "+ response.status);
console.log( response);
}
} catch (error) {
console.log(error);
}
}
const y = getBikes();
console.log(y)
BEARER_KEY=eyJhbGciOiJIUzI1NiJ9.eyJ1c2V

Difference between Python requests POST and axios POST

I am having a difficult time understanding why my API call does not work in axios (relatively new to JS). I have built an API server that takes in an Authorization header with a JWT token.
Here is my POST request workflow in Python:
resp = requests.post('http://127.0.0.1:8000/api/v1/login/access-token', data={'username': 'admin#xyz.com', 'password': 'password'})
token = resp.json()['access_token']
test = requests.post('http://127.0.0.1:8000/api/v1/login/test-token', headers={'Authorization': f'Bearer {token}'})
# ALL SUCCESSFUL
Using axios:
const handleLogin = () => {
const params = new URLSearchParams();
params.append('username', username.value);
params.append('password', password.value);
setError(null);
setLoading(true);
axios.post('http://localhost:8000/api/v1/login/access-token', params).then(response => {
console.log(response)
setLoading(false);
setUserSession(response.data.access_token);
props.history.push('/dashboard');
}).catch(error => {
setLoading(false);
console.log(error.response)
if (error.response.status === 401) {
setError(error.response.data.message);
} else {
setError("Something went wrong. Please try again later.");
}
});
}
// the above works fine
// however:
const [authLoading, setAuthLoading] = useState(true);
useEffect(() => {
const token = getToken();
if (!token) {
return;
}
axios.post(`http://localhost:8000/api/v1/login/test-token`, {
headers: {
'Authorization': 'Bearer ' + token
}
}).then(response => {
// setUserSession(response.data.token);
console.log('we made it')
setAuthLoading(false);
}).catch(error => {
removeUserSession();
setAuthLoading(false);
});
}, []);
if (authLoading && getToken()) {
return <div className="content">Checking Authentication...</div>
}
// RETURNS A 401 Unauthorized response...
What is different about the two above requests? Why does the axios version return different results than requests?
In my API, CORS have been set to *, and I know that the token within Axios is being saved properly in sessionStorage.
Any ideas?
As far as I can see you are passing your username and password in axios as params and as body data in your python request, I am not sure if your backend expects it as params or body data but try changing const params = new URLSearchParams(); to
const params = new FormData(); if the problem is that the backend isn't getting the body data it needs. The best thing I could recommend is checking your browser network tab and seeing what exactly the problem is when you hit your server.

Alexa Skill/Lambda wont Log Request Body

I am new to Alexa Skill / Lambda. I am trying to print the body of my request. I have tried using fetch with isomorphic-unfetch and now I am trying to use the npm request module. For some reason I cannot get the request body to print in CloudWatch Logs, everything else I am log shows up fine. I am also not getting an error because I am logging that too and it is not showing up. What am I doing wrong? I am using account linking and I know that I have the user accessToken and I am using that token to verify who the user is by hitting another endpoint so I know I have the token and the method I have works if I run it locally and with just node but as soon as I try and log the body in on aws/lambda CloudWatch I cant get the body of the request to log so im not sure if its working. Now when I mean it works locally I mean I remove ${event.session.user.accessToken} and hardcode in the access token then run node index.js and then I can see the body of the request.
All help is welcome. Thanks!
let skill;
exports.handler = async function (event, context) {
getUserInfo(event);
console.log(`REQUEST++++${JSON.stringify(event)}`);
if (!skill) {
skill = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
CreateQuoteIntent,
SessionEndedRequestHandler,
)
.addErrorHandlers(ErrorHandler)
.create();
}
// console.log("SESSION**** " + event.session.user.accessToken);
const response = await skill.invoke(event, context);
console.log(`RESPONSE++++${JSON.stringify(response)}`);
return response;
};
function getUserInfo(event) {
console.log("TOKEN: " +event.session.user.accessToken);
const options = {
url: 'not showing url but api url is fine ',
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Client-ID': 'not showing me key on stack',
'Authorization': `OAuth ${event.session.user.accessToken}`
}
};
console.log('BEFORE REQUEST');
request(options, function(err, res, body) {
console.log("ERROR: " + err);
// let json = JSON.parse(body);
console.log("BODY: " + body);
});
console.log('AFTER REQUEST');
}

Basic authentication (or any authentication) with fetch

Couldn't find any documentation on this, so before I dig deep in code does anyone out there know how to use basic authentication when making a REST request using 'fetch' (https://github.com/github/fetch).
Just tried the following line, but the header was not set in the request:
fetch('http://localhost:8080/timeEntry', {
mode: 'no-cors',
headers: { 'Authorization': 'Basic YW5kcmVhczpzZWxlbndhbGw=' }
})
.then(checkStatus)
.then(parseJSON)
.then(function(activities) {
console.log('request succeeded with JSON response', data);
dispatch(activitiesFetched(activities, null));
}).catch(function(error) {
console.log('request failed', error);
dispatch(activitiesFetched(null, error));
});
The username and password is my own first and last name, using curl it works.
If I put { 'Accept' : 'application/test' } Accept is set, just not Authorization... strange.
Just for me to able to continue I added credentials: 'include' which makes the browser to prompt for username and password which is used for communicationg with the REST backend. Just for testing, will use OAuth further on.
fetch('http://localhost:8080/timeEntry', {
mode: 'no-cors',
credentials: 'include'
})
.then(checkStatus)
.then(parseJSON)
.then(function(activities) {
console.log('request succeeded with JSON response', data);
dispatch(activitiesFetched(activities, null));
}).catch(function(error) {
console.log('request failed', error);
dispatch(activitiesFetched(null, error));
});
no-cors mode prevents the headers from being anything other than simple headers.
"Authorization" header doesn't fit to simple headers. See more here: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
Note that if you use fetch with Authorization header you will NOT establish a session. You will have to manually add that header for every request. Navigating to secured path would also not be possible.
So to make this work You should pre-authenticate with XMLHttpRequest. You can do this like so:
var authUrl = location.origin + '/secured-path/';
var http = new XMLHttpRequest();
http.open("get", authUrl, false, login, pass);
http.send("");
if (http.status == 200) {
//location.href = authUrl;
} else {
alert("⚠️ Authentication failed.");
}
Note that above is synchronous so you don't need a callback here.
So after doing this you can use fetch without headers e.g. this request should be successful:
fetch(authUrl, {
method: 'get',
}).then(function(response) {
console.log(response);
});
Since it looks like the library you are using is a polyfill for Fetch API, I'm going to work off of the assumption that the syntax should carry through as well.
The samples I found on Mozilla's page indicate that the fetch method signature is fetch('API_ENDPOINT', OBJECT) where object looks like:
myHeaders = new Headers({
"Authorization": "Basic YW5kcmVhczpzZWxlbndhbGw="
});
var obj = {
method: 'GET',
headers: myHeaders
})
So the method becomes:
fetch('http://localhost:8080/timeEntry', obj)
.then(checkStatus)
.then(parseJSON)...
I have not tested this code, but it seems consistent with what I was able to find. Hope this points you in the right direction.

Categories