# Secure Authentication with JWT and Two-factor authentication in Angular & Node.js

In this article, our main focus will be on creating Angular authentication using JWT Tokens and 2FA. We'll use Angular for the client-side and Node.js for the server-side implementation.

First, let's create a new app with initial routing configuration

`ng new ng-auth-app --style=scss --routing`

Next, generate the Login component `ng g c components/login`

Let's add logic to the Login component

```typescript
export class LoginComponent {
  loginForm!: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.required],
      password: ['', Validators.required],
    });
  }

  onSubmit() {
    console.log(this.loginForm.value);
  }
}
```

```html
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <div>
    <label for="username">Username:</label>
    <input id="username" formControlName="username" type="text" />
    <div
      *ngIf="
        loginForm.controls['username'].touched &&
        loginForm.controls['username'].errors
      "
    ></div>
  </div>
  <div>
    <label for="password">Password:</label>
    <input id="password" formControlName="password" type="password" />
    <div
      *ngIf="
        loginForm.controls['username'].touched &&
        loginForm.controls['password'].errors
      "
    ></div>
  </div>
  <button type="submit">Login</button>
</form>
```

and run `ng serve -o`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712653425293/6c86b05a-fadf-4ab3-870b-622c3eb0ecbb.png align="center")

Also, we'll apply styling to our form

```scss
form {
  display: flex;
  flex-direction: column;
  width: 300px;
  margin: 0 auto;
}

input {
  margin-bottom: 10px;
  padding: 10px;
  border-radius: 5px;
  border: 1px solid #ccc;
}

button {
  padding: 10px;
  border-radius: 5px;
  border: none;
  background-color: #007bff;
  color: white;
  cursor: pointer;
}
```

and save the changes:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712653780397/fccf86a7-506a-4d0d-a6d3-98603b70e34a.png align="center")

Now let's create our service for login

```typescript
export class LoginService {
  private apiUrl = environment.apiUrl;

  constructor(private http: HttpClient) {}

  login(user: { username: string; password: string }) {
    return this.http.post<LoginResponse>(`${this.apiUrl}/login`, {
      username: user.username,
      password: user.password,
    });
  }
}
```

and change Submit method in LoginComponent

```typescript
this.loginService.login(this.loginForm.value).subscribe((res: LoginResponse) => {
      if (res.code === 200) {
        localStorage.setItem('token', res.token);
        this.router.navigate(['/success']);
      } else {
        console.error(res.message);
      }
    });
```

It's time to proceed with the server-side implementation. Let's create a new file named server.js and add code

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const jwt = require("jsonwebtoken");
require("dotenv").config(); 

const app = express();
const cookieParser = require("cookie-parser");
app.use(cookieParser());
app.use(cors());
app.use(bodyParser.json());

app.post("/login", (req, res) => {
  const { username, password } = req.body;
  if (username === "admin" && password === "admin") {
    const token = jwt.sign({ username }, process.env.JWT_SECRET, {
      expiresIn: "1h",
    });
    res.json({ code: 200, message: "Login successful", token });
  } else {
    res.json({ code: 401, message: "Invalid username or password" });
  }
});

app.listen(3000, () => console.log("Server is running on port 3000"));
```

To generate a JWT token, you need to install the jsonwebtoken package

`npm install jsonwebtoken` and `npm install cors` to enable CORS. It's also good practice to create a `.env` file (a configuration file where we can store environment-specific variables) and store our JWT\_SECRET there.

```json
JWT_SECRET=your_secret_key
```

After a successful login attempt, we'll be routed to the Success component and receive the token

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712667396538/fe307de7-73bd-4bff-a370-c185443e4091.png align="center")

Although storing JWT tokens in local storage can be risky and it's recommended to use HttpOnly cookies for token storage, for the purpose of our learning exercise, we'll proceed with this approach.

Now it's time to add two-factor authentication for our loginComponent. We'll use 2FA with SMS using Twilio.

First we need to install twilio `npm install twilio` and store the data from twilio account in our .env file

```json
JWT_SECRET=your_secret_key
TWILIO_ACCOUNT_SID=your_twilio_account_sid
TWILIO_AUTH_TOKEN=your_twilio_aut_toke
TWILIO_PHONE_NUMBER=your_phone_number
```

These variables can be accessed in our code using `process.env.VARIABLE_NAME`

Next, we initialize the Twilio client using our account SID and authentication

```javascript
const client = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
```

create the POST request that sends sms message

```javascript
server.post('/send-sms', (req, res) => {
  const { phoneNumber, message } = req.body;

  client.messages
    .create({
      body: message,
      from: process.env.TWILIO_PHONE_NUMBER,
      to: phoneNumber,
    })
    .then((message) => res.status(200).json({ message: 'SMS sent successfully' }))
    .catch((err) => res.status(500).json({ error: err.message }));
});
```

and additionally, we need to create a method `send2FACodeSMS` in our service

```typescript
private send2FACodeSMS(code: number) {
    const phoneNumber = environment.phoneNumber;
    const message = `Your 2FA code is ${code}`;

    this.http.post(`${this.apiUrl}/send-sms`, { phoneNumber, message }).subscribe();
  }
```

and call it in the tap operator in our `login` method for cleaner code

```typescript
login(user: { username: string; password: string }) {
    return this.http.post<LoginResponse>(`${this.apiUrl}/login`, user).pipe(
      tap((res) => {
        this.send2FACodeSMS(res.code);
      })
    )
  }
```

The final step involves adding verification handling in the `LoginComponent` to ensure a secure login process.

```typescript
onSubmit() {
    this.loginService
      .login(this.loginForm.value)
      .subscribe((res: LoginResponse) => {
        if (res && res.token) {
          // Store the token temporarily
          localStorage.setItem('tempToken', res.token);
          // Prompt the user to enter the 2FA code
          const code = Number(window.prompt('Please enter your 2FA code'));
          // Verify the 2FA code
          if (code === res.twoFAcode) {
            localStorage.setItem('token', res.token);
            this.router.navigate(['/success']);
          } else {
            localStorage.removeItem('tempToken');
            alert('Invalid 2FA code. Please try again.');
          }So, now after login attemp we will be asked about the code that is sent to defined phone number 
        }
      });
  }
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712739976555/2d323b53-8665-451c-9389-b0b52b7c7ad0.png align="center")

After attempting to log in, we will be prompted to enter the code sent to our registered phone number

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712740710487/38d49352-f28b-4763-a27b-bd96d4b57a94.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712740076910/c86a7aeb-0e31-42ba-b5b3-926ee292c256.png align="center")

Once entered, we'll be successfully logged in 🙂

One more thing remains to be done. Storing tokens in localStorage is not the best idea due to potential security risks, so we will change our app to store the token in HTTP-only cookies. In our POST request when user logs in successfully, we should set a cookie with the JWT token

```javascript
res.cookie("token", token, { httpOnly: true });
```

In LoginService we need to make sure that our HTTP requests include credentials so that cookies can be sent and received

```typescript
login(user: { username: string; password: string }) {
    return this.http
      .post<LoginResponse>(`${this.apiUrl}/login`, user, {withCredentials: true})
      .pipe(
        tap((res) => {
          this.send2FACodeSMS(res.twoFAcode);
        })
      );
  }
```

And finally, in our [`LoginComponent`](vscode-file://vscode-app/c:/Users/danil/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html), we no longer need to manually store the token in local storage because the browser will automatically send the cookie with each request

```typescript
this.loginService
      .login(this.loginForm.value)
      .subscribe((res: LoginResponse) => {
        if (res && res.token) {
          //localStorage.setItem('tempToken', res.token); // Remove this line
          // Prompt the user to enter the 2FA code
          const code = Number(window.prompt('Please enter your 2FA code'));

          // Verify the 2FA code
          if (code === res.twoFAcode) {
            //localStorage.setItem('token', res.token); // Remove this line
            this.router.navigate(['/success']);
          } else {
            //localStorage.removeItem('tempToken');  // Remove this line
            alert('Invalid 2FA code. Please try again.');
          }
        }
      });
```

And with that, we're all set 👍

In summary, combining JWT and Two-factor authentication provides solid security measures, ensuring a safe and reliable authentication process.
