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

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