I have some code in TypeScript to process Spotify's login flow, it looks like this:
import * as React from 'react';
import '#patternfly/react-core/dist/styles/base.css';
import { useNavigate } from "react-router-dom";
import env from "ts-react-dotenv";
interface Props {
token: string;
}
const Callback: React.FC<Props> = (props: Props) => {
const { token } = props;
const searchParams = new URLSearchParams(token);
const code = searchParams.get('code');
var accessToken = '';
// Create the POST request to get the access token
const data = {
grant_type: 'authorization_code',
code: code,
redirect_uri: 'http://localhost:3000/callback',
};
const body = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
if (value){
body.append(key, value);
}
}
const request = new Request('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + btoa(env.CLIENT_ID + ':' + env.CLIENT_SECRET),
},
body: body,
});
// Send the POST request
fetch(request)
.then((response) => response.json())
.then((data) => {
console.log("Access token: " + data.access_token);
if (accessToken){
accessToken = data.access_token;
}
});
// Navigate to Home with the code
// https://github.com/react-navigation/react-navigation/issues/10803
// Redirect to Home
const navigate = useNavigate();
setTimeout(() => navigate('/home', { state: { accessToken } }), 50);
return <div>Loading... if the app doesn't work please reload</div>;
};
export default Callback;
I see that in the console there are 2 calls, the first one produces the correct result while there is somehow a second call that seems like its being made. I suspect that my post request somehow triggered another call within the callback field but I am not sure how that happened.
How can I fix this, set the correct access_token and go to the next page?
You need to put the fetch call inside a useEffect hook, with dependency array as empty, Please refer useEffect
When we run the code on local server there component will be render at least 2 time, since CRA will create the code with strict mode.
import * as React from "react";
import "#patternfly/react-core/dist/styles/base.css";
import { useNavigate } from "react-router-dom";
import env from "ts-react-dotenv";
interface Props {
token: string;
}
const Callback: React.FC<Props> = (props: Props) => {
const { token } = props;
const searchParams = new URLSearchParams(token);
const code = searchParams.get("code");
var accessToken = "";
// Create the POST request to get the access token
const data = {
grant_type: "authorization_code",
code: code,
redirect_uri: "http://localhost:3000/callback",
};
const body = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
if (value) {
body.append(key, value);
}
}
const request = new Request("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Basic " + btoa(env.CLIENT_ID + ":" + env.CLIENT_SECRET),
},
body: body,
});
// Send the POST request
useEffect(() => {
fetch(request)
.then((response) => response.json())
.then((data) => {
console.log("Access token: " + data.access_token);
if (accessToken) {
accessToken = data.access_token;
}
});
}, []);
// Navigate to Home with the code
// https://github.com/react-navigation/react-navigation/issues/10803
// Redirect to Home
const navigate = useNavigate();
setTimeout(() => navigate("/home", { state: { accessToken } }), 50);
return <div>Loading... if the app doesn't work please reload</div>;
};
export default Callback;
Related
I'm trying to use HERE's RouteMatching API of JavaScript
Current full code is here: https://github.com/code4history/ShibeContour/blob/d7e56a7/here_mapmatcher.js
For authentication, I coded like this:
import properties from "properties"
import hmacSHA256 from 'crypto-js/hmac-sha256.js'
import Base64 from 'crypto-js/enc-base64.js'
import fetch from 'node-fetch'
import {promises as fs} from "node:fs"
const getProps = async () => {
return new Promise((res) => {
properties.parse("./credentials.properties", {path: true}, function (error, data) {
res(data)
})
})
}
const getToken = async (props) => {
const nonce = `${performance.now()}`
const timestamp = Math.floor((new Date()).getTime() / 1000)
const parameters = [
"grant_type=client_credentials",
`oauth_consumer_key=${props["here.access.key.id"]}`,
`oauth_nonce=${nonce}`,
"oauth_signature_method=HMAC-SHA256",
`oauth_timestamp=${timestamp}`,
"oauth_version=1.0"
].join("&")
const encoding_params = encodeURIComponent(parameters)
const base_string = `POST&${encodeURIComponent(props["here.token.endpoint.url"])}&${encoding_params}`
console.log(base_string)
const signing_key = `${props["here.access.key.secret"]}&`
const hmac_digest = encodeURIComponent(Base64.stringify(hmacSHA256(base_string, signing_key)))
const headers = {
"Authorization": `OAuth oauth_consumer_key="${props["here.access.key.id"]}",oauth_nonce="${nonce}",oauth_signature="${hmac_digest}",oauth_signature_method="HMAC-SHA256",oauth_timestamp="${timestamp}",oauth_version="1.0"`,
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded"
}
const body = `grant_type=client_credentials`
const response = await fetch(props["here.token.endpoint.url"], {
method: 'post',
body,
headers
})
return response.json()
}
This works well, I got authentication token successfully.
Like this:
{
access_token: 'eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiJIZ0NSaFV4...',
token_type: 'bearer',
expires_in: 86399,
scope: 'hrn:here:authorization::org...'
}
But even I used this access_token, route matching call causes authentication error.
Code is:
const main = async () => {
const props = await getProps()
const token_data = await getToken(props)
const body = await fs.readFile("gps/8DD83AC3-8B5A-4108-9CC0-2B78CF9936EC.kml", {encoding: "UTF-8"})
const headers = {
"Authorization": `Bearer ${token_data.access_token}`,
"Cache-Control": "no-cache",
"Content-Type": "application/octet-stream"
}
const response = await fetch(`https://routematching.hereapi.com/v8/calculateroute.json?routeMatch=1&mode=fastest;car;traffic:disabled&apiKey=${props["here.access.key.id"]}`, {
method: 'post',
body,
headers
})
const respond = await response.json()
console.log(respond)
}
main()
Error response was like this:
{
error: 'Forbidden',
error_description: 'These credentials do not authorize access'
}
What is wrong?
I can't imagine what is wrong.
Finally I found the reason
API URL is not match.
We can find many candidate urls,
https://fleet.api.here.com/2/calculateroute.json
https://routematching.hereapi.com/v8/calculateroute.json
etc...
but true working url is only
https://routematching.hereapi.com/v8/match/routelinks
which we can find in this document.
https://platform.here.com/services/details/hrn:here:service::olp-here:route-matching-8/api-ref
Once I changed API endpoint to this correct one, it works well.
Whenever I am trying to invoke the "btoa" method, I am not able to use this within my script. I created a variable to store the client id: client_secret in base64. The id and secrets are being retrieved from the ".env" file.
I have also tried to use the Buffer method, but unable to use this as well. I am getting the error "invalid from" in Buffer.
can someone help me?
Please look at the full code,
const client_id = process.env.SPOTIFY_CLIENT_ID;
const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
const refresh_token = process.env.SPOTIFY_REFRESH_TOKEN;
const basic = btoa(`${client_id}:${client_secret}`);
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`;
const TOP_TRACKS_ENDPOINT = `https://api.spotify.com/v1/me/top/tracks`;
const TOKEN_ENDPOINT = `https://accounts.spotify.com/api/token`;
const getAccessToken = async () => {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token
})
});
return response.json();
};
export const getNowPlaying = async () => {
const { access_token } = await getAccessToken();
return fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`
}
});
};
export const getTopTracks = async () => {
const { access_token } = await getAccessToken();
return fetch(TOP_TRACKS_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`
}
});
};
Using the above script I am trying to embed the customized Spotify play on my site. This wrapper is intended to display the top track as well.
Also, whenever I am trying to run the wrapper used to display the top tracks, it displays the following error,
Full code for displaying the top tracks:
import { type NextRequest } from 'next/server';
import { getTopTracks } from 'lib/spotify';
export const config = {
runtime: 'experimental-edge'
};
export default async function handler(req: NextRequest) {
const response = await getTopTracks();
const { items } = await response.json();
const tracks = items.slice(0, 10).map((track) => ({
artist: track.artists.map((_artist) => _artist.name).join(', '),
songUrl: track.external_urls.spotify,
title: track.name
}));
return new Response(JSON.stringify({ tracks }), {
status: 200,
headers: {
'content-type': 'application/json',
'cache-control': 'public, s-maxage=86400, stale-while-revalidate=43200'
}
});
}
The problem is that you misspelled the Bytes to ASCII function, it is btoa, not btao.
If you are looking to do it the other way around, spell it atob.
I have all my functions based views on django protected with #permission_classes([IsAuthenticated]) so I have to send a JWT as Bearer token on every request.
In the first version I was using this code:
import axios from 'axios';
import { decodeUserJWT } from '../../extras'
const user = JSON.parse(localStorage.getItem("user"));
var decoded = decodeUserJWT(user.access);
var user_id = decoded.user_id
const instance = axios.create({
baseURL: 'http://localhost:8000/api',
headers: {Authorization: 'Bearer ' + user.access},
params: {userAuth: user_id}
});
export default instance;
Everything was working fine.
But then I added interceptors so I could handle the refreshToken process:
const setup = (store) => {
axiosInstance.interceptors.request.use(
(config) => {
const token = TokenService.getLocalAccessToken();
if (token) {
// const uid = await decodeUserJWT(token);
config.headers["Authorization"] = 'Bearer ' + token;
// config.headers["userAuth"] = uid;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
const { dispatch } = store;
axiosInstance.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/token/obtain/" && err.response) {
console.log("TOKEN INTERCEPTOR");
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
const rs = await axiosInstance.post("/auth/token/refresh/", {
refresh: TokenService.getLocalRefreshToken(),
});
const { access } = rs.data;
dispatch(refreshToken(access));
TokenService.updateLocalAccessToken(access);
return axiosInstance(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
};
What happens?
When I add the line config.headers["userAuth"] = uid; the django server console starts showing up that when the react app tries to access the routes it gets a Not Authorized, and when I take that line off de code ... it works fine.
I also tried to pass the param userAuth in the axios.create and keep only the Bearer config inside the interpector code, but still no positive result, the code with the interpector code only works when I take off the userAuth line from axios.
Any ideia on why this is happening and how can I fix this?
I have a strange situation with a React Native app.
The part of the code with problems is this:
const request = async (options) => {
const defaults = {baseURL: 'base URL'}
let token = await AsyncStorage.getItem('token');
console.log('this is logged')
if(token) {
console.log("this is logged")
const headers = {
'TokenAuth': token
}
Object.assign(defaults, headers: headers);
}
console.log('this is NOT logged anymore')
options = Object.assign({}, defaults, options);
};
The idea is that i can't see anywhere the javascript error.
The error is on Object.assign(defaults, headers);
Why i can't see it ?
Thank you.
This is the whole component:
import constants from './../constants/constants';
import axios from 'axios';
import { AsyncStorage } from 'react-native';
import * as Utils from '../configs/utils'
const request = async (options) => {
const defaults = {baseURL: constants.BASE_URL}
let token = await AsyncStorage.getItem('token');
if(token) {
const headers = {'TokenAuth': token}
Object.assign(defaults, {headers: headers});
}
options = Object.assign({}, defaults, options);
return axios(options)
.then(response => {
return response.data
} )
.catch( error => {
if (error.response.status == 401) {
Utils.deleteToken();
}
let errResponse = 'Bad Error'
throw errResponse;
});
};
export function getAllTodos() {
return request({method: 'get',
baseURL: constants.BASE_URL,
url: '/api/items',
})
}
this is a very weird problem! I'm trying to build a login form which sets a JWT token in localstorage. Other forms then use that token to post requests. I can see the token in my console.log just fine, but sometimes (like 3 out of 5 times), when I am setting localstorage.getitem('idToken'), it shows as null. This behavior most noticeably happens when I remove the console.log(idToken) from my loginUser() function (code in actions.js file - given below). What am I doing wrong? my app is built using React/Redux.
action.js
export function loginUser(creds) {
const data = querystring.stringify({_username: creds.username, _password: creds.password});
let config = {
method: 'POST',
headers: { 'Content-Type':'application/x-www-form-urlencoded' },
body: data
};
return dispatch => {
// We dispatch requestLogin to kickoff the call to the API
dispatch(requestLogin(creds));
return fetch(BASE_URL+'login_check', config)
.then(response =>
response.json().then(user => ({ user, response }))
).then(({ user, response }) => {
if (!response.ok) {
// If there was a problem, we want to
// dispatch the error condition
dispatch(loginError(user.message));
return Promise.reject(user)
} else {
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken')
console.log(token);
// if I remove this log, my token is returned as null during post.
dispatch(receiveLogin(user));
}
}).catch(err => console.log("Error: ", err))
}
}
here's my POST request:
import axios from 'axios';
import {BASE_URL} from './middleware/api';
import {reset} from 'redux-form';
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr }
};
export default (async function showResults(values, dispatch) {
console.log(AuthStr);
axios.post(BASE_URL + 'human/new', values, headers)
.then(function (response) {
console.log(response);
alert("Your submit was successful");
//dispatch(reset('wizard'));
}).catch(function (error) {
console.log(error.response);
alert(error.response.statusText);
});
});
This GET request works everytime, BTW:
getHouses = (e) => {
let token = localStorage.getItem('idToken') || null;
const AuthStr = 'Bearer '.concat(token);
axios.get(BASE_URL + 'household/list', { headers: { Authorization: AuthStr } }).then((response) =>
{
let myData = response.data;
let list = [];
let key =[];
for (let i = 0; i < myData._embedded.length; i++) {
let embedded = myData._embedded[i];
list.push(embedded.friendlyName);
key.push(embedded.id);
}
this.setState({data: list, key: key});
})
.catch((error) => {
console.log('error' + error);
});
}
I'm at my wit's end! Please help!
The localStorage.setItem() is a asynchronous task, and sometimes you run let token = localStorage.getItem('idToken') just after the setItem will fail, so you get a null, so please put the getItem operation some later, have a try, it will be different :
setTimeout(function() {
let token = localStorage.getItem('idToken');
dispatch(receiveLogin(user));
}, 50);
Move your token logic (i.e. localStorage.getItem('idToken');) inside the exported function and it should work
export default (async function showResults(values, dispatch) {
let token = localStorage.getItem('idToken');
const AuthStr = 'Bearer '.concat(token);
let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr
}
};
axios.post(BASE_URL + 'human/new', values, headers)...
There can't be a case where you set a key value in localstorage and then it returns you null, immediately in the next line.
localStorage.setItem('idToken', user.token);
let token = localStorage.getItem('idToken');
This will only happen if your user.token value is null.
Maybe the case here is your thennable function not returning value to your next then like this:
....
.then(response =>
// return response to your next then function
// this will be passed to next then function as params
return response.json();
).then(({ user, response }) => {
....
Make a function whose return the value or a default value
const [hideTyC, setHideTyC] = useState(false);
const loadTyCFlag = (): any => {
if (
localStorage.getItem("tyc") !== null ||
localStorage.getItem("tyc") !== undefined
) {
return localStorage.getItem("tyc") || false;
}
};
useIonViewDidEnter(() => {
hideTabBar();
setHideTyC(loadTyCFlag());
});