Fetch API using JWT authorization - api

My EdX API access request has been approved. As per the documentation, I have generated the client id and client secret on the admin website. Using fetch, I have got the token from the https://api.edx.org/oauth2/v1/access_token URL by passing the client id and client secret. The token is a valid one I can see in my console. But when I pass this token as below, I get a 403 (Forbidden) error:
fetch("https://api.edx.org/catalog/v1/catalogs", {
method:"GET",
headers: {
"Authorization": "JWT " + token,
"Access-Control-Allow-Origin": "http://localhost:3000"
}
})
.then( r => r.json() )
.then( res => console.log("SUCCESS " + res.count) )
.catch( err => console.log("ERROR " + err) );
I have tried all the variations of the request like credentials:true, all the "Allow-control" headers etc, but this error persists. Upon using the dev tools on chrome, the "Network" tab shows a completely different Fetch is used which is:
fetch("https://api.edx.org/catalog/v1/catalogs", {
"credentials":"omit",
"referrer":"http://localhost:3000/",
"referrerPolicy":"no-referrer-when-downgrade",
"body":null,
"method":"GET",
"mode":"cors"
});
The equivalent curl works at the command line and through Insomnia, and gets the data perfectly.

Without knowing the ins and outs of the specific system to which you are connecting, I cannot be authoritative, but most systems seem to use not JWT but Bearer as the prefix to the authorization bearer token in the Authorization header:
const res = await fetch("https://api.edx.org/catalog/v1/catalogs", {
headers: {
"Authorization": "JWT " + token
}
});
const json = await res.json();

Related

refresh JWT token when expire time is reached

I'm working on a React native application and I need to set Authentication header on my sensitive requests.
But my accessToken (JWT) expires after 10 seconds. So before any request I have to check that if the token is expired renew it using a refreshToken and then call last request again.
I'm able to do all of these except last part (bold text).
And I'm using Axios for sending request to server.
Any idea?
So the idea is to use a response interceptor that works after the response is available, but before it is passed down in the code.
It looks for unauthenticated error, which corresponds to statusCode 401
Keep in mind that this is a pseudo-code and You have to modify some parts like
auth.setToken().
const createAxiosResponseInterceptor = () => {
const interceptor = axios.interceptors.response.use(
(response) => response,
(error) => {
// Reject promise if not 401 error
if (error.response.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Remove the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*
* Also eject the interceptor to prevent it from working again if the REFRESH request returns 401 too
*/
axios.interceptors.response.eject(interceptor);
return axios({
url: API_REFRESH_URL,
method: "POST",
withCredentials: true,
})
.then((response) => {
auth.setToken(response.data.access_token);
error.response.config.headers["Authorization"] =
"Bearer " + response.data.access_token;
return axios(error.response.config);
})
.catch((error) => {
auth.removeToken();
return Promise.reject(error);
})
.finally(createAxiosResponseInterceptor);
}
);
};
createAxiosResponseInterceptor();
error.response.config contains all the data about the old request so we can repeat it. Keep in mind that after completing the Refresh request we again apply the interceptor in .finally(create...Interceptor)
For more details please see this question, and official Docs here

ERR_ABORTED 403 (Forbidden)

I'm trying to build a program which can control my Sonos Speaker. I'm following the instructions over at https://developer.sonos.com/build/direct-control/authorize/.
The first step - getting the authorization code - is working as intended but the problem I'm facing arises when I try to send the authorization code per POST request with the following code:
const redirect_uri = "https%3A%2F%2Fsonoscontrol-c4af4.web.app%2F";
const redirect_url = "https://sonoscontrol-c4af4.web.app/";
const client_id = // API Key (deleted value for safety)
const secret = // Secret (deleted value for safety)
const auth_url = `https://api.sonos.com/login/v3/oauth?client_id=${client_id}&response_type=code&state=testState&scope=playback-control-all&redirect_uri=${redirect_uri}`;
function GetAccessToken() {
var target_url = "https://api.sonos.com/login/v3/oauth/access/";
var authCode = GetAuthCode();
var encoded_msg = btoa(client_id + ":" + secret); // base64-encodes client_id and secret using semicolon as delimiter
var params = "grant_type=authorization_code" + `&code=${authCode}` + `&redirect_uri=${redirect_uri}` + `&client_id=${client_id}`;
var myHeaders = new Headers({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Authentication': `Basic {${encoded_msg}}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
});
fetch(target_url, {
method: "POST",
mode: "no-cors",
credentials: "include",
redirect: "follow",
headers: myHeaders,
body: params
}).then((res) => {
console.log(res.responseText);
}).catch(function(error) {
console.log(error);
});
}
function GetAuthCode() {
return (new URLSearchParams(location.search)).get('code'); // returns authorization code from URL
}
Now I get the following error when trying to send the POST request: POST https://api.sonos.com/login/v3/oauth/access/ net::ERR_ABORTED 403 (Forbidden)
I am using a Cloud Firebase app as webserver and added the correct redirect URL in the credentials.
What could be the problem for this error message?
I noticed a couple of things in your code that may be causing your 403 Forbidden issue.
Your "Authentication" header should be "Authorization", eg: "Authorization: Basic {YourBase64EncodedClientId:SecretGoesHere}"
Your "target_url" has a trailing slash, it should be "https://api.sonos.com/login/v3/oauth/access"
Your query parameters "params" are including the client_id on the token request, which isn't necessary, though I don't believe it will cause an error.
Addressing the above should hopefully resolve your issue!
Thanks,
-Mark

How to send JWT header token and client data in POST request using axios

I want to send a JWT token to express server with axios POST method.
What I have tried is:
let data = data
let head = {header: { Token: localStorage.getItem("token") }}
axios
.post("http://localhost:3003/api/v/helllo", data, head)
.then((result) => {
console.table(result);
})
.catch((err) => {
console.error(err);
});
Usually, when working with JWT - Authorization header is used. Also pay attention that instead of header - headers field should be used:
let head = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem("token")
}
};
Beware, that storing tokens in local storage is not secure.

Excessive number of api calls for refresh token

After reading through multiple JWT refresh token tutorials I am still unclear on how the flow of API calls is supposed to work.
Here is my understanding:
1) Client is in posession of access token and refresh token. It submits the access token to api/getCustomerData.
2) Let's say the access token is expired. Server responds with a 401.
3) Client responds to the 401 request with the refresh token to api/token.
4) Server responds with new access token since the refresh token is still valid.
5) Client then makes a new request to api/getCustomerData with the new access token.
My impression is that this is an excessive number of API calls, but I am not seeing any tutorial that clarifies a way to do this more efficiently. As it stands it seems like if I am following this pattern, each API request will look like this:
const getCustomers = async () => {
const config = {
data: body,
withCredentials: true,
method: 'POST' as 'POST',
}
await axios(address + '/api/getCustomerData', config)
.then((response) => {
...
})
.catch((error: any) => {
const response = error.response;
if (response) {
if (response.status === 401) {
if (!failcount){
failcount++;
getCustomers();
}
else {
history.push('/login')
}
}
}
})
}
What you can do is pre-emptively get a new access token using a refresh token shortly before you know the current access token is about to expire, using the token's "exp (expiration time)" claim. That at least removes one API call - the call with the access token that causes a 401.

Accessing the response from one GET request within another

I'm working with Vue to interact with an external API on a Drupal website, but in order to do so dynamically per Drupal user, I need to get a token from Drupal first. To do so, I'm trying to do two GET requests. The first gets me a bearer token out of Drupal, and the second uses it to authenticate the third-party API request.
Below is what I'm trying – I'm able to get the token successfully in the first request's response, but not when I try to use it in the header of my second request. If I try hardcoding the token that I see in the console log, it does work, so I know none of that is the issue. It's just that this.jwt['data']['token'] in the second request's headers seems to not pull back the token.
What do I need to adjust in order to access the token from the first response as part of the headers of my second request?
created() {
axios
.get('/jwt/token')
.then(response => {
this.jwt = response
console.log(this.jwt['data']['token']) // this does show what I want to access later
})
},
mounted() {
axios
.get('/comment/doc/' + this.id, {
headers: { Authorization: "Bearer " + this.jwt['data']['token'] } // ...but this doesn't work
})
.then(response => {
this.comments = response
})
},
It's likely the response to the token request has not finished by the time the component mounts, at which point this.jwt is not yet assigned.
I would move the token request into the mounted hook, fetching comments only when the token request succeeds:
export default {
mounted() {
axios
.get('/jwt/token')
.then(tokenResp => {
this.jwt = tokenResp
axios
.get('/comment/doc/' + this.id, {
headers: { Authorization: 'Bearer ' + this.jwt['data']['token'] }
})
.then(commentsResp => {
this.comments = commentsResp
})
})
}
}