I am building an angular application that supports JWT authentication. I build my JWT server using Spring Boot. A user provides his/her credentials for authentication. After a successful authentication, a response containing the access token and authentication will be forwarded to the user.
There is a exp value in my JWT access token containing the token lifespan. The refresh token is persisted in a DB.
To get a new access token, you have to sent a request with this header
--->>>X-Auth: refreshToken<<<--- to get a new access token.
I implemented a function that will resend a request for an access token using the refresh token.
I had tried implementing the function to run the function globally but to no avail. I wish to find out if there is a way that I could run the function once globally in my angular app.
/***Login sample request***/
{
"password": "secret123",
"username": "nkengbeza"
}
/***Login sample response***/
{
"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJua2VuZ2JlemEiLCJhdXRoIjpbeyJhdXRob3JpdHkiOiJBRE1JTiJ9XSwidG9rZW5fdHlwZSI6ImFjY2Vzc190b2tlbiIsImlhdCI6MTU0ODY2OTE0MSwiZXhwIjoxNTQ4NjcyNzQxfQ.oaWOIaeMqLg35unM82bcNg88ga030m5J1k7E5EM2O0s",
"refresh_token": "a102c7a063c7f149bcb276eadf83c0c61659ff9b37c0a8ab7d29e92c08ac94e7aa99abc5e9a3c1c59da4e66f1ada0c6a02dca2f577a4aa3f81885c73f5a879d1"
}
/***Request for an access token using a valid refresh token***/
reLogin() {
(async () => {
const resp = await fetch(AppRoutes.RELOAD_AUTH, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Auth': this.getAuth().refreshToken
}
});
if (resp.status !== 200) {
this.doLogout();
}
const content = await resp.json();
const accessToken = content.access_token;
const tokenInfo = this.getDecodedAccessToken(accessToken); // decode token
const auth = new Auth();
auth.refreshToken = content.refresh_token;
auth.role = tokenInfo.auth[0];
const expireDate = tokenInfo.exp; // get token expiration dateTime
this.setAccessToken(accessToken);
this.setAuth(auth);
})();
}
Related
I'm trying to get new access token from spotify by sending the refresh token to spotify token endpoints but it's returning this {error: 'invalid_grant', error_description: 'Invalid refresh token'}
this is my code:
const basic = Buffer.from(
`${import.meta.env.VITE_CLIENT_ID}:${import.meta.env.VITE_CLIENT_SECRET}`
).toString("base64");
const params = new URLSearchParams();
params.append("grant_type", "refresh_token");
params.append("refresh_token", import.meta.env.VITE_REFRESH_TOKEN);
const response = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
});
const result = await response.json();
return result;
It's suppose to return a new access token but it's returning error for some reasons i don't understand.
Note: I got the access token and refresh token from this website https://alecchen.dev/spotify-refresh-token/ after inputting my client id and client secret. If i use the access token directly to make a request to spotify api it works but i need to refresh it to get a new one but it's returning error
You needs to call this format in body of POST.
grant_type = refresh_token
refresh_token = <received refresh_token>
access_token= <received access_token>
The website https://alecchen.dev/spotify-refresh-token/ has a potential leak your credential.
I will shows getting refresh token in local and update refresh token.
Demo Code.
Save as get-token.js file.
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client id>"
CLIENT_SECRET = "<your client secret>"
REDIRECT_URI = '<your redirect URI>' // my case is 'http://localhost:3000/callback'
SCOPE = [
"user-read-email",
"playlist-read-collaborative"
]
app.get("/login", (request, response) => {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&client_id=${CLIENT_ID}&scope=${SCOPE}&state=123456&redirect_uri=${REDIRECT_URI}&prompt=consent`
response.redirect(redirect_url);
})
app.get("/callback", async (request, response) => {
const code = request.query["code"]
await axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: {
'grant_type': 'client_credentials'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
.then(resp1 => {
axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'refresh_token',
'refresh_token': resp1.data.refresh_token,
'access_token': resp1.data.access_token
}),
config = {
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
}
).then(resp2 => {
return response.send(JSON.stringify([resp1.data, resp2.data]));
})
});
})
// your port of REDIRECT_URI
app.listen(3000, () => {
console.log("Listening on :3000")
Install dependencies
npm install express axios cors
Run a local server and access it
node get-token.js
Open your browser and enter this address
http://localhost:3000/login
It will get code and both tokens then exchange the exchanged token.
It Will display both tokens and exchanged token in Browser.
Result
First red box is get access-token and refresh-token
Second red box is to grant the refresh-token
I'm trying to make a project that utilizes the Spotify web api, and am trying to implement a login/logout function using React and Express.
I'm trying to show a user's top songs only if the user is signed in - this means that there is a working access_token set in the cookies.
I'm a bit new when it comes to web development, so my understanding isn't super comprehensive, but this is my approach to doing this.
Check if there is an active access_token in cookies
If there is, proceed. If there isn't, send a post request to the backend /login route, which redirects the page to the spotify authorization, which eventually, places a token in the cookies.
When I do this, however, I get a CORS error saying that cross-origin redirection has been denied: origin null is not allowed by access-control-allow-origin. I ran the server in Firefox as well (instead of safari), and got a different error.(Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
I saw this post (Spotify API CORS error with React front-end and Node back-end) where the user kind of has the same problem I do, and was a bit confused by the response. They were told to not redirect responses to API calls. Am I supposed to do everything on the client-side?
Here is some of my server-side and client code.
app.post('/refresh', (req, res) => {
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS');
const refresh_token = req.body.headers.refresh_token;
const authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')),
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
const access_token = body.access_token;
res.cookie('access_token', access_token);
res.send({
'access_token': access_token
});
}
});
});
app.get('/login', (req, res) => {
console.log('logging in 3');
const scope = [
'ugc-image-upload',
'user-read-playback-state',
'user-modify-playback-state',
'user-read-currently-playing',
'streaming',
'app-remote-control',
'user-read-email',
'user-read-private',
'playlist-read-collaborative',
'playlist-modify-public',
'playlist-read-private',
'playlist-modify-private',
'user-library-modify',
'user-library-read',
'user-top-read',
'user-read-playback-position',
'user-read-recently-played',
'user-follow-read',
'user-follow-modify'
];
const state = Math.random().toString(36).slice(2,18);
const auth_query_parameters = new URLSearchParams({
response_type: "code",
client_id: client_id,
scope: scope,
redirect_uri: 'http://localhost:3001/access',
state: state
});
res.redirect('https://accounts.spotify.com/authorize?' +
auth_query_parameters.toString());
});
useEffect(() => {
console.log(Cookies.get('refresh_token'))
console.log('getting refresh token');
const refresh_token = Cookies.get('refresh_token');
setAccessToken(Cookies.get('access_token'));
axios.post('http://localhost:3001/login')
.then(res => {
console.log(res)
})
}, [])
I have a button where the logged user can download a file which is stored in the database and is fetched by react from node js/express js. Without authentication I can easily do that by just an tag. But with authentication I am struggling a lot.
React:
const handleDownload = async () => {
const result = await fetch (process.env.REACT_APP_BACKEND_URL + `/files/download/${props.id}`, {
headers: {'Authorization': auth.token}
});
const responseData = await result.json();
return responseData;
}
return (
<button onClick={handleDownload}>Download File</button>
)
Express js:
router.get('/download/:fid', filesControllers.downloadFile);
const downloadFile = async (req, res, next) => {
const fileId = req.params.fid;
let filePost;
try {
filePost = await File.findById(fileId);
} catch (err) {
return next(new HttpError("Error", 500));
}
console.log(filePost.file);
res.download(filePost.file);
};
possible solutions:
verify that your auth.token really contains the token (try a
console.log for example)
if you're using a bearer token do this: {'Authorization': "Bearer
"+auth.token}
from backend side you will need to do some changes learn jwt authentication or anyother authentication technique for backend and make sure that it works with frontend
how this app will work is when user is not logged in and he clicks on download backend verifies the token and if token is invalid or doesnt exist it send the error through resopnse and frontend shows it on ui
I'm trying to use ably.io with Angular and Azure Functions using the JWT way of authenticating since it's secure, but I'm having issues with configuring the angular side of it. The use case is for a live auction site to update bids in realtime. There isn't a specific angular tutorial for this so I'm trying to piece it together. Also this code
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
never throws the alert so I don't think it's working right. I used to have it working where I wasn't using ably JWT, but had the API key on the client-side in a component like this
let api = "<api key>";
let options: Ably.Types.ClientOptions = { key: api };
let client = new Ably.Realtime(options); /* inferred type Ably.Realtime */
let channel = client.channels.get(
"auctions"
);
and I could subscribe to that channel and update auctions accordingly by their id inside ngOnInit()
channel.subscribe(message => {
const auction = this.posts.find(action => {
return action.id === message.data.auctionId;
});
if (auction) {
auction.currentBid = message.data.lastBid;
}
});
but I need to switch this logic for JWT and somehow feed that JWT token into different components as well.
Ably.io JWT tutorial reference
I put the following in my angular login service
login(email: string, password: string) {
const authData: AuthDataLogin = { email: email, password: password };
return this.http
.post<{
token: string;
expiresIn: number;
userId: string;
}>(environment.azure_function_url + "/POST-Login", authData)
.pipe(takeUntil(this.destroy)).subscribe(response => {
//JWT login token. Not Ably JWT Token
const token = response.token;
this.token = token;
if (token) {
console.log('Fetching JWT token from auth server')
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
realtime.connection.once('connected', function () {
console.log('Client connected to Ably using JWT')
alert("Client successfully connected Ably using JWT auth")
});
...
}
With my azure function already configured, When I login, the browser console outputs
GET wss://realtime.ably.io/?access_token=<token was here>&format=json&heartbeats=true&v=1.1&lib=js-web-1.1.22
SO it returns my token, but
the alert never happens
I'm not sure how to grab that JWT token that's returned to the browser. I was thinking I could store it in localStorage to share between components and clear out localStorage when user logs out, but I need to be able to subscribe to response and assign the token to a variable, but I didn't see in ably javascript tutorial how to get variable assigned to JWT Token response since it's being called with this syntax.
I appreciate any help with this!
var realtime = new Ably.Realtime({
authUrl: "http://localhost:7071/api/AblyAuth"
});
My azure function looks like
const checkAuth = require('../middleware/check-auth');
var jwt = require("jsonwebtoken")
var appId = '<APP ID>'
var keyId = '<key ID>'
var keySecret = '<key secret>'
var ttlSeconds = 60
var jwtPayload =
{
'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe'] })
}
var jwtOptions =
{
expiresIn: ttlSeconds,
keyid: `${appId}.${keyId}`
}
console.log("JwtPayload");
console.log(jwtPayload);
console.log("jwtOptions");
console.log(jwtOptions);
module.exports = function (context, req) {
console.log("INSIDE ABLY AUTH")
// checkAuth(context, req);
console.log('Sucessfully connected to the server auth endpoint')
jwt.sign(jwtPayload, keySecret, jwtOptions, function (err, tokenId) {
if (err) {
console.log("ERR")
console.log(err)
console.trace()
return
}
context.res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate')
context.res.setHeader('Content-Type', 'application/json')
console.log('Sending signed JWT token back to client')
console.log(tokenId)
context.res = {
status: 200,
body: JSON.stringify(tokenId),
headers: {
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Set-Cookie",
"Access-Control-Max-Age": "86400",
"Vary": "Accept-Encoding, Origin",
"Content-Type": "application/json"
}
};
context.done();
})
}
I'd recommend if you're wanting to intercept the JWT prior to passing it to Ably (so as to verify the contents, and also use the JWT for other components), you make use of authCallback instead of authUrl. You can use a function instead of a direct URL, within which you can call the endpoint, and do anything you like with the response, prior to passing the JWT back to the Ably constructor. I've made a JavaScript example of using the authCallback for normal Token Authentication, but the same principle applies.
As to why you're not seeing the alert, it looks like you're sending an invalid JWT for what Ably is expecting, and thus you're not successfully connecting to Ably. For example, you're specifying 'expiresIn' rather than 'exp'. For a token to be considered valid, it expected certain elements in a very specific structure, see the documentation. I'd recommend for this sort of situation where you're not certain what's breaking that you make use of verbose logging, which you can enable in the connection constructor as "log": 4.
I've been struggling to do this for about 6 days...
Everything is working perfectly such as authorization but one problem I had is making authentication.
On my user model (for creating the database schema) I do have a way to generate a token for logged in users or registered.
userSchema.methods.generateAuthToken = function(){
const token = jwt.sign({ _id: this._id }, config.get('jwtPrivateKey'));
return token;
}
So when user post to /login, server will respond with a token:
router.post('/', async (req, res) =>{
// Here i'm validating data and then if everything is right the code under will run.
console.log('logged in as: ' + user.username);
// Here i'm using the function to generateAuthToken().
const token = user.generateAuthToken();
console.log("Token from server: " + token);
// now here is my main problem i would like to use cookies to store it for an hour or so.
// then client can send it back to server for protected route.
res.status(200).send(token);
});
I have made a middleware function for auth (to check the token if you're going through a protected route)
module.exports = function (req, res, next){
// instead of using headers i would like to check for the cookie value if it's the token,
// pass the user in, else Access denied.
// I have no idea how to use cookie parser with middleware functions.
const token = req.header('x-auth-token');
if(!token) return res.status(401).send('Access denied. Sign in or register.');
try{
const decoded = jwt.verify(token, config.get('jwtPrivateKey'));
req.user = decoded;
next();
}
catch(err){
res.status(400).send('Invalid Token!');
}
}
here i'm using the auth middleware function:
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
// but it's actually not passing the user in since i haven't done it with cookies.
router.get('/', auth, (req, res) =>{
res.render('index', {});
});
I do know I can do it with localStorage but it's a terrible practice and it would be better to store it on cookies so no one could hack on.
Is there any good approach to solve this problem? I'm kinda lost and lost hope to go back to sessionID (which I don't want to :( ).
After you request on frontend, you need get the response (token) and save on browser using this for example:
fetch('http://your-api-host/login', {
method: 'POST',
body: {
username: "user1",
password: "passworduser"
}
})
.then((res) => res.text((res)))
.then((token) => {
document.cookie = `AUTH_API=${token}`; <-- this save the cookie
})
With this value saved on frontend you need send this information on all requests, it's commum send this value on your HEADER (how you makes), to save on header you need read the value from token and put on header, like this:
const headersTemp = document.cookie.split(';'); // <-- this get all cookies saves and splits them in the array.
const finalHeaders = {};
headersTemp.forEach((header) => { // <-- looping on all cookies
const headerTemp = header.split('='); // <-- split each cookie to get key and value
finalHeaders[headerTemp[0].trim()] = headerTemp[1].trim() // <-- save on object to access using keys.
})
Now you can access all cookies using the key (the same used before), I used the key AUTH_API to save my cookie, let's send the request using fetch api:
fetch('http://your-api-host/route-protected', {
method: 'POST',
headers: {
'x-auth-token': finalHeaders['AUTH_API']
},
})
If you creating your application using libraries how React or any SPA framework, probably you will use tools like Axios, and I recommend uses libraris how This, it's more easy to work with cookies.