Building an Ionic JWT Refresh Token Flown

img
akash.png
Akash MoreSoftware Developerauthor linkedin
Published On
Updated On
Table of Content
up_arrow

Introduction

JSON Web Tokens (JWT) have become a standard method for authentication in modern web and mobile applications. However, managing token lifecycle and security can be challenging. In this blog post, we'll dive deep into implementing a robust JWT refresh token flow in an Ionic application.

Why Use JWT for Authentication?

  • Stateless Authentication: No need for the server to maintain session data.
  • Compact Tokens: JWTs are lightweight and can be sent via HTTP headers.
  • Security: JWTs are signed, ensuring data integrity and protection against tampering.

Understanding JWT and Refresh Tokens A JSON Web Token is a compact, URL-safe means of representing claims to be transferred between two parties. It consists of three parts:

  • Header: Contains the token type and hashing algorithm
  • Payload: Stores claims or user information
  • Signature: Ensures the token's integrity

Authentication Flow Overview:

  • Registration: Users create an account by providing a username and password.
  • Login: Users log in with their credentials to receive an access token and a refresh token.
  • Token Storage: Access and refresh tokens are stored securely on the client-side.
  • Token Refresh: The client uses the refresh token to obtain a new access token when it expires.
  • Logout: Clears tokens and ends the session.
  • Install Required Packages:

    • axios: For making API requests.
    • @ionic/storage-angular: For secure local storage of tokens.
    • jwt-decode: To decode JWTs and check expiration.

    npm install axios @ionic/storage-angular jwt-decode

    Backend Setup (JWT Generation and Validation)

    Ensure your backend API is configured to generate and validate JWT tokens. Here’s a basic Node.js example for reference

    Key points:

    1. Password Hashing: Using bcrypt for secure password storage.
    2. JWT Tokens: Short-lived access tokens (15m) and long-lived refresh tokens (7d).

    1. Express js setup:

    We use Express.js to create the server and handle API routes. app.use(cors()) allows cross-origin requests from the Ionic app.

    2. User Registration (/api/register):

    • We hash the password using bcrypt for security before storing it.
    • bcrypt.hash(password, 10) creates a hashed version of the password with 10 salt rounds for added security.

    3.Login (/api/login):

    • Checks if the user exists and verifies the password.
    • Generates two JWTs: Access Token & Refresh Token.

    4.Token Refresh (/api/refresh):

    • Verifies the refresh token and issues a new access token.
    const express = require('express');
    const jwt = require('jsonwebtoken');
    const cors = require('cors');
    const bcrypt = require('bcrypt');

    const app = express();
    app.use(express.json());
    app.use(cors());

    const SECRET_KEY = 'your_secret_key';
    const users = []; // In-memory user storage (replace with a database in production)

    // Register Endpoint
    app.post('/api/register', async (req, res) => {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    users.push({ username, password: hashedPassword });
    res.status(201).send('User registered successfully');
    });

    // Login Endpoint
    app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;
    const user = users.find((u) => u.username === username);
    if (user && (await bcrypt.compare(password, user.password))) {
    const accessToken = jwt.sign({ username }, SECRET_KEY, { expiresIn: '15m' });
    const refreshToken = jwt.sign({ username }, SECRET_KEY, { expiresIn: '7d' });
    res.json({ accessToken, refreshToken });
    } else {
    res.status(401).send('Invalid credentials');
    }
    });

    // Refresh Token Endpoint
    app.post('/api/refresh', (req, res) => {
    const { refreshToken } = req.body;
    if (!refreshToken) return res.sendStatus(403);
    jwt.verify(refreshToken, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403);
    const newAccessToken = jwt.sign({ username: user.username }, SECRET_KEY, { expiresIn: '15m' });
    res.json({ accessToken: newAccessToken });
    });
    });

    app.listen(3000, () => console.log('Server running on port 3000'));
    1. Express Server Setup:

      • express() initializes the app.
      • app.use(cors()) allows cross-origin requests from the Ionic client.
      • app.use(express.json()) parses incoming JSON payloads.
    2. Registration Endpoint (/api/register):

      • bcrypt hashes passwords before storing them, ensuring sensitive data is secure.
      • bcrypt.hash(password, 10) adds a salt to strengthen password security.
    3. Login Endpoint (/api/login):

      • Users provide a username and password.
      • If credentials are valid, two JWTs are issued:
    4. Refresh Endpoint (/api/refresh):

      • Takes a refresh token and validates it using jwt.verify().
      • Issues a new access token to maintain the session without requiring re-login.

    Integrating AuthService in Ionic App

    Create an AuthService to manage registration, login, token storage, and refreshing tokens using Ionic Storage.

    1. Dependency Injection: We inject Storage to store tokens locally on the device.
    2. register(): Sends the registration data to the backend.
    3. login(): Stores accessToken and refreshToken in Ionic Storage after successful login.
    4. logout(): Clears stored tokens from the device.
    import { Injectable } from '@angular/core';  
    import axios from 'axios';
    import { Storage } from '@ionic/storage-angular';

    @Injectable({ providedIn: 'root' })
    export class AuthService {
    private API_URL = 'http://localhost:3000/api';
    private storage: Storage | null = null;

    constructor(private storageCtrl: Storage) {
    this.init();
    }

    async init() {
    this.storage = await this.storageCtrl.create();
    }

    // Register
    async register(username: string, password: string) {
    try {
    await axios.post(`${this.API_URL}/register`, { username, password });
    console.log('User registered successfully');
    } catch (error) {
    console.error('Registration failed:', error.response.data);
    }
    }

    // Login
    async login(username: string, password: string) {
    try {
    const response = await axios.post(`${this.API_URL}/login`, { username, password });
    await this.storage?.set('accessToken', response.data.accessToken);
    await this.storage?.set('refreshToken', response.data.refreshToken);
    console.log('Login successful');
    } catch (error) {
    console.error('Login failed:', error.response.data);
    }
    }

    // Logout
    async logout() {
    await this.storage?.remove('accessToken');
    await this.storage?.remove('refreshToken');
    }
    }
    1. Storage Initialization:

      • We initialize Ionic Storage to store tokens securely on the client device.
    2. register() Method:

      • Calls the backend /register endpoint to create a new user.
    3. login() Method:

      • Sends the username and password to the backend /login endpoint.
      • Stores both the access and refresh tokens locally.
    4. refreshAccessToken():

      • Retrieves the refresh token and uses it to request a new access token when the current one expires.
    5. logout() Method:

      • Clears the stored tokens, effectively logging the user out.

    Building the Registration Page in Ionic

    Now create the RegisterPage that will allow users to sign up.

    1. Form Data Binding:

      • [(ngModel)] binds the input fields to username and password properties.
    2. onRegister() Method:

      • Calls AuthService.register() with the provided credentials.

    <ion-header>  
    <ion-toolbar>
    <ion-title>Register</ion-title>
    </ion-toolbar>
    </ion-header>

    <ion-content>
    <form (submit)="onRegister()">
    <ion-item>
    <ion-label position="floating">Username</ion-label>
    <ion-input type="text" [(ngModel)]="username" name="username"></ion-input>
    </ion-item>
    <ion-item>
    <ion-label position="floating">Password</ion-label>
    <ion-input type="password" [(ngModel)]="password" name="password"></ion-input>
    </ion-item>
    <ion-button expand="full" type="submit">Register</ion-button>
    </form>
    </ion-content>
    import { Component } from '@angular/core';  
    import { AuthService } from '../services/auth.service';

    @Component({ selector: 'app-register', templateUrl: './register.page.html' })
    export class RegisterPage {
    username: string = '';
    password: string = '';

    constructor(private authService: AuthService) {}

    async onRegister() {
    if (this.username && this.password) {
    await this.authService.register(this.username, this.password);
    console.log('Registration successful');
    } else {
    console.error('All fields are required');
    }
    }
    }

    Conclusion

    By the end of this guide, you've set up a fully functional Ionic authentication system using JWTs and Node.js. You now have a solid foundation for managing user sessions, refreshing tokens, and building secure Ionic applications.

    Schedule a call now
    Start your offshore web & mobile app team with a free consultation from our solutions engineer.

    We respect your privacy, and be assured that your data will not be shared