Blog Series: Building a Single Page Application (SPA) with Angular and Rails

Raghavendra S

This 6-part blog series takes you through building a single-page application (SPA) using Angular for the frontend and Ruby on Rails for the backend. By the end of this series, you’ll have a fully functional SPA and understand the integration of these two frameworks.

Part 4: Adding Authentication

Step 1: Add Devise to Rails

Install Devise for authentication:

bundle add devise
rails generate devise:install
rails generate devise User
rails db:migrate

Step 2: Add Angular Authentication

Add devise-jwt to the Gemfile:

bundle add devise-jwt

Update the User model to include JWT support:

class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
end

Generate the JWT denylist model:

rails generate model JwtDenylist jti:string:index exp:datetime:index
rails db:migrate

Update config/initializers/devise.rb to include JWT settings:

config.jwt do |jwt|
jwt.secret = Rails.application.credentials.devise[:jwt_secret_key]
jwt.dispatch_requests = [
['POST', %r{^/login$}]
]
jwt.revocation_requests = [
['DELETE', %r{^/logout$}]
]
end

Step 3: Create Authentication Endpoints

Add routes for login and logout:

Rails.application.routes.draw do
devise_for :users, controllers: {
sessions: 'users/sessions'
}
end

Generate a controller for sessions:

rails generate controller users/sessions

Update users/sessions_controller.rb:

class Users::SessionsController < Devise::SessionsController
respond_to :json

private

def respond_with(resource, _opts = {})
render json: { message: 'Logged in successfully.', user: resource }, status: :ok
end

def respond_to_on_destroy
render json: { message: 'Logged out successfully.' }, status: :ok
end
end

Step 4: Add Angular Authentication

ng generate component login

Update login.component.ts:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment';

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
email: string = '';
password: string = '';

constructor(private http: HttpClient) {}

login() {
this.http.post(`${environment.apiUrl}/login`, {
user: { email: this.email, password: this.password }
}).subscribe(response => {
console.log('Login successful', response);
localStorage.setItem('token', response['jwt']);
});
}
}

Update login.component.html:

<div>
<h2>Login</h2>
<form (ngSubmit)="login()">
<label>Email</label>
<input [(ngModel)]="email" name="email" required>

<label>Password</label>
<input [(ngModel)]="password" name="password" type="password" required>

<button type="submit">Login</button>
</form>
</div>

continued in part 5

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Raghavendra S
Raghavendra S

Written by Raghavendra S

Artificial enthusiast. Rubyist.

No responses yet

Write a response