~/recipeapp/$ ng g m User --module=app ~/recipeapp/$ ng g s user/Authentication ~/recipeapp/$ ng g c user/Login --module=user ~/recipeapp/$ ng g c user/Register --module=user
src/app/user/authentication.service.ts
function parseJwt(token) {
if (!token) {
return null;
}
const base64Token = token.split('.')[1];
const base64 = base64Token.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
}
src/app/user/authentication.service.ts
@Injectable()
export class AuthenticationService {
private readonly _tokenKey = 'currentUser';
private _user$: BehaviorSubject<string>;
constructor(private http: HttpClient) {
let parsedToken = parseJwt(localStorage.getItem(this._tokenKey));
if (parsedToken) {
const expires = new Date(parseInt(parsedToken.exp, 10) * 1000) < new Date();
if (expires) {
localStorage.removeItem(this._tokenKey);
parsedToken = null;
}
}
this._user$ = new BehaviorSubject<string>(parsedToken && parsedToken.unique_name);
}
src/app/user/authentication.service.ts
login(email: string, password: string): Observable<boolean> {
return this.http.post(
`${environment.apiUrl}/account`,
{ email, password },
{ responseType: 'text' }
).pipe(
map((token: any) => {
if (token) {
localStorage.setItem(this._tokenKey, token);
this._user$.next(email);
return true;
} else {
return false;
}
})
);
}
src/app/user/authentication.service.ts
register(
firstname: string, lastname: string, email: string, password: string
): Observable<boolean> {
return this.http
.post(
`${environment.apiUrl}/account/register`,
{
firstname, lastname,
email, password,
passwordConfirmation: password
},
{ responseType: 'text' }
)
.pipe(
map((token: any) => {
if (token) {
localStorage.setItem(this._tokenKey, token);
this._user$.next(email);
return true;
} else {
return false;
}
})
);
}
src/app/user/authentication.service.ts
logout() {
if (this.user$.getValue()) {
localStorage.removeItem('currentUser');
this._user$.next(null);
}
}
src/app/user/register/register.component.ts
export class RegisterComponent implements OnInit {
public user: FormGroup;
ngOnInit() {
this.user = this.fb.group({
firstname: ['', Validators.required],
lastname: ['', Validators.required],
email: [
'', [Validators.required, Validators.email],,
serverSideValidateUsername(this.authService.checkUserNameAvailability)
],
passwordGroup: this.fb.group({
password: ['', [Validators.required, Validators.minLength(8), patternValidator(...)]],
confirmPassword: ['', Validators.required]
}, { validator: comparePasswords })
});
}
src/app/user/register/register.component.ts
function comparePasswords(control: AbstractControl): ValidationErrors {
const password = control.get('password');
const confirmPassword = control.get('confirmPassword');
return password.value === confirmPassword.value ? null
: { 'passwordsDiffer': true };
}
src/app/user/register/register.component.ts
function serverSideValidateUsername(
checkAvailabilityFn: (n: string) => Observable<boolean>
): ValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors> => {
return checkAvailabilityFn(control.value).pipe(
map(available => {
if (available) {
return null;
}
return { userAlreadyExists: true };
})
);
};
}
RecipeApi/Controllers/AccountController.cs
[AllowAnonymous]
[HttpGet("checkusername")]
public async Task<ActionResult<Boolean>>
CheckAvailableUserName(string email)
{
var user = await _userManager.FindByNameAsync(email);
return user == null;
}
src/app/user/authentication.services.ts
checkUserNameAvailability = (email: string): Observable<boolean> => {
return this.http.get<boolean>(
`${environment.apiUrl}/account/checkusername`,
{
params: { email }
}
);
}
<form [formGroup]="user" (ngSubmit)="onSubmit()">
<mat-card-header>
<mat-card-title>Register</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column">
<span fxLayout="row" fxLayoutGap="2%">
<mat-form-field>
<input
matInput
placeholder="first name"
aria-label="first name"
data-cy="register-firstname"
formControlName="firstname"
/>
<mat-error
*ngIf="
user.get('firstname').errors && user.get('firstname').touched
"
>
{{ getErrorMessage(user.get('firstname').errors) }}
</mat-error>
</mat-form-field>
<!-- ... and so on -->
src/app/http-interceptors/AuthenticationInterceptor.ts
@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor {
constructor(private authService: AuthenticationService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.token.length) {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization',`Bearer ${this.authService.token}`)
});
return next.handle(clonedRequest);
}
return next.handle(req);
}
}
frontend/src/app/http-interceptors/index.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthenticationInterceptor } from './AuthenticationInterceptor';
export const httpInterceptorProviders = [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthenticationInterceptor,
multi: true
}
];
src/app/app.module.ts
import { httpInterceptorProviders } from '../http-interceptors/index';
@NgModule({
declarations: [...],
imports: [...],
providers: [httpInterceptorProviders],
bootstrap: [AppComponent]
})
export class AppModule {}
src/app/app-routing/app-routing.module.ts
const appRoutes: Routes = [
{
path: 'recipe',
canActivate: [ AuthGuard ],
loadChildren: '../recipe/recipe.module#RecipeModule'
},
{ path: '', redirectTo: 'recipe/list', pathMatch: 'full'},
{ path: '**', component: PageNotFoundComponent}
];
src/app/user/auth-guard.service.ts
import { CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if (this.authService.user$.getValue()) {
return true;
}
this.authService.redirectUrl = state.url;
this.router.navigate(['/login']);
return false;
}
}
onSubmit() {
this.authService.login(this.user.value.username,
this.user.value.password).subscribe(val => {
if (val) {
if (this.authService.redirectUrl) {
this.router.navigateByUrl(this.authService.redirectUrl);
this.authService.redirectUrl = undefined;
} else {
this.router.navigate(['/recipe/list']);
}
}
}, err => this.errorMsg = err.json().message);
}