Offline/Online Sync in a Real World Ionic Application with CouchDB
This is supposed to be a longer description
Login and Registration
Enabling user registration and authentication in the application
PROModule Outline
- Source Code & Resources PRO
- Lesson 1: Introduction PUBLIC
- Lesson 2: Application Requirements PUBLIC
- Lesson 3: A Brief Introduction to NoSQL PUBLIC
- Lesson 4: Introduction to CouchDB PRO
- Lesson 5: Introduction to PouchDB PRO
- Lesson 6: Structuring Data in CouchDB PRO
- Lesson 7: Installing CouchDB Locally PRO
- Lesson 8: Adding Data to Futon PRO
- Lesson 9: Starting the Application PRO
- Lesson 10: Setting up the Basic User Interface PRO
- Lesson 11: Using Design Documents to Create Views in CouchDB PRO
- Lesson 12: Getting Data From CouchDB into Ionic PRO
- Lesson 13: Using Node, Express, and SuperLogin PRO
- Lesson 14: Login and Registration PRO
- Lesson 15: Offline Access and Reauthentication PRO
- Lesson 16: Advanced Form Validation PRO
- Lesson 17: Restricting Document Updates PRO
- Lesson 18: Filtering Data from CouchDB PRO
- Lesson 19: Improving User Experience PRO
- Lesson 20: Migrating to Production PRO
- Lesson 21: Conclusion PRO
Lesson Outline
Login and Registration
In the last lesson, we set up a simple Node/Express server that uses SuperLogin to facilitate user registration and authentication. In the same way that we can interact directly with CouchDB using an HTTP API (but we use PouchDB API instead), we can also interact with SuperLogin's functionality through HTTP requests.
We are going to walk through implementing this in our application in this lesson, but to give you the basic idea, if we wanted to create a new user we would make a POST
request like this:
this.http.post(SERVER_ADDRESS + 'auth/register', details);
or if we wanted to validate that a particular username was valid, we would make a GET
request like this:
return this.http.get(SERVER_ADDRESS + 'auth/validate-username/' + username);
In this lesson, we will be creating two new services to help us deal with the integration with SuperLogin, and then we will also be adding two new pages to handle logging in and account creation.
User Service
We are going to create a User
service that we will be able to use throughout the application to access the currently logged in user's details. This is going to be a very simple service, all it will do is keep a reference to the currently logged in users data, and save and fetch that data from storage.
As we did for our Chat
and Notice
types, we will also create an interface for a User
.
Create a file at src/app/interfaces/user.ts and add the following:
export interface User {
issued: number;
expires: number;
provider: string;
ip: string;
token: string;
password: string;
user_id: string;
roles: string[];
userDBs: {
['hangz-app']: string;
};
}
NOTE: We are using ["hangz-app"]
here because our database name is hyphenated, otherwise we could have just done something like hangz: string
If you are wondering how I came up with these properties for the User
object, you can see the structure of the data that is returned by SuperLogin in the documentation. A response from SuperLogin might look like this:
{
"issued": 1440232999594,
"expires": 1440319399594,
"provider": "local",
"ip": "127.0.0.1",
"token": "aViSVnaDRFKFfdepdXtiEg",
"password": "p7l9VCNbTbOVeuvEBhYW_A",
"user_id": "joesmith",
"roles": ["user"],
"userDBs": {
"supertest": "http://aViSVnaDRFKFfdepdXtiEg:p7l9VCNbTbOVeuvEBhYW_A@localhost:5984/supertest$joesmith"
}
}
Create the service with the following command:
ionic g service services/User
Modify src/app/services/user.service.ts to reflect the following:
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage-angular';
import { User } from '../interfaces/user';
@Injectable({
providedIn: 'root',
})
export class UserService {
public currentUser: User;
private _storage: Storage;
private storageInitialised: boolean = false;
constructor(private storage: Storage) {}
async init() {
const storage = await this.storage.create();
this._storage = storage;
this.storageInitialised = true;
}
async saveUserData(data: User): Promise<void> {
if (!this.storageInitialised) {
await this.init();
}
this.currentUser = data;
this._storage.set('hangzUserData', data);
}
async getUserData(): Promise<User> {
if (!this.storageInitialised) {
await this.init();
}
return this._storage.get('hangzUserData');
}
}
We have a class member called currentUser
that we will use to store the currently logged in user's data, and we will be able to access that from anywhere in the application directly through this provider.
We also have a saveUserData
method that we will pass the user's data into, this will set the currentUser
member and also save the data to local storage. The getUserData
method will retrieve any existing user data that has been saved to storage.
Auth Service
Now we are going to create the Auth
service that will handle the integration with SuperLogin, this one is a little bit more complex but it's still reasonably straightforward - SuperLogin already does most of the heavy lifting for us.
Create the provider with the following command:
ionic g service services/Auth
We are going to create a couple of new interfaces now as well. We will create an interface to represent the data object containing a user's credentials when they are attempting to log in, and an interface to represent the data required to create a new user account.
Create a file at src/app/interfaces/credentials.ts and add the following:
export interface Credentials {
username: string;
password: string;
}
Create a file at src/app/interfaces/registration-details.ts and add the following:
export interface RegistrationDetails {
username: string;
email: string;
password: string;
confirmPassword: string;
}
Modify src/app/services/auth.service.ts to reflect the following:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NavController } from '@ionic/angular';
import { UserService } from './user.service';
import { DataService } from './data.service';
import { SERVER_ADDRESS } from '../../environments/environment';
import { Observable } from 'rxjs';
import { Credentials } from '../interfaces/credentials';
import { RegistrationDetails } from '../interfaces/registration-details';
import { User } from '../interfaces/user';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(
private http: HttpClient,
private userService: UserService,
private dataService: DataService,
private navCtrl: NavController
) {}
authenticate(credentials: Credentials): Observable<User> {
return this.http.post<User>(`${SERVER_ADDRESS}/auth/login`, credentials);
}
async logout(): Promise<void> {
const headers = new HttpHeaders();
headers.append(
'Authorization',
`Bearer ${this.userService.currentUser.token}:${this.userService.currentUser.password}`
);
this.http
.post(`${SERVER_ADDRESS}/auth/logout`, {}, { headers: headers })
.subscribe(() => {});
try {
await this.dataService.db.destroy();
this.dataService.db = null;
this.userService.saveUserData(null);
this.navCtrl.navigateRoot('/login');
} catch (err) {
console.log(err);
console.log('could not destroy db');
}
}
register(details: RegistrationDetails): Observable<User> {
return this.http.post<User>(`${SERVER_ADDRESS}/auth/register`, details);
}
validateUsername(username: string): Observable<Object> {
return this.http.get(
`${SERVER_ADDRESS}/auth/validate-username/${username}`
);
}
validateEmail(email: string): Observable<Object> {
const encodedEmail = encodeURIComponent(email);
return this.http.get(
`${SERVER_ADDRESS}/auth/validate-email/${encodedEmail}`
);
}
}
Although this is not the limit of what SuperLogin can do, this service facilitates all of the integrations we need which are:
- Logging In
- Logging out
- Account Creation
- Validating usernames
- Validating emails
Thanks for checking out the preview of this lesson!
You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).