Hello Community, Today I am writing 2 part of this article series in which I am trying to clone dev.to with minimum feature.It will be a prototype, in which user can signUp/signIn, create post & other features.
I am doing it for just learning purpose.
If you have not visited the first Part, please understand it first Part-1
Technology Stack:
NodeJs, ReactJs, Graphql, TypeOrm,TypeGraphql, Typescript, JWT, Apollo-server-express, Jest, Apollo-client, Apollo-link & much more..
In second part of this series, I am able to make logic for Refreshing tokens, Authenticated middlewares for backend server. Additionally, I have also make my setup for React frontEnd with Apollo-Client, React-Bootstrap, Styled-components, Graphql-CodeGen && much more.
For ease, I have made 2 branches in my Git Repository to understand things more clearly :
* Part-4 Add authentication for graphql API and revoking refresh token logic
* Part-5 FrontEnd & backend setup
Part-4(GitHub Branch)
In this branch, We have done mainly 4 things which are as below :
Token Version : This 'tokenVersion' is saved with each user with default value of 0. It will be helpful to identify the authentication for Refresh-tokens, how many times user loggedIn and other things like ending all sessions of user.
//For authentication, I am changing createRefreshToken method.
export const createRefreshToken = (user: User) => {
return sign(
{ userId: user.id, tokenVersion: user.tokenVersion },
process.env.REFRESH_TOKEN_SECRET!,
{
expiresIn: "7d"
}
);
Refresh Token : As we know, when we hit 'loginMutation' query then server send us 'Refresh token' back as cookies. Now picture this, If a user loggedIn our dev.to clone and refresh the page then we need to make a mechanism to make it loggedIn and provide all services which he/she is authenticated for. To make this possible, I am making a 'POST' api at our server which accepts cookies for the refresh-token and then it verifies it. If refresh-token verified successfully at server end then we again send the latest 'refresh-token' and 'access-token' to user so that user have not to login again and again unnecessarily.
app.use(cookieParser());
app.post("/refresh_token", async (req, res) => {
const token = req.cookies.devId;
if (!token) {
console.log("token is not valid " + token);
return res.send({ ok: false, accessToken: "" });
}
let payload: any = null;
try {
payload = await verify(token, process.env.REFRESH_TOKEN_SECRET!);
} catch (err) {
console.log(err);
return res.send({ ok: false, accessToken: "" });
}
console.log("payload :: " + payload.userId);
//token is valid and we can send him access token now.abnf
const user = await User.findOne({ id: payload.userId });
if (!user) {
console.log("User not found");
return res.send({ ok: false, accessToken: "" });
}
if (user.tokenVersion !== payload.tokenVersion) {
return res.send({ ok: false, accessToken: "" });
}
//Referesh Token
res.cookie("devId", createRefreshToken(user), {
httpOnly: true
});
return res.send({ ok: true, accessToken: createAccessToken(user) });
});
Steps to follow :
1) Login and get RefreshToken.
2) Use postman for hitting REST API, and set refreshToken as cookie in it.
3) Get the new AccessToken based on refresh-token.
Authentication Middleware :
Suppose we have some graphql queries which we want to be available for authenticated user only. To achieve this task, I am using middlewares from 'type-graphql'.
@Query(() => String)
@UseMiddleware(isAuth) //Below is implementation
me(@Ctx() { payload }: MyContext) {
return `${payload!.userId}`;
}
//isAuth.ts
export const isAuth: MiddlewareFn<MyContext> = ({ context }, next) => {
const authorization = context.req.headers["authorization"];
if (!authorization) {
throw new Error("Not Authenticated");
}
try {
const token = authorization.split(" ")[1];
const payload = verify(token, process.env.ACCESS_TOKEN_SECRET!);
context.payload = payload as any;
} catch (err) {
console.error(err);
throw new Error("Not Authenticated");
}
return next();
};
Revoking Refresh-token :
Picture this, Your "refresh-token" is not expired & you "forgot-password", then you dont want that anyone can authenticate himself for protected graphql queries or simple remove all its loggedIn sessions, then you can update the tokenVersion of the specific user so that he needs to verify himself with refreshTokens' tokenVersion.
@Mutation(() => Boolean)
async revokeRefreshToken(@Arg("userId", () => Int) userId: number) {
await getConnection()
.getRepository(User)
.increment({ id: userId }, "tokenVersion", 1);
return true;
}
Part-5 Branch
In this branch, We have setup our react frontend app with command
"npx create-react-app devto --typescript". After installation add following modules :
yarn add apollo-boost @apollo/react-hooks graphql
yarn add -D @types/graphql
** Update your app.tsx file **
import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "@apollo/react-hooks";
import "bootstrap/dist/css/bootstrap.min.css";
import App from "./App";
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
credentials: "include"
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root1")
);
Add graphql/CodeGen
yarn add -D @graphql-codegen/cli
//then
npx graphql-codegen init
Make your first graphql Query
query Hello {
hello
}
useHelloQuery() in app.tsx
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { HeaderComponent } from "./components/Header/header.component";
import { GlobalStyle } from "./ui_components/GlobalStyle";
import { ColStyle, RowStyle } from "./ui_components/RowColStyle";
import { RegisterComponent } from "./components/User/Register.component";
import { useHelloQuery } from "./generated/graphql";
const App: React.FC = () => {
const { data, loading } = useHelloQuery();
if (loading || !data) {
return <div>...</div>;
}
return (
<>
<GlobalStyle></GlobalStyle>
<RowStyle>
<ColStyle md={12}>
<HeaderComponent />
</ColStyle>
</RowStyle>
<RowStyle>
<ColStyle md={12}>
<Router>
<Switch>
<Route exact path="/" render={() => <div>{data.hello}</div>} />
<Route exact path="/register" component={RegisterComponent} />
</Switch>
</Router>
</ColStyle>
</RowStyle>
</>
);
};
export default App;
Hope you will like this article, I will come back soon with more code :).
Top comments (1)
Could you like add this into a series for a easier access for each part