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)} />;
}
Related
I'm building an app with Capacitor JS & Nuxt JS to interface with the Slack API so that I can set my Slack status, I've created a Slack App and have a xoxp- token which works just fine when I hit the endpoint with a POST request via Postman, but from my browser (localhost) and from the running app on my phone I'm getting the following CORS error:
Access to XMLHttpRequest at 'https://slack.com/api/users.profile.set' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
Now this seems silly because you must use the authorization header to provide the Bearer token for authentication, but even after temporarily omitting this, the CORS error remains.
I'm trying to POST to the endpoint for users.profile.set
View another method
What am I missing in my Axios code?
setSlackStatusWithReminder (title, expiry) {
const body = this.convertToQueryString({
profile: this.convertToQueryString(this.profile),
token: 'xoxp-mytoken'
})
this.$axios.post('https://slack.com/api/users.profile.set', body, {
timeout: 10000,
transformRequest(data, headers) {
delete headers.common['Content-Type'];
return data;
}
}).then(res => {
console.log(res)
if (res.data.ok != true) {
alert('something went wrong with the .then')
}
this.isSettingStatus = false
this.actions.isShown = false
}).catch(err => {
this.isSettingStatus = false
this.actions.isShown = false
})
},
UPDATE
I've got a function to convert my request body into a query string from my data, which looks like:
export default {
data () {
return {
profile: {
status_text: '',
status_emoji: '',
status_expiration: 0
}
}
}
}
Query string function to convert body
convertToQueryString (obj) {
const convert = Object.keys(obj)
.map((key, index) => `${key}=${encodeURIComponent(obj[key])}`)
.join('&')
return convert
},
And I'm building it up like:
const body = this.convertToQueryString({
profile: this.convertToQueryString(this.profile),
token: 'xoxp-mytoken'
})
It's giving me an invalid_profile response.
Slack doesn't respond to the pre-flight OPTIONS request with a compatible response.
Avoid the preflight check entirely by ensuring it matches the requirements to be handled as a so-called "simple request".
Notably, ensure the content-type is application/x-www-form-urlencoded, serialize the request body to match and do not use the Authorization header to pass your bearer token, instead pass it as an argument in your request (token).
Not sure why this was so difficult, the following is a valid POST request to the Slack API:
// this.profile -> is the object with the status_* fields
const body = `profile=${JSON.stringify(this.profile)}&token=some_token`
this.$axios.post('https://slack.com/api/users.profile.set', body, {
timeout: 10000,
transformRequest(data, headers) {
delete headers.common['Content-Type'];
return data;
}
}).then(res => {
console.log(err)
}).catch(err => {
console.log(err)
})
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.
I'm building a NextJS app, and I'm trying the access a cookie so I can use it to set a Http Header for GraphQL Request, I am using apollo-link-context. This is the code to create the ApolloClient
function createApolloClient(initialState = {}) {
const httpLink = new HttpLink({ uri: `${baseUrl}/graphql`, credentials: 'same-origin', fetch })
const authLink = setContext((_, prevCtx) => {
let token = ''
if (typeof window === 'undefined') token = getCookieFromServer(authCookieName, REQ)
else token = getCookieFromBrowser(authCookieName)
return ({ headers: { 'Auth-Token': token } })
})
const client = new ApolloClient({
ssrMode: typeof window === 'undefined',
cache: new InMemoryCache().restore(initialState),
link: authLink.concat(httpLink)
})
return client
}
The issue here is that the getCookieFromServer function expects an Express Request as the second argument, so it can extract the cookie from req.headers.cookie, and I have no idea where I can get it from there.
I finally found a way. Whenever I send a request from the server (in PageComponent.getInitialProps), I set the header in the context, then I can access it from setContext:
PageComponent.getInitialProps = async (ctx) => {
...
const token = getCookieFromServer(authCookieName, ctx.req)
const { data } = await client.query({
query,
context: { headers: { 'Auth-Token': token } }
})
...
}
Then in setContext:
const authLink = setContext((_, prevCtx) => {
let headers = prevCtx.headers || {}
if (!headers['Auth-Token']) {
const token = getCookieFromBrowser(authCookieName)
headers = { ...headers, 'Auth-Token': token }
}
return ({ headers })
})
So if the header is already present in the previous context (which is the case when sent from the server), just use it. If it is not present (when sent from the browser), get the cookie from the browser and set it.
I hope it will help somebody one day.
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.
When making the following fetch request on my front-end I'm getting my desired type and id values.
export const getUserProfile = () => {
return (
fetch(
"https://api.spotify.com/v1/me", {
headers: {"Authorization": "Bearer " + user_id}
})
.then(response => {
return response.json()
})
.then(data => {
console.log(data.type)
console.log(data.id)
})
)
}
Knowing you can't use fetch api in Node I used the npm install request package to get the data on my node server.
request.post(authOptions, function(error, response, body) {
var access_token = body.access_token
let postInfo = {
url: 'https://api.spotify.com/v1/me',
headers: {
"Authoriztion": "Bearer " + access_token
},
json: true
}
request.post(postInfo, function(error, response, body) {
const route = body.type
const current_user_id = body.id
console.log(body)
let uri = process.env.FRONTEND_URI || `http://localhost:3000/${route}/${current_user_id}`
res.redirect(uri + '?access_token=' + access_token)
})
})
The purpose of doing this is so when the res.redirect gets called it sends the client to the user's home page. However when the client gets redirected the url is http://localhost:3000/undefined/undefined?accesss_token={some token}
when looking why the values are undefined I console.log(body) and I get
{
error: {
status: 401,
message: 'No token provided'
}
}
but I can see when logging the response that the token is included
_header: 'POST /v1/me HTTP/1.1\r\nAuthoriztion: Bearer {some token}=\r\nhost: api.spotify.com\r\naccept: application/json\r\ncontent-length: 0\r\nConnection: close\r\n\r\n'
I can see why my values are undefined but why am I getting an unauthorized status in node but not on the client using fetch api? Also I noticed that the url access_token doesn't match the server logged token.
Here are the docs I'm using:
https://www.npmjs.com/package/request
https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/
Github file: https://github.com/ryansaam/litphum-server/blob/master/server.js
If you use node-fetch in your server code, you have a similar API as fetch.