I have a frontend javascript (svelte) and a go backend API. The front end calls the go backend URL to generate an OAuth2 token from github. The backend replies with the crafted url as its response)
https://github.com/login/oauth/authorize?access_type=online&client_id=xxxxxxx&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fapi%2Fv1%2Fcallback&response_type=code&scope=user%3Aemail&state=state
This url is opened by the user with location.assign(authUrl);
The actual full code:
const authUrl = await fetch('http://localhost:8080/api/v1/authurl', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()).then(data => data.url);
// Redirect the user to the GitHub authorization URL
location.assign(authUrl);
The backend then handles the callback, which then performs the exchange and receives a valid OAuth2 token (yay!)
From here I need to inform the client the transaction has completed, but I am unsure of how to do this (this is where I freely admit I suck at frontend and don't have much experience). I have decided to return a httponly session cookie to the frontend and store the GitHub OAuth2 token securely on the backend, but how should I get this token to the frontend so that it can store it?
Keep in mind there is not fetch from the client, the backend API initiates with the call back.
One thing I can do is redirect;
cookie := &http.Cookie{
Name: "token",
Value: jwt_token,
MaxAge: 3600,
Path: "/",
Domain: "localhost",
Secure: false,
HttpOnly: true,
}
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
c.Redirect(http.StatusFound, "http://localhost:3000/dashboard/")
Where http://localhost:3000/dashboard/ would be a landing page on the front end
But to do this I would need to POST from the backend, to a listening API on the front end, which kind of feels wrong? Is this the wrong approach to take and should I use a more optimal method?
Related
I have an API and a web client developed using node and TypeScript. Users can authenticate using JWT. The system works, but I would like to know if it is secure, or if it has flaws:
The system is as follows:
Client POST to /login endpoint on API
On API, when POST /login request is received, a JWT token is generated using some user data as content, and a secret string that is stored as an environment variable:
// SECRET is an environment variable == "bd0b2760-5869-11ec-bf63-0242ac130002"
const userData = {
id: 1,
name: "John Doe",
};
const token = jwt.sign(JSON.stringify(userData), SECRET);
In the response for POST /login API sends two cookies: one holding the token, and another one with raw user data:
return res
.cookie('sessionData', userData, {
httpOnly: true,
path: '/',
domain: "example.com",
})
.cookie('sessionToken', token, {
httpOnly: true,
path: '/',
domain: "example.com",
}).send();
The client receives the token. The client can be sure that sessionToken is valid, as it was sent by the API. It wont verify it, as to do it the SECRET is needed, and we don't want to expose it to the client.
On reload, client will use the sessionData cookie to know that user is logged in, using this data to load the client-side user data.
As those cookies are http cookies, both cookies are attached to every request send to the API, and are received by the API. On every request to endpoints requiring auth, the API will decrypt sessionToken and match it against sessionToken cookie: if they doesnt match, API will delete cookies in the response, effectively logging out the client.
// SECRET is an environment variable == "bd0b2760-5869-11ec-bf63-0242ac130002"
const sessionToken = req.cookies.sessionToken;
const sessionData = req.cookies.sessionData;
const decodedToken = jwt.verify(sessionToken, SECRET);
if(decodedToken.id !== sessionData.id || decodedToken.name !== sessionData.name ) {
return res
.clearCookie('sessionToken', { path: '/', domain: "example.com" })
.clearCookie('sessionData', { path: '/', domain: "example.com" })
}
As previously said, this system works, and it seems secure. But maybe I'm missing something, so better ask. All code is kinda pseudocode.
Any help will be welcome!
It wont verify it, as to do it the SECRET is needed, and we don't want to expose it to the client.
If you want the client to be able to verify this JWT you can use asymmetric signing. Then the client uses a public key, which can be used only to verify that the JWT is OK. It can't be used to sign new JWTs.
On reload, client will use the sessionData cookie to know that user is logged in, using this data to load the client-side user data.
You're using http only cookies so that won't work. Your client is not able to read that cookie.
the API will decrypt sessionToken
Minor thing, but worth remembering - the API will decode the sessionToken cookie, not decrypt. Signed JWTs are encoded, not encrypted. You can create encrypted JWTs (JWE), in which case no one, apart from the API will be able to read the content of the token, but JWEs are much harder to maintain.
the API will decrypt sessionToken and match it against sessionData cookie
I don't see how this gives you any additional security. The session token is a signed JWT, so you can easily verify it to check whether it's been tampered with. If someone managed to steal the sessionToken cookie they might as well have stolen the sessionData cookie and use them together. You're using http only cookies so XSS won't be able to steal the contents of those cookies. You could also use secure flag to make sure that those cookies are not sent through unencrypted connections.
I'm building a Next.js app using Strapi as a CMS and authentication server. I've managed to get a JWT from the auth endpoint, and have stored it in a cookie. To retrieve secured content from strapi I need to send this JWT in the header
I'm using Apollo to send graphQL queries to strapi, according to their documentation, I can easily set auth headers by adding this to utils/apollo.js
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = cookies.get('jwt') <-- This is what I'd like to do, but I can't figure out how to access cookies at this point.
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
How can I access cookies in the app mentioned above. It's not part of a component, though it is used to build a HOC?
While using localstorage is an option, I've read that cookies are a safer way to store data.
Using cookies with secure isn't an option - I'm not sure if strapi allows it, and also because there are multiple frontend origins vercel builds previews for all branches
Once you set the cookie it will be passed in the Cookie header so you need to retrieve it from there, not the cookies object which is only available in the browser but not available in server-side requests. In addition, if you're using CORS you will need to enable credentials both in the client and server, specifically in the client you need to set credentials: 'include'. This is required setting for cookies to work.
I am a little bit confused about how HTTP works.
My question is that when the server assigns a JWT token to a client after providing credentials, the server assigns token in HTTP header['Authorization']. Now the user is logged in and can make a request.
So please tell me if when the user makes an API call again, will the HTTP header['Authorization'] remain same or be changed?
The header['Authorization'] remains the same.
The server basically generates a token and sends it to the client-side.
On the client-side, the token will be saved in the request header of subsequent requests sent to the server.
Please check this resource
Yes client has to do every request with authorisation request (protected routes )
so you have to set up a axios configuration (https://github.com/axios/axios#axioscreateconfig)
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'TOKEN'}
});
And you have to know how this authentication and authorisation tokens works for this please go through this (https://blog.restcase.com/4-most-used-rest-api-authentication-methods/)
I am building an app with a back-end in NodeJS 12 Express 4 and a front-end client SPA in Angular 8. I am building local authentication with the PassportJS local authentication strategy.
Auth workflow:
A GET request is sent to the /login endpoint and the login page is loaded.
Note: The login page is NOT in the SPA, it is a simple pug page with a form that sends a to POST to the /login endpoint.
GET /login
router.get('/', (req, res, next) => {
return res.render('login');
});
The user enters their credentials, submits the form and sends a POST request to /login. The Passport local strategy checks the credentials and then in the callback, creates a JWT to be sent back to the client.
This is where I am getting stuck. If I had the login page in the SPA, I could just send an AJAX request and get the response back to the client as JSON e.g. return res.json(jwtToken) and the SPA could parse the token and store it in session storage directly.
However, since the login page is on the server directly, how can I send the token back to the client?
At the moment, I am trying to place the token in the auth header and 'redirect' to the client URL. This is working (I can see the auth token in the browser console) but how does can the SPA read the token on the Angular side? Angular would need to access the HTTP Headers, get the token and save it in the session storage.
POST /login
router.post('/', (req, res, next) => {
// Passport authentication strategy
passport.authenticate('local', function (err, user, info) {
// Callback after authentication strategy is complete
// Check error
if (err) {
console.error(err);
return res.status(404).json(err);
}
// Check if user was returned
if (user) {
// Generate JWT token
let jwt;
jwtToken = generateJwt();
// Set authorization header
res.set({
'Content-Type': 'application/json',
'Authorization': `Bearer: ${jwtToken}`,
});
// Redirect to the client
return res.redirect('http://localhost:4200/login');
//return res.json(jwtToken);
}
})(req, res);
});
Question:
In the Angular component, is it possible to parse the Authorization header to get the token that was sent by the server? I tried using the activatedRoute module but wasn't able to get access to the header data from the initial page load. I also tried adding the token as a URL parameter in the GET request but that makes the URL extremely long and exposes the token in plain text. Are there any reasonable ways send the token from the server to the client safely?
UPDATE:
I am going to try sending the token in a cookie using express cookieParser similar to this answer in another post. That way the SPA can access the token saved in the cookie, parse it, and then save it in session storage. However, I am hesitant to do this and am not sure if this is going to be the most sustainable technique, since this requires an additional module to parse the cookie in the SPA.
let options = {
maxAge: 1000 * 60 * 15,
httpOnly: false,
signed: true
}
// Set cookie
res.cookie('jwt-token', jwt, options)
// Redirect to the client
return res.redirect(302, 'http://localhost:4200/login');
I have 2 microservices, MicroserviceA is a core service including authentication and authorization
services and other APIs services.
MicroserviceB includes several other APIs (not including authentication and authorization)
MicroserviceA include some API likes this:
localhost:3000/api/login (by JWT)
localhost:3000/api/postA
localhost:3000/api/getA
localhost:3000/api/deleteA
localhost:3000/api/pushA
MicroserviceB include some API like this:
localhost:3001/api/postB
localhost:3001/api/getB
localhost:3001/api/deleteB
localhost:3001/api/pushB
My front-end only calls only 1 based url is: http: // localhost: 3000, so I do like that, to communicate between 2 microservices,
I am using axios to call from core MicroserviceA to MicroserviceB
So in MicroserviceA, I have the following APIs:
localhost:3000/api/login (JWT)
localhost:3000/api/postA
localhost:3000/api/getA
localhost:3000/api/deleteA
localhost:3000/api/pushA
localhost:3000/api/postB (axios call)
localhost:3000/api/getB (axios call)
localhost:3000/api/deleteB (axios call)
localhost:3000/api/pushB (axios call)
But the problem is that when I want to call some APIs from MicroserviceB,
I have to call it from MicroserviceA. Is there any way that frond-end dev
only needs to authenticate once but can call APIs independently from
MicroserviceA and MicroserviceB without having to call twice as my way?
MicroserviceB will also need to implement JWT authentication. It doesn't need to implement the login method, you have MicroserviceA responsible for that, it only needs to validate the token. As long as both MicroserviceA and MicroserviceB share the same secret the token can be validated on both.
Your main protection is your application's secret used to sign the tokens. This is essentially your private key so ideally use some algorithm/password generator to generate it and make sure nobody else have access to it.
Ideally your JWT token would include some way of linking to the user in its payload such as the user id or email etc. With careful planning it's even possible to not need to make a database request to fetch the user - your endpoints should be able to use the JWT payload to identify the user/role/permissions etc.
Say for example microserviceA implements a /login endpoint that returns the token in a JSON response. So to login you'd do something like:
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'email=' + email + '&password=' + password
})
.then(response => response.json())
.then(result => {
localStorage.setItem('jwt', result.token); // save token somewhere
});
Then to make a request to microserviceB you just need to add the authorization header:
fetch('http://microserviceB.com/do/something', {
headers: {
'Authorization': localStorage.getItem('jwt')
}
})
.then(processResponseFromMicroserviceB);
You can validate the jwt token in microserviceB the same way you did in microserviceA but you don't need to implement the login part.