Pls help me figure out where I am going wrong.
Problem: When I login and then refresh, I get logged out.
Expected: Upon logging in, a cookie should be set by Node, which should be sent to the server for every request. Which is not happening.
The app runs an initializer function to try and auto authenticate based on the token present in the cookie.
Angular initializer function
export function appInitializer(accountService: AccountService) {
return () => new Promise(resolve => {
// attempt to refresh token on app start up to auto authenticate
accountService.refreshToken()
.subscribe()
.add(resolve);
});
}
Angular Account Service
#Injectable({ providedIn: 'root' })
export class AccountService {
private accountSubject: BehaviorSubject<Account>;
public account: Observable<Account>;
....
constructor(private httpClient: HttpClient, private router: Router) {
this.accountSubject = new BehaviorSubject<Account>(null);
this.account = this.accountSubject.asObservable();
}
login(userName: string, password: string) {
return this.httpClient.post<any>(`${this.reqUrl}/authenticate`, {userName, password})
.pipe(map(account => {
this.accountSubject.next(account);
this.startRefreshTokenTimer();
return account;
}));
}
refreshToken() {
return this.httpClient.post<any>(`${this.reqUrl}/refresh-token`, {})
.pipe(map(account => {
this.accountSubject.next(account);
this.startRefreshTokenTimer();
return account;
}));
}
private refreshTokenTimeout;
private startRefreshTokenTimer() {
// parse json object from base64 encoded jwt token
const jwtToken = JSON.parse(atob(this.accountValue.jwtToken.split('.')[1]));
// set a timeout to refresh the token a minute before it expires
const expires = new Date(jwtToken.exp * 1000);
const timeout = expires.getTime() - Date.now() - (60 * 1000);
this.refreshTokenTimeout = setTimeout(() => this.refreshToken().subscribe(), timeout)
}
Node Route
router.post('/authenticate', AccountController.authenticateSchema, AccountController.authenticate);
router.post('/refresh-token', AccountController.refreshToken);
Node Acct Controller
exports.authenticate = (req, res, next) => {
const { userName, password } = req.body;
const ipAddress = req.ip;
accountService.authenticate({userName, password, ipAddress})
.then(({refreshToken, ...account}) => {
setTokenCookie(res, refreshToken); // <<<<<<<<<<<<<<<<<<<<<<<<<
res.json(account);
})
.catch(next);
}
exports.refreshToken = (req, res, next) => {
const token = req.cookies.refreshToken;
if(!token) {
return res.status(400).json({ message: 'acct ctlr rTkn not found'}); // <<< even after Login and refresh I reach here
}
const ipAddress = req.ip;
accountService.refreshToken({token, ipAddress})
.then(({refreshToken, ...account}) => {
setTokenCookie(res, refreshToken);
res.json(account);
})
.catch(next); // ?
}
function setTokenCookie(res, token) {
// create cookie with refresh token that expires in 1 day
const cookieOptions = {
httpOnly: true,
expires: new Date(Date.now() + 1*24*60*60*1000)
};
res.cookie('refreshToken', token, cookieOptions); ///?
}
I get this error in dev tool upon starting the appand also when i refresh after logging in.
Try adding:
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', true);
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
Related
I am using NodeJS ReactJS Redux with JWT in an application and I have issue getting expiresIn and token at the reactjs frontend. When I console log the token at the NodeJS controller, it displays the token nd everything perfectly but it pointed to the jwt.verify() with error JsonWebTokenError: jwt malformed but at the ReactJS frontend, it displays the payLoad userData part of the token and displays undefined for both expiresIn and token.
As you can see in the Redux authSlice class that I set localStorage Item in there for token, expire and userData but when I tried to get the localStorage item in another page, I could only get the userData payloads but the token and expire are undefined.
I don't know what is wrong here because the NodeJS sent the token for real as I can get the token from console of NodeJS and ThunderClient API as well gave an 200 OK when i test the api using ThunderClient in VS Code.
My concern is that ThunderClient displays 200 OK and return Token, expiresIn and userData complete and everything perfect, NodeJS console displays correct information on console but gave JsonWebTokenError: jwt malformed and at ReactJS frontend, I got the userData from the token sent by the API but accessToken and expiresIn are missing i.e out of the 3 string that JWT encrypted, I got only the Payload which is the userData.
How can I solve this?
*******************NODEJS
jwtHelper.js
exports.extractToken = (req) => {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
jwtVerify.js
module.exports = function (req, res, next) {
try {
const token = extractToken(req);
if (token == null) {
return res.status(StatusCodes.UNAUTHORIZED).send("Unauthorized");
}
jwt.verify(token, common_helper.getJWTAccessToken(), {}, (err, user) => {
if (err) {
console.log(err);
return res.status(StatusCodes.FORBIDDEN).send("Invalid user");
}
req.user = user["userData"];
next();
});
} catch (e) {
next(e);
}
};
Login (Controller)
const token = jwt.sign({userData}, common_helper.getJWTAccessToken(), {
algorithm: 'HS256',
expiresIn: common_helper.getJWTExpiryTime(),
});
res.status(StatusCodes.OK).send({"expires_in": common_helper.getJWTExpiryTime(),"access_token":token,"token_type": "bearer","userData":userData});
console.log(`The token is ${token}`) // This displays the ciper token
console.log(`The secret_token is ${common_helper.getJWTExpiryTime()}`) //this displays the real secret key
*******************REACTJS
Redux Slice. Note that the localStorage is set here
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
interface IAuthToken {
isAuthenticated?:boolean,
jwtToken: any;
expiryDate: any;
errorMessage?:string;
userData?:any;
notverified?: string;
}
const initialState: IAuthToken = {
jwtToken: undefined,
expiryDate: undefined,
errorMessage:'',
isAuthenticated:false,
userData:undefined,
notverified: undefined,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setJWTToken: (state, _action: PayloadAction<IAuthToken>) => {
state.jwtToken = _action.payload.jwtToken;
state.expiryDate = _action.payload.expiryDate;
state.userData=_action.payload.userData;
localStorage.setItem('token', state.jwtToken);
//localStorage.setItem('token', JSON.stringify(state.jwtToken));
localStorage.setItem('expire', state.expiryDate);
//localStorage.setItem('expire', JSON.stringify(state.expiryDate));
if(state.userData)
localStorage.setItem('userData',JSON.stringify(state.userData));
state.isAuthenticated = true;
state.notverified = _action.payload.notverified;
},
removeJWTToken: (state) => {
localStorage.clear();
state.jwtToken = '';
state.expiryDate=undefined;
state.isAuthenticated = false;
},
setError: (state, _action: PayloadAction<string>) => {
state.errorMessage = _action.payload;
},
},
});
export const { setJWTToken, removeJWTToken,setError } = authSlice.actions;
export default authSlice.reducer;
ReactJS Login
Axios.post(`${baseURL}/signin`, { username: formik.values.username, password: formik.values.password})
.then((response) => {
if(response.data.notverified)
{
setSubmitting("");
navigate("/needemailconfirmation", { replace: true });
}
setSubmitting("");
console.log(response.data)
dispatch(setJWTToken(response.data));
navigate("/dashboardd", { replace: true });
authAction
export const signIn = (email, password) => {
return (dispatch) => {
axios
.post(`${url}/signin`, { email, password })
.then((token) => {
localStorage.setItem("token", token.data);
dispatch({
type: "SIGN_IN",
token: token.data,
});
})
.catch((error) => {
console.log(error.response);
toast.error(error.response?.data, {
position: toast.POSITION.BOTTOM_RIGHT,
});
});
};
};
authReducer
const authReducer = (state = initialState, action) => {
switch (action.type) {
case "SIGN_IN":
case "SIGN_UP":
case "USER_LOADED":
toast("Welcome...", {
position: toast.POSITION.BOTTOM_RIGHT,
});
const user = jwtDecode(action.token);
return {
...initialState,
token: action.token,
name: user.name,
email: user.email,
_id: user._id,
};
case "SIGN_OUT":
localStorage.removeItem("token");
toast("Goodbye...", {
position: toast.POSITION.BOTTOM_RIGHT,
});
return {
token: null,
name: null,
email: null,
_id: null,
};
default:
return state;
}
};
In my ReactJS frontend, from the token sent by the NodeJS api I was able to get userData payload from the token but I could not get the token and expiresIn for reason I do not know.
You can find the token details from the browser inspect here where I got userData payload but got undefines for accesstoken and expiresIn
Update
In my question above, I made the point of not getting the token value in my ReactJS frontendm while debugging the code I set the localStorage at the login page immediately after a successful login and i used the localStorage getItem() to get the value in my Redux slice but the issue that persist is that my ReactJS frontend is not allowing me to get to dashboard as it is acting like the value is not set i.e the middleware cannot get the value at the and the route allow the user to navigate to the dashboard is bouncing the navigation back to login page despite the fact that the token is set and I can get the token at the browser inspect to be sure that it is set but Auth Middleware is not getting it. See the code I have now below:
******************ReactJS
Login.js
//call the api
Axios.post(`${baseURL}/signin`, { username: formik.values.username, password: formik.values.password})
.then((response) => {
if(response.data.notverified)
{
setSubmitting("");
navigate("/needemailconfirmation", { replace: true });
}
setSubmitting("");
console.log(response.data)
if(response.data.access_token){
dispatch(setJWTToken(response.data));
localStorage.setItem('token', JSON.stringify(response.data.access_token));
localStorage.setItem('expire', JSON.stringify(response.data.expires_in));
localStorage.setItem('userData', JSON.stringify(response.data.userData));
}
navigate("/dashboardd", { replace: true });
}) .catch((error) =>{
setSubmitting("");
//console.log(error.data);
setErrorMsg("Invalid Login Credentials Supplied.");
//alert("Invalid Login Credentials Supplied, Error : <br>" + error.data);
});
}
Redux Slice Auth File
interface IAuthToken {
isAuthenticated?:boolean,
jwtToken: any;
expiryDate: any;
errorMessage?:string;
userData?:any;
notverified?: string;
}
const initialState: IAuthToken = {
jwtToken: undefined,
expiryDate: undefined,
errorMessage:'',
isAuthenticated:false,
userData:undefined,
notverified: undefined,
};
const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setJWTToken: (state, _action: PayloadAction<IAuthToken>) => { state.jwtToken = localStorage.getItem("token");
state.expiryDate = localStorage.getItem("expire");
state.userData = localStorage.getItem("userData");
if(localStorage.getItem("userData"))
state.isAuthenticated = true;
state.notverified = '';
},
removeJWTToken: (state) => {
localStorage.clear();
state.jwtToken = '';
state.expiryDate=undefined;
state.isAuthenticated = false;
},
setError: (state, _action: PayloadAction<string>) => {
state.errorMessage = _action.payload;
},
},
});
export const { setJWTToken, removeJWTToken,setError } = authSlice.actions;
export default authSlice.reducer;
AuthenticatedRoute.js : this is used in bouncing user back if they token is not found
const AuthenticatedRoute: FC<{ element: any }> = ({ element }) => {
const token = localStorage.getItem('token');
const expire = localStorage.getItem('expire');
const now = Date.now();
if (token && expire && parseInt(expire) > parseInt(now.toString())) {
return element;
} else {
return <Navigate to="/" replace />
}
};
export default AuthenticatedRoute;
My React Router Dom
<Route path="/checkpoint" element={<AuthenticatedRoute element={<CheckpointList />} />}></Route>
<Route path="/vendour" element={<AuthenticatedRoute element={<VendourList />} />}></Route>
React Router Dom path are working normal but just that if I put AuthenticatedRoute to dashboard Route, it would not allow me in because the AuthenticatedRoute is not seeing the token I think but when I console the token in dashboard if i remove AuthenticatedRoute, the values are there.
Using React Router Dom, I was debugging this code and remove the AuthenticatedRoute middleware from dashboard and I was able to navigate to dashboard and get the localStorage value for the token and expiresIn but the point remains that when I put the AuthenticatedRoute middleware in the dashboad Route, the AuthenticatedRoute would bounce me back to login page meaning it cannot find the token.
I require a Google Sign-In for a Firebase application, to get this done I used multiple sources:
auth-sessions provides a good out of the box example with NodeJS + Firebase + Google Login.
manage firebase session provides a sample NodeJS functions (not entire working solution) to work with Firebase Functions.
The problem:
When attempting to sign in into a Google account on production, the following error occurs when executing the code authenticate function below:
admin
.auth()
.verifySessionCookie(sessionCookie, true /** checkRevoked */)
.then((decodedClaims) => {
log("Decode success");
// inbetween checks
log("Successfully decoded and authenticated");
next();
})
.catch((error) => {
log("Error authenticating"); < ---- THIS IS THE PROBLEM
...
});
This error ONLY occurs when on production i.e. deployed to Firebase. When testing locally using firebase emulators emulating hosting and functions only (auth, firestore, database, etc are all production), login is successful. When deploying, the login fails with the error below.
Error:
Decoding Firebase session cookie failed. Make sure you passed the entire string JWT which represents a session cookie. See https://firebase.google.com/docs/auth/admin/manage-cookies for details on how to retrieve a session cookie.
More Details:
Below is a high-level overview of steps/actions performed
Steps overview of actions performed
1. Visit any page e.g. /login
2. Click sign in with Google, execute the popup provider (see [here][3])
2.
1. Sign in with Google account
2. Send token to firebase functions for verification i.e. `POST /sessionLogin`
3. Receive response (assume 200 OK)
4. Redirect to authenticated URL
The error is on the last step i.e. 4
This error occurs after successfully creating a session using the sample /sessionLogin code found here on firebase's site:
const auth = admin.auth();
auth.verifyIdToken(idToken).then(value => {
debug("Token verified")
return auth.createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
// res.json(JSON.stringify({status: 'success'}));
res.status(200).send("OK");
}).catch((error) => {
debug(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
}).catch(reason => {
debug("Unable to verify token");
debug(reason);
res.status(401).send('INVALID TOKEN!');
});
The logs respond with a Token verified and a status 200 is sent to the client.
The client then redirects to an authenticated URL /user/dashboard which performs the authenticate check (see below) which fails and redirects back to /login:
const authenticate = (req, res, next) => {
log("Authenticating");
// source: https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions
const sessionCookie = req.cookies.session || '';
// Verify the session cookie. In this case an additional check is added to detect
// if the user's Firebase session was revoked, user deleted/disabled, etc.
return admin
.auth()
.verifySessionCookie(sessionCookie, true /** checkRevoked */)
.then((decodedClaims) => {
log("Decode success");
// inbetween checks
log("Successfully decoded and authenticated");
next();
})
.catch((error) => {
log("Error authenticating");
if(error.errorInfo && error.errorInfo.code && error.errorInfo.code === "auth/argument-error") {
debug(error.errorInfo.message);
res.redirect('/user/login');
return;
}
debug(error);
// Session cookie is unavailable or invalid. Force user to login.
req.flash("message", [{
status: false,
message: "Invalid session, please login again!"
}])
res.redirect('/user/login');
});
};
which is middleware for the express app:
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://my-company-default-rtdb.firebaseio.com",
storageBucket: "gs://my-company.appspot.com"
});
const app = express();
app.use(cors({origin: true}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(morgan('dev'));
app.use(cookieParser('0000-0000-0000-0000-0000'))
app.set('trust proxy', 1) // trust first proxy
// Attach CSRF token on each request.
app.use(attachCsrfToken('/', 'csrfToken', (Math.random()* 100000000000000000).toString()));
app.use(session({
secret: '0000-0000-0000-0000-0000',
resave: false,
name: '__session',
store: new FirebaseStore({
database: admin.database()
}),
}));
app.use(flash());
app.use(authenticate);
// routes
exports.app = functions.https.onRequest(app);
Execution Logs:
1:12:02.796 PM app Function execution started
1:12:02.910 PM app Authenticating
1:12:02.910 PM app Attempting to verify session cooking
1:12:02.910 PM app Cookies: {}
1:12:02.911 PM app Error authenticating
1:47:41.905 PM app auth/argument-error
1:12:02.911 PM [app] Decoding Firebase session cookie failed. Make sure you passed the entire string JWT which represents a session
cookie. See https://firebase.google.com/docs/auth/admin/manage-cookies
for details on how to retrieve a session cookie.
1:12:02.937 PM [app] Function execution took 141 ms, finished with status code: 302
Update
Call to backend for auth:
const postIdTokenToSessionLogin = (idToken, csrfToken) => {
return axios({
url: "/user/sessionLogin",
method: "POST",
data: {
idToken: idToken,
csrfToken: csrfToken,
},
}).then(value => {
console.log(value);
if(value.status === 200) {
window.location.assign("/user/dashboard");
}
}).catch(reason => {
console.error(reason);
alert("Failed to login");
});
}
Client side call:
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
.signInWithPopup(provider)
.then(async value => {
firebase.auth().currentUser.getIdToken().then(idToken => {
// const idToken = value.credential.idToken;
const csrfToken = getCookie('_csrf');
return postIdTokenToSessionLogin(idToken, csrfToken);
}).catch(reason => {
console.error("Failed to get current user token");
alert(reason.message);
});
})/*.then(value => {
window.location.assign("/user/dashboard")
})*/.catch((error) => {
console.error("Failed to sign in with Google");
alert(error.message);
});
Update 2:
Updated client-side axios request with the following, also added extra req.cookies logging
return axios({
url: "/user/sessionLogin",
method: "POST",
withCredentials: true,
data: {
idToken: idToken,
csrfToken: csrfToken,
},
})
Extra logging:
4:43:23.493 PM app Function execution started
4:43:23.501 PM app Authenticating
4:43:23.501 PM app Creating session
4:43:23.502 PM app /sessionLogin Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJ..."}
4:43:23.503 PM app Token verified
4:43:23.503 PM app {"name":redacted ,"picture":"","iss":"","aud":"",...}
4:43:23.503 PM app ==============
4:43:23.503 PM app /sessionLogin#verifyIdToken Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJ..."}
4:43:23.634 PM app /sessionLogin#createSessionCookie Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3N..."}
4:43:23.634 PM app Cookie:
4:43:23.634 PM app "eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJhc2UuZ29vZ..."
4:43:23.634 PM app ==============
4:43:23.643 PM app [0mPOST /user/sessionLogin [32m200[0m 139.036 ms - 2[0m
4:43:23.643 PM app Function execution took 150 ms, finished with status code: 200
4:43:24.131 PM app Function execution started
4:43:24.153 PM app Authenticating
4:43:24.153 PM app Attempting to verify session cooking
4:43:24.153 PM app Cookies: {}
Update 3
Rewrites enabled API & NodeJS access exactly as shown in firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"hosting": {
"site": "my-company-admin-portal",
"public": "public",
"rewrites": [
{
"source": "/api/**",
"function": "api"
},
{
"source": "**",
"function": "app"
}
],
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
},
"storage": {
"rules": "storage.rules"
},
"emulators": {
"auth": {
"port": 9099
},
"functions": {
"port": 5001
},
"database": {
"port": 9000
},
"hosting": {
"port": 5000
},
"storage": {
"port": 9199
},
"ui": {
"enabled": true
}
}
}
sessionCookie is undefined as in the code provided in the question.
// Authenticate middleware right now
const authenticate = (req, res, next) => {
log("Authenticating");
// No sessionCookie declared
return admin
.auth()
.verifySessionCookie(sessionCookie, true /** checkRevoked */)
// undefined passed here ^^^
}
You must pass the cookie that you set after using createSessionCookie in verifySessionCookie method as shown below:
// updated authenticate middleware
const authenticate = async (req, res, next) => {
try {
log("Authenticating");
// Read the value of cookie here
const sessionCookie = req.cookies.session
// Return unauthorized error if cookie is absent
if (!sessionCookie) return res.sendStatus(401)
const decodedClaims = await admin.auth().verifySessionCookie(sessionCookie, true)
// cookie verified, continue
} catch (e) {
console.log(e)
return res.send("An error occurred")
}
}
I suggest you to check this post in which they have a similar issue.
In summary, if you check the quickstart-nodejs repository you have to add the cookie-parser and body-parser. Here is the example that they provide in the post:
const admin = require("firebase-admin");
const functions = require("firebase-functions");
const next = require("next");
const cors = require("cors");
const express = require("express");
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev, conf: { distDir: "next" } });
const handle = app.getRequestHandler();
admin.initializeApp({
credential: admin.credential.cert("Service account key"),
databaseURL: "Path to database"
});
const server = express();
server.use(cors({ origin: true }));
server.use(bodyParser.json());
// Support URL-encoded bodies.
server.use(bodyParser.urlencoded({
extended: true
}));
// Support cookie manipulation.
server.use(cookieParser());
// Attach CSRF token on each request.
server.use(attachCsrfToken('/', 'csrfToken', (Math.random()* 100000000000000000).toString()));
function attachCsrfToken(url, cookie, value) {
return function(req, res, next) {
if (req.url === url) {
res.cookie(cookie, value);
}
next();
}
}
server.post("/login", (req, res) => {
if (req.body && req.body.idToken) {
const idToken = `${req.body.idToken}`;
const expiresIn = 60 * 60 * 24 * 5 * 1000;
admin.auth().createSessionCookie(idToken, { expiresIn }).then((sessionCookie) => {
const options = { maxAge: expiresIn, httpOnly: true, secure: true };
res.cookie("session", sessionCookie, options);
res.end(JSON.stringify({ sessionCookie }));
}, error => {
res.status(401).send(error);
});
} else {
res.status(401).send("Token empty");
}
});
server.post("/profile", (req, res) => {
if (req.cookies && req.cookies.session) {
const sessionCookie = `${req.cookies.session}`;
admin.auth().verifySessionCookie(
sessionCookie, true /** checkRevoked */).then((decodedClaims) => {
res.end(JSON.stringify({ decodedClaims }));
}).catch(error => {
res.status(401).send(error);
});
} else {
res.status(401).send("Session empty");
}
});
exports.next = functions.https.onRequest((req, res) => {
if (req.method === "POST") {
if (!req.path) req.url = `/${request.url}`;
return server(req, res);
}
return app.prepare().then(() => handle(req, res));
});
Architecture: front end Angular, backend nodejs/express.
Currently the setup works as follow:
Login to the site via the Cognito Hosted UI
This redirects to our home page and sends us a code in the URL
I pull down this code in Angular
import { Component, OnInit } from '#angular/core';
import { DbService } from '../db.service';
import { Iss } from '../db.service';
import { Router, ActivatedRoute } from '#angular/router';
import { Http, Response, RequestOptions, Headers} from '#angular/http';
#Component({
selector: 'app-dashboard'
})
export class GroupSelectionComponent implements OnInit {
cognitoCode: string;
constructor(
private DbService: DbService,
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.queryParams
.subscribe(params => {
console.log(params);
console.log(params.code);
this.cognitoCode = params.code;
});
this.DbService.getIss(this.cognitoCode).subscribe(
iss => this.iss = iss
);
}
In the code you will see I am passing the congitocode to the dbservice for getIss.
db.service
getIss(cognitoCode ): Observable<Issuer[]> {
const url = hosturl +'i_l';
// let header: HttpHeaders = new HttpHeaders();
const httpOptions = {
headers: new HttpHeaders({
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Authorization': cognitoCode
})
};
let params = new HttpParams()
console.log(httpOptions.headers);
return this._http.get(url, httpOptions)
.pipe(
map((res) => {
console.log(res);
return <Issuer[]> res;
})
);
}
I then send the code as part of the headers of my GET request to the backend.
The GET then hits my backend router with these settings.
var authMiddleware = require('../middleware/AuthMiddleware.js');
router.get('/i_l', authMiddleware.Validate, i_l.get);
This will then call my authMiddleware which takes the code provided by Cognito Hosted UI and use a POST against oauth2/token to get my JWT token.
That token is then parsed used to compare to the https://cognito-idp.us-east-2.amazonaws.com/REMOVED/.well-known/jwks.json for congnito.
Once validated the request continues and I get data back from the backend.
// POST that trades the code for a token with cognito
var options = {
'method': 'POST',
'url': 'https://REMOVED.amazoncognito.com/oauth2/token',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'grant_type': 'authorization_code',
'client_id': 'CLIENTIDREMOVED',
'code': req.headers['authorization'],
'redirect_uri': 'http://localhost/group-selection'
}
};
// First request gets the JSON request of the token using the POST above
request(options, function (error, response) {
if (error) throw new Error(error);
token = JSON.parse(response.body).access_token;
//localStorage.setItem('token', token);
// request pull down status based on validitiy of token
request({
url : `https://cognito-idp.us-east-2.amazonaws.com/REMOVED/.well-known/jwks.json`,
json : true
}, function(error, response, body){
console.log('token: ' + token);
if (!error && response.statusCode === 200) {
pems = {};
var keys = body['keys'];
for(var i = 0; i < keys.length; i++) {
var key_id = keys[i].kid;
var modulus = keys[i].n;
var exponent = keys[i].e;
var key_type = keys[i].kty;
var jwk = { kty: key_type, n: modulus, e: exponent};
var pem = jwkToPem(jwk);
pems[key_id] = pem;
}
var decodedJwt = jwt.decode(token, {complete: true});
if (!decodedJwt) {
console.log("Not a valid JWT token");
res.status(401);
return res.send("Not a valid JWT token");
}
var kid = decodedJwt.header.kid;
var pem = pems[kid];
if (!pem) {
console.log('Invalid token - decodedJwt.header.kid');
res.status(401);
return res.send("Invalid token - decodedJwt.header.kid");
}
jwt.verify(token, pem, function(err, payload) {
if(err) {
console.log("Invalid Token - verify");
res.status(401);
return res.send("Invalid token - verify");
} else {
console.log("Valid Token.");
return next();
}
});
} else {
console.log("Error! Unable to download JWKs");
res.status(500);
return res.send("Error! Unable to download JWKs");
}
});
});
Quesiton -- how I set this up so that the Token I get back continues for the user?
If I understand your question properly then you are trying to validate all your apis through cognito user right?
Then you just need to do two things.
Add in header JWT token once you are getting after login. Just store into your application scope and pass everytime whenever any API is calling.
Auth.signIn(data.username, data.password)
.then(user => {
let jwkToken = user.getSignInUserSession().getAccessToken().getJwtToken();
// Store above value in singletone object or application scope.
})
.catch(err => {
//error
});
Now When API is calling pass jwkToken as header.
Then Go AWS ApiGateWay Console and add into Authorizers.
I am working on a web-app using node.js and vue.js, I am doing authentication and maintaining session using jwt and passport.js using passport-jwtstrategy
I have done all the things from creating jwt to protecting routes all the things now my issue is while generating jwt I am passing expiresIn:3600 so I want to auto-logout my user from Ui and remove token from localStorage once it has been one hour
On decoding my jwt I am getting
{
"name": "Dheeraj",
"iat": 1571896207,
"exp": 1571899807
}
So how can I get the real-time when to logout
In my auth.js vue store file my logout code when user clicks on logout is
logout({ commit }) {
return new Promise((resolve, reject) => {
localStorage.removeItem('jwt-token')
localStorage.removeItem('user-name')
commit('setAuthUser', null)
resolve(true)
})
},
In the same file, I have a method getAuthUser which is running whenever a page is loading or reloading to check to protect rout and guestUser
getAuthUser({ commit, getters }) {
const authUser = getters['authUser']
const token = localStorage.getItem('jwt-token')
const isTokenValid = checkTokenValidity(token)
if (authUser && isTokenValid) {
return Promise.resolve(authUser)
}
commit('setAuthUser', token)
commit('setAuthState', true)
debugger
return token
}
So how can I logout once my token is expired
Anyone out here please guide me how can I logout once the token is expired
Edit
In my router.js file
router.beforeEach((to, from, next) => {
store.dispatch('auth/getAuthUser')
.then((authUser) => {
const isAuthenticated = store.getters['auth/isAuthenticated']
if (to.meta.onlyAuthUser) {
if (isAuthenticated) {
next()
} else {
next({ name: 'login' })
}
} else if (to.meta.onlyGuestUser) {
if (isAuthenticated) {
next({ name: 'welcome' })
} else {
next()
}
} else {
next()
}
})
})
from my auth file I am calling get authUser which I have already mention above
for checking token validity I am using this code
function checkTokenValidity(token) {
if (token) {
const decodedToken = jwt.decode(token)
return decodedToken && (decodedToken.exp * 1000) > new Date().getTime()
}
return false
}
but it returns false when I am on login page and there is no token there but once I am loged in it shows null
My global api file
import axios from 'axios';
export default () => {
let headers = {
'cache-control': 'no-cache'
};
let accessToken = localStorage.getItem('jwt-token');
if (accessToken && accessToken !== '') {
headers.Authorization = accessToken;
};
return axios.create({
baseURL: 'http://localhost:8086/',
headers: headers
});
}
Refer to the axios documentataion: https://github.com/axios/axios
import axios from 'axios';
export default () => {
let headers = {
'cache-control': 'no-cache'
};
let accessToken = localStorage.getItem('jwt-token');
if (accessToken && accessToken !== '') {
headers.Authorization = accessToken;
};
const instance = axios.create({
baseURL: 'http://localhost:8086/',
headers: headers
});
instance.interceptors.response.use((response) => {
if(response.status === 401) {
//add your code
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
//add your code
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});
return instance;
}
Can't understand the nodejs Authentication (KoaJS2).
I have this piece of code :
router.post("/login", koaBody, (ctx, next) => {
const data = ctx.request.body;
db
.any("SELECT * FROM users ORDER BY id ASC")
.then(function(data) {
token = jwt.sign(data, token_secret, {
expiresIn: "24h" // expires in 24 hours
});
console.log(ctx.request.header);
// ctx.set("token", `test`);
})
.catch(function(error) {
// error;
});
Where is the token stored after I sign it?
There is no "Authentication" header...
On the server side you neary to the right thing. What you need to do: return back the token to the client:
router.post("/login", koaBody, (ctx, next) => {
const data = ctx.request.body;
// ...
// extract username and password from body
// ...
db
.any("SELECT * FROM users ORDER BY id ASC") // here using and checking credentials is also missing ;-)
.then(function(data) {
// create the token
token = jwt.sign(data, token_secret, {
expiresIn: "24h" // expires in 24 hours
});
// now you have to pass it back to the client
// the token is NOT stored on the server side!
ctx.body = { 'token': token }
})
.catch(function(error) {
// error;
});
On the client side - if you get back a token in the body - you store it e.g. in the local storage
I am using angular 2 on the client side, here the code (client side login service) could look like this:
login(credentials) {
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
const reqopt: RequestOptions = new RequestOptions({
headers: headers
});
// credentials: username, password --
return this.http.post(...your_api_endpoint_ + '/login', credentials, reqopt)
.map(res => {
const data = res.json();
if (data && data.token) {
localStorage.setItem('token', data.token); // <--- here you store your token
}
});
}
Each time you now hit your API again, do not forget to provide you token in the header and check it on the server side (JWT.verify()).
Here you can find a more general introduction to JWT:
https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec
Hope that helps.