JWT Authorization with Axios and Vue.js (Header) - javascript

I'm still pretty new to web development, so I apologize in advance if the solution is obvious or my question is asked poorly.
So: I would like to use JWT to authenticate my users. I use axios, vue.js and of course JWT. I would like to access a secure route:
router.post('/secureroute', checkAuth, (req, res) => {
res.status(200).json({
message: 'all ok'
})
});
In order to do so, I use this check-auth.js:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(" ")[1];
console.log(token);
const decoded = jwt.verify(token, process.env.SECRET_KEY);
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed'
})
}
next();
}
part of my Login.vue:
methods: {
login() {
if (!this.username) this.alertUsername = true;
if (!this.password) this.alertPassword = true;
axios
.post("/user/login", {
username: this.username,
password: this.password
})
.then(res => {
localStorage.setItem("usertoken", res.data.token);
if (res.data.token) {
console.log("Success");
router.push({ name: "start" });
} else {
this.alertWrong = true;
}
this.username = "";
this.password = "";
})
.catch(err => {
console.log(err);
});
this.emitMethod();
}
Using postman with an authorization header, everything seems to work fine. But after hours of searching the Internet and trying things out, I simply do not know how to make it work with the real website. I would like to pass the JWT as an authorization-header. I know that it is possible with axios, but I really don't know how I can do so in my example here.

You've got your login flow, and you are storing the usertoken in localStorage as the usertoken key. You also verified that your requests are processed correctly if the authorization header is set.
The easiest way to work with api requests is by abstracting axios a bit more, to automatically add the authorization token, and maybe pre-process the response you get back. For example, you may want to handle some errors globally instead of on a case-by-case basis, or want to transform the request into something that is always the same.
You first want to make some abstraction that calls axios.request. You can pass it a configuration object as described here. What's most important for you right now is the headers key-value pair, but you may want to expand this in the future.
export default request (config) {
const userToken = window.localStorage.getItem('usertoken');
const requestConfig = { ...config };
if (!requestConfig.headers) {
requestConfig.headers = {};
}
if (userToken) {
requestConfig.headers.authorization = `Bearer ${userToken}`;
}
return axios.request(requestConfig);
}
Now we can expand on that:
export default post (url, data = {}, config = {}) {
return request({
...config,
method: 'POST'
url,
data
});
}
When inspecting the request in your developer console you should now see that, if the user token is correctly set in localStorage, you have an extra header in your request that is sent to the server.

Related

Getting error on console despite handling it (MERN + Redux)

I am sending axios get request whose end-point sends the user associated with the token stored in localStorage and then the redux state is updated with the user. When I don't have a token the end-point return a res with status 401 with message "Unauthorized" and then I handle it in the catch statement and set the "error" redux state. But even after doing this the error is displayed on the console like this:
Failed to load resource: the server responded with a status of 401 (Unauthorized) /users/auth:1
This is the function which makes api call and authorizes the user:
export function loadUser(){
return function (dispatch,getState){
dispatch(userLoading());
const token = getState().auth.token;
const config = {
headers:{
'Content-Type':'application/json'
}
}
if(token) config.headers['auth-token']=token;
axios.get('http://localhost:80/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
}
This is the middleware which handles the token before hitting the endpoint (In my case response is returned from here itself since there is no token sent):
function auth(req,res,next){
const token = req.header('auth-token');
if(!token) res.status(401).json({msg:"Unauthorized"})
else{
try{
const decoded = jwt.verify(token,jwt_secret);
req.user = decoded;
next();
}
catch(e){
res.status(400).json({msg:"Invalid token"})
}
}
}
I'm not able to figure out why am I getting error on console (State is getting updated as desired)
It is actually impossible to do with JavaScript. because of security concerns and a potential for a script to hide its activity from the user.
The best you can do is clearing them from your console.
console.clear();
I think it is because you are not getting the token when consulting your API.
If this is the case I recommend you use defaults.headers.common in this way
const axiosApi = axios.create({ baseURL: "http://localhost:80" });
const headerAuth = () => {
const token = getMyToken();
if (token) {
axiosApi.defaults.headers.common["Authorization"] = `Bearer ${token}`;
} else {
delete axiosApi.defaults.headers.common.Authorization;
}
};
export function loadUser(){
headerAuth(); // <-----
return function (dispatch,getState){
dispatch(userLoading());
axiosApi.get('/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
I recommend that you do not store the token in the REDUX but in sessionStorage

Error in Apollo Server deploy with AWS Lambda

People, how are you? I have a query, I just implemented my API made with apollo server in an AWS Lambda. I used the official documentation as a guide, but I'm noticing that the context handling varies a bit. I have a doubt with the latter, since I made certain changes and everything works fine locally using "serverless offline", but once I deploy it doesn't. Apparently the authentication context that I generate does not finish reaching my query. If someone can guide me a bit with this, I will be very grateful.
This is my API index:
const { ApolloServer, gql } = require('apollo-server-lambda');
const typeDefs = require('./db/schema');
const resolvers = require('./db/resolvers');
const db = require('./config/db');
const jwt = require('jsonwebtoken');
require('dotenv').config({ path: 'variables.env' });
db.conectDB();
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: "/graphql"
},
context: ({ event, context }) => {
try {
const token = event.headers['authorization'] || '';
if(token){
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);
}
return {
headers: event.headers,
functionName: context.functionName,
event,
context,
}
} catch (error) {
console.error(error);
}
}
});
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
This is my query:
getUserByToken: async (_, {}, { context }) => {
if(context)
throw new Error((context ? 'context' : '') + ' ' + (context.user ? 'user' : ''));
let user = await db.findOne('users',{ _id: ObjectId(context.user._id) });
if(user.birthdate)
user.birthdate = user.birthdate.toString();
if(user.password)
user.password = true;
else
user.password = false;
return user;
}
My API response:
API response
From what I can see, you're not calling getUserByToken in your context. Is that correct? So, I'm not sure how you're encountering this error.
Can I give you some pointers?
Connecting to your DB is probably (or it should be) asynchronous. For that, I'd run your code like this:
db.connect()
.then(() => {
... handle your request in here
})
.catch(console.error);
I think you meant to call your getUserByToken in this line:
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);

how to troubleshoot using the node.js GOT http request library?

I have some code using GOT querying a graphQL endpoint:
// set up params for call to weather cache
const queryQL = `
query weather {
weather(where: {idLatLong: {_eq: "${latLong}"}}) {
id
idLatLong
updated_at
lat
long
requestedByUserId
data
created_at
}
}
`
const query = {query: queryQL};
const options = {
headers: {
'X-Hasura-Admin-Secret': process.env.HASURA_KEY
},
responseType: 'json'
}
// see if there's an existing record for the lat long
try {
const response = await got.post(process.env.GQL_ENDPOINT, query, options);
console.log('query weather hasura');
console.log(response.body);
} catch(error) {
console.log(error);
}
I am getting a response from Hasura {"errors":[{"extensions":{"path":"$","code":"invalid-headers"},"message":"Missing Authorization header in JWT authentication mode"}]}
How do I see what GOT is sending out to the GQL endpoint? FYI, this call works fine in the GQL console and also in Postman.
The got() library has hooks that allow you to see the headers it's about to send. Here's an example that you can run and then insert the same thing into your code:
const got = require('got');
got("http://www.google.com", {
hooks: {
beforeRequest: [function(options) {
console.log(options);
}]
}
}).then(result => {
let i = 1;
}).catch(err => {
console.log(err);
});
You can also get a network analyzer like Wireshark to put on your client computer and watch the actual network traffic.

How do I save my data from my POST request, as a variable, to use in my GET request?

I am receiving my data from my POST request. I want to use that data as a variable in a GET request.The data from the POST request is user inputted to make different queries on the 3rd party API> How can I save my data as a variable to do so? You can see my request.body is being saved as const data. And I need it used as ${data} in my get request. I am pretty new to this, so any suggestions on best practice would be appreciated. Thanks for helping out.
Server.js
app.get(`/getmovies`, (req, res) => {
request(`http://www.omdbapi.com/?t=${data}&apikey=${API_KEY}`,
function (error, response, body) {
if (!error && response.statusCode == 200) {
var parsedBody = JSON.parse(body);
res.send(parsedBody)
} else {
console.log("error in the server")
}
}
)
})
app.post('/postmovie', (request, response) => {
console.log("I got a request!")
console.log(request.body);
const data = request.body;
response.json({
status: 'success',
name: data
})
})
///Client
postMovie = async (e) => {
e.preventDefault();
const data = this.state.movie;
const options = {
method: 'Post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
};
const response = await fetch('/postmovie', options);
const json = await response.json();
console.log(json);
};
getMovies = (e) => {
e.preventDefault();
const { movie } = this.state;
axios.get(`/getmovies`)
.then(response => this.setState({ movies: response.data }))
// .then(response => this.setState({ movies: response.data }))
.catch(err => console.error(err))
}
render() {
const { movie } = this.state;
return (
<div>
<section className="form-container">
<div className="movie-form-div">
< form>
<input className="form-input" type="text" name="name"
value={movie.name}
onChange={e => this.setState({ movie: { ...movie, name: e.target.value } })} />
<button onClick={this.getMovies}>Search</button>
<button onClick={this.postMovie}>Post</button>
</form >
</div>
</section>
As I have mentioned in the comments, your question is worded such that you make it sound like you absolutely need two requests.
Per the comments, OP has clarified that they don't necessarily need two requests so on that note here are a few things that you can try.
1) Set up an application.conf
Configuration files allow us to parameterize and configure variables that our applications need to function. You can vary the config files between environments (dev/stage/prod etc.) and deployments.
This will assume your API_KEY doesn't change frequently. Think of it as hard coding your api key like
var apiKey = "...."
but a better and more secure way of doing so.
In your case this won't work but it is good to be aware of it. You can use this package in the future
2) Get request query parameters.
This is your best bet
Query parameters, as the name suggest, allow us to pass in variables with the get requests to filter the results in the server side. This can be something like a user-id, page number etc.
Your get url from the client side would look like something like this
mydomain.com/getmovies?apikey={YourKeyGoesHere}
I am sure you have seen that ? in many of the urls you visit.
If you are using express your end point will look something like this.
app.get(`/getmovies`, (req, res) => {
var apiKey = req.query.apikey
request(`http://www.omdbapi.com/?t=${data}&apikey=${apikey}`,
function (error, response, body) {
if (!error && response.statusCode == 200) {
var parsedBody = JSON.parse(body);
res.send(parsedBody)
} else {
console.log("error in the server")
}
}
)
})
See How to get GET (query string) variables in Express.js on Node.js?
This will allow you to pass it from the client side.
If you are not using express please see https://nodejs.org/api/querystring.html
3) If you absolutely need two endpoints...
Use a cookie. Once a cookie is set, it is transported back and forth with every request, until it expires.
See this How to set cookie in node js using express framework? or this
Once you receive the post request you would set a cookie
app.post('/postmovie', (request, response) => {
response.cookie("apikey", data, maxAge: 900000, httpOnly: true });
response.json({
status: 'success'
})
})
Once this cookie is set, it will be transported to the client and back with all the requests including the get.
So in your get request you can do:
app.get(`/getmovies`, (req, res) => {
var apikey = req.cookies.apikey;
request(`http://www.omdbapi.com/?t=${data}&apikey=${apikey}`,
function (error, response, body) {
if (!error && response.statusCode == 200) {
var parsedBody = JSON.parse(body);
res.send(parsedBody)
} else {
console.log("error in the server")
}
}
)
})
So what's the difference between #2 and #3?
The query parameters that are encoded in the url are public and can be intercepted. This is fine for things like page numbers but if the API key is a paid one, this isn't very safe. Also once you submit the query, it is gone (unless you save the api key in the client side to the memory or local storage) so the user will have to potentially re-enter the api key everything.
Cookies on the other hand are private. The http-only flag we set in the code, prevents even the browser from reading it. Only the issuing server can see the contents. Plus cookies get stored in the browser until the expiration date so they can be reused with multiple requests.
Pick the one that works for you.
P.S. I didn't test the code above, you may need to tweak it a little bit. The answer is to give you a general idea on how these things are usually handled

How to restore an expired token [AWS Cognito]?

I'm using AWS for my website. After 1 hour the token expires and the user pretty much can't do anything.
For now i'm trying to refresh the credentials like this:
function getTokens(session) {
return {
accessToken: session.getAccessToken().getJwtToken(),
idToken: session.getIdToken().getJwtToken(),
refreshToken: session.getRefreshToken().getToken()
};
};
function getCognitoIdentityCredentials(tokens) {
const loginInfo = {};
loginInfo[`cognito-idp.eu-central-1.amazonaws.com/eu-central-1_XXX`] = tokens.idToken;
const params = {
IdentityPoolId: AWSConfiguration.IdPoolId
Logins: loginInfo
};
return new AWS.CognitoIdentityCredentials(params);
};
if(AWS.config.credentials.needsRefresh()) {
clearInterval(messwerte_updaten);
cognitoUser.refreshSession(cognitoUser.signInUserSession.refreshToken, (err, session) => {
if (err) {
console.log(err);
}
else {
var tokens = getTokens(session);
AWS.config.credentials = getCognitoIdentityCredentials(tokens);
AWS.config.credentials.get(function (err) {
if (err) {
console.log(err);
}
else {
callLambda();
}
});
}
});
}
the thing is, after 1hour, the login token gets refreshed without a problem, but after 2hrs i can't refresh the login token anymore.
i also tried using AWS.config.credentials.get(), AWS.config.credentials.getCredentials() and AWS.config.credentials.refresh()
which doesn't work either.
The error messages i'm getting are:
Missing credentials in config
Invalid login token. Token expired: 1446742058 >= 1446727732
After almost 2 weeks i finally solved it.
You need the Refresh Token to receive a new Id Token. Once the Refreshed Token is acquired, update the AWS.config.credentials object with the new Id Token.
here is an example on how to set this up, runs smoothly!
refresh_token = session.getRefreshToken(); // you'll get session from calling cognitoUser.getSession()
if (AWS.config.credentials.needsRefresh()) {
cognitoUser.refreshSession(refresh_token, (err, session) => {
if(err) {
console.log(err);
}
else {
AWS.config.credentials.params.Logins['cognito-idp.<YOUR-REGION>.amazonaws.com/<YOUR_USER_POOL_ID>'] = session.getIdToken().getJwtToken();
AWS.config.credentials.refresh((err)=> {
if(err) {
console.log(err);
}
else{
console.log("TOKEN SUCCESSFULLY UPDATED");
}
});
}
});
}
Usually it's solved by intercepting http requests with additional logic.
function authenticationExpiryInterceptor() {
// check if token expired, if yes refresh
}
function authenticationHeadersInterceptor() {
// include headers, or no
}}
then with use of HttpService layer
return HttpService.get(url, params, opts) {
return authenticationExpiryInterceptor(...)
.then((...) => authenticationHeadersInterceptor(...))
.then((...) => makeRequest(...))
}
It could be solved by proxy as well http://2ality.com/2015/10/intercepting-method-calls.html
In relation to AWS:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Credentials.html
You're interested in:
getPromise()
refreshPromise()
Here is how I implemented this:
First you need to authorize the user to the service and grant permissions:
Sample request:
Here is how I implemented this:
First you need to authorize the user to the service and grant permissions:
Sample request:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token&
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic aSdxd892iujendek328uedj
grant_type=authorization_code&
client_id={your client_id}
code=AUTHORIZATION_CODE&
redirect_uri={your rediect uri}
This will return a Json something like:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "refresh_token":"dn43ud8uj32nk2je","id_token":"dmcxd329ujdmkemkd349r", "token_type":"Bearer", "expires_in":3600}
Now you need to get an access token depending on your scope:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token
Content-Type='application/x-www-form-urlencoded'&
Authorization=Basic aSdxd892iujendek328uedj
grant_type=client_credentials&
scope={resourceServerIdentifier1}/{scope1} {resourceServerIdentifier2}/{scope2}
Json would be:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "token_type":"Bearer", "expires_in":3600}
Now this access_token is only valid for 3600 secs, after which you need to exchange this to get a new access token. To do this,
To get new access token from refresh Token:
POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token >
Content-Type='application/x-www-form-urlencoded'
Authorization=Basic aSdxd892iujendek328uedj
grant_type=refresh_token&
client_id={client_id}
refresh_token=REFRESH_TOKEN
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{"access_token":"eyJz9sdfsdfsdfsd", "refresh_token":"dn43ud8uj32nk2je", "id_token":"dmcxd329ujdmkemkd349r","token_type":"Bearer", "expires_in":3600}
You get the picture right.
If you need more details go here.
This is how you can refresh access token using AWS Amplify library:
import Amplify, { Auth } from "aws-amplify";
Amplify.configure({
Auth: {
userPoolId: <USER_POOL_ID>,
userPoolWebClientId: <USER_POOL_WEB_CLIENT_ID>
}
});
try {
const currentUser = await Auth.currentAuthenticatedUser();
const currentSession = currentUser.signInUserSession;
currentUser.refreshSession(currentSession.refreshToken, (err, session) => {
// do something with the new session
});
} catch (e) {
// whatever
}
};
More discussion here: https://github.com/aws-amplify/amplify-js/issues/2560.

Categories