Skip to content

Commit b082e5d

Browse files
committed
Set up login form
1 parent 5c46348 commit b082e5d

9 files changed

Lines changed: 171 additions & 119 deletions

File tree

angular.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@
7474
},
7575
"serve": {
7676
"builder": "@angular-devkit/build-angular:dev-server",
77+
"options": {
78+
"proxyConfig": "./proxy.conf.json"
79+
},
7780
"configurations": {
7881
"production": {
7982
"browserTarget": "angular-http-basic-auth-example:build:production"
@@ -112,4 +115,4 @@
112115
}
113116
},
114117
"defaultProject": "angular-http-basic-auth-example"
115-
}
118+
}

proxy.conf.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"/api": {
3+
"target": "http://localhost:8080",
4+
"secure": true,
5+
"changeOrigin": true,
6+
"pathRewrite": {
7+
"^/api": ""
8+
}
9+
}
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { AuthenticatedGuard } from './authenticated.guard';
4+
5+
describe('AuthenticatedGuard', () => {
6+
let guard: AuthenticatedGuard;
7+
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({});
10+
guard = TestBed.inject(AuthenticatedGuard);
11+
});
12+
13+
it('should be created', () => {
14+
expect(guard).toBeTruthy();
15+
});
16+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@angular/core';
2+
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
3+
import { Observable } from 'rxjs';
4+
import { AuthenticationService } from '../service/authentication.service';
5+
6+
@Injectable({
7+
providedIn: 'root'
8+
})
9+
export class AuthenticatedGuard implements CanActivate {
10+
11+
constructor(
12+
private router: Router,
13+
private authenticationService: AuthenticationService
14+
) { }
15+
16+
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
17+
const user = this.authenticationService.userValue;
18+
if (user) {
19+
// logged in so return true
20+
return true;
21+
}
22+
23+
// not logged in so redirect to login page with the return url
24+
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
25+
return false;
26+
}
27+
28+
}
Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,47 @@
1+
import { HttpClient } from '@angular/common/http';
12
import { Injectable } from '@angular/core';
2-
import { HttpClient, HttpHeaders } from '@angular/common/http';
3-
import { Observable } from 'rxjs';
3+
import { Router } from '@angular/router';
44
import { environment } from '@environments/environment';
5+
import { BehaviorSubject, Observable } from 'rxjs';
6+
import { map } from 'rxjs/operators';
7+
import { User } from '../model/user';
58

69
@Injectable({
710
providedIn: 'root'
811
})
912
export class AuthenticationService {
1013

11-
private httpOptions = {
12-
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
13-
};
14+
private userSubject: BehaviorSubject<User>;
15+
public user: Observable<User>;
1416

15-
constructor(private http: HttpClient) { }
17+
constructor(
18+
private router: Router,
19+
private http: HttpClient
20+
) {
21+
this.userSubject = new BehaviorSubject<User>(JSON.parse(<string>localStorage.getItem('user')));
22+
this.user = this.userSubject.asObservable();
23+
}
24+
25+
public get userValue(): User {
26+
return this.userSubject.value;
27+
}
1628

17-
login(username: string, password: string): Observable<any> {
18-
return this.http.post(environment.apiUrl + '/api/auth/' + 'signin', {
19-
username,
20-
password
21-
}, this.httpOptions);
29+
login(username: string, password: string) {
30+
return this.http.post<any>(`${environment.apiUrl}/login`, { username, password })
31+
.pipe(map(user => {
32+
// store user details and basic auth credentials in local storage to keep user logged in between page refreshes
33+
user.authdata = window.btoa(username + ':' + password);
34+
localStorage.setItem('user', JSON.stringify(user));
35+
this.userSubject.next(user);
36+
return user;
37+
}));
2238
}
2339

24-
register(username: string, email: string, password: string): Observable<any> {
25-
return this.http.post(environment.apiUrl + 'signup', {
26-
username,
27-
email,
28-
password
29-
}, this.httpOptions);
40+
logout() {
41+
// remove user from local storage to log user out
42+
localStorage.removeItem('user');
43+
this.userSubject.next(new User());
44+
this.router.navigate(['/login']);
3045
}
46+
3147
}
Lines changed: 27 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,28 @@
1-
<div class="col-md-12">
2-
<div class="card card-container">
3-
<form
4-
*ngIf="!isLoggedIn"
5-
name="form"
6-
(ngSubmit)="f.form.valid && onSubmit()"
7-
#f="ngForm"
8-
novalidate
9-
>
10-
<div class="form-group">
11-
<label for="username">Username</label>
12-
<input
13-
type="text"
14-
class="form-control"
15-
name="username"
16-
[(ngModel)]="form.username"
17-
required
18-
#username="ngModel"
19-
/>
20-
<div
21-
class="alert alert-danger"
22-
role="alert"
23-
*ngIf="username.errors && f.submitted"
24-
>
25-
Username is required!
26-
</div>
27-
</div>
28-
<div class="form-group">
29-
<label for="password">Password</label>
30-
<input
31-
type="password"
32-
class="form-control"
33-
name="password"
34-
[(ngModel)]="form.password"
35-
required
36-
#password="ngModel"
37-
/>
38-
<div
39-
class="alert alert-danger"
40-
role="alert"
41-
*ngIf="password.errors && f.submitted"
42-
>
43-
<div *ngIf="password.errors.required">Password is required</div>
44-
</div>
45-
</div>
46-
<div class="form-group">
47-
<button class="btn btn-primary btn-block">
48-
Login
49-
</button>
50-
</div>
51-
<div class="form-group">
52-
<div
53-
class="alert alert-danger"
54-
role="alert"
55-
*ngIf="f.submitted && isLoginFailed"
56-
>
57-
Login failed: {{ errorMessage }}
58-
</div>
59-
</div>
60-
</form>
61-
<div class="alert alert-success" *ngIf="isLoggedIn">
62-
Logged in as {{ roles }}.
1+
<div class="col-md-6 offset-md-3 mt-5">
2+
<div class="card">
3+
<h4 class="card-header">Angular 10 Basic Authentication Example</h4>
4+
<div class="card-body">
5+
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
6+
<div class="form-group">
7+
<label for="username">Username</label>
8+
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
9+
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
10+
<div *ngIf="f.username.errors.required">Username is required</div>
11+
</div>
12+
</div>
13+
<div class="form-group">
14+
<label for="password">Password</label>
15+
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
16+
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
17+
<div *ngIf="f.password.errors.required">Password is required</div>
18+
</div>
19+
</div>
20+
<button [disabled]="loading" class="btn btn-primary">
21+
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
22+
Login
23+
</button>
24+
<div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{error}}</div>
25+
</form>
6326
</div>
64-
</div>
65-
</div>
27+
</div>
28+
</div>
Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Component } from '@angular/core';
2-
import { AuthenticationTokenService } from '@app/authentication/service/authentication-token.service';
2+
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3+
import { ActivatedRoute, Router } from '@angular/router';
34
import { AuthenticationService } from '@app/authentication/service/authentication.service';
5+
import { first } from 'rxjs/operators';
46

57
@Component({
68
selector: 'app-login-form',
@@ -9,43 +11,56 @@ import { AuthenticationService } from '@app/authentication/service/authenticatio
911
})
1012
export class LoginFormComponent {
1113

12-
form: any = {
13-
username: null,
14-
password: null
15-
};
14+
loginForm: FormGroup;
15+
loading = false;
16+
submitted = false;
17+
returnUrl: string = '';
18+
error = '';
1619

17-
isLoggedIn = false;
18-
isLoginFailed = false;
19-
errorMessage = '';
20-
roles: string[] = [];
21-
22-
constructor(private authService: AuthenticationService, private tokenStorage: AuthenticationTokenService) { }
23-
24-
ngOnInit(): void {
25-
if (this.tokenStorage.getToken()) {
26-
this.isLoggedIn = true;
27-
this.roles = this.tokenStorage.getUser().roles;
20+
constructor(
21+
private formBuilder: FormBuilder,
22+
private route: ActivatedRoute,
23+
private router: Router,
24+
private authenticationService: AuthenticationService
25+
) {
26+
// redirect to home if already logged in
27+
if (this.authenticationService.userValue) {
28+
this.router.navigate(['/']);
2829
}
30+
31+
this.loginForm = this.formBuilder.group({
32+
username: ['', Validators.required],
33+
password: ['', Validators.required]
34+
});
2935
}
30-
onSubmit(): void {
31-
const { username, password } = this.form;
32-
this.authService.login(username, password).subscribe(
33-
data => {
34-
this.tokenStorage.saveToken(data.accessToken);
35-
this.tokenStorage.saveUser(data);
36-
this.isLoginFailed = false;
37-
this.isLoggedIn = true;
38-
this.roles = this.tokenStorage.getUser().roles;
39-
this.reloadPage();
40-
},
41-
err => {
42-
this.errorMessage = err.error.message;
43-
this.isLoginFailed = true;
44-
}
45-
);
36+
37+
ngOnInit() {
38+
// get return url from route parameters or default to '/'
39+
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
4640
}
47-
reloadPage(): void {
48-
window.location.reload();
41+
42+
// convenience getter for easy access to form fields
43+
get f() { return this.loginForm.controls; }
44+
45+
onSubmit() {
46+
this.submitted = true;
47+
48+
// stop here if form is invalid
49+
if (this.loginForm.invalid) {
50+
return;
51+
}
52+
53+
this.loading = true;
54+
this.authenticationService.login(this.f.username.value, this.f.password.value)
55+
.pipe(first())
56+
.subscribe(
57+
data => {
58+
this.router.navigate([this.returnUrl]);
59+
},
60+
error => {
61+
this.error = error;
62+
this.loading = false;
63+
});
4964
}
5065

5166
}

src/app/login/login.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
3-
import { FormsModule } from '@angular/forms';
3+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
44
import { AuthenticationModule } from '@app/authentication/authentication.module';
55
import { LoginFormComponent } from './login-form/login-form.component';
66
import { ItemLoginRoutingModule } from './login-routing.module';
@@ -15,7 +15,8 @@ import { ItemLoginRoutingModule } from './login-routing.module';
1515
ItemLoginRoutingModule,
1616
AuthenticationModule,
1717
CommonModule,
18-
FormsModule
18+
FormsModule,
19+
ReactiveFormsModule
1920
]
2021
})
2122
export class LoginModule { }

src/environments/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
export const environment = {
66
production: false,
7-
apiUrl: 'http://localhost:8080'
7+
apiUrl: '/api'
88
};
99

1010
/*

0 commit comments

Comments
 (0)