Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tienbku committed Dec 20, 2023
1 parent f63aa98 commit 1ac0f4e
Show file tree
Hide file tree
Showing 48 changed files with 1,143 additions and 501 deletions.
92 changes: 78 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,91 @@
# Angular16JwtAuth
# Angular 16 JWT Authentication & Authorization example with Rest API

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.0.2.
Build Angular 16 JWT Authentication & Authorization example with Rest Api, HttpOnly Cookie and JWT (including HttpInterceptor, Router & Form Validation).
- JWT Authentication Flow for User Registration (Signup) & User Login
- Project Structure with HttpInterceptor, Router
- Way to implement HttpInterceptor
- How to store JWT token in HttpOnly Cookie
- Creating Login, Signup Components with Form Validation
- Angular Components for accessing protected Resources
- How to add a dynamic Navigation Bar to Angular App
- Working with Browser Session Storage

## Development server
## Flow for User Registration and User Login
For JWT – Token based Authentication with Rest API, we’re gonna call 2 endpoints:
- POST `api/auth/signup` for User Registration
- POST `api/auth/signin` for User Login
- POST `api/auth/signout` for User Logout

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
You can take a look at following flow to have an overview of Requests and Responses that Angular 16 JWT Authentication & Authorization Client will make or receive.

## Code scaffolding
![angular-16-jwt-authentication-authorization-flow](angular-16-jwt-authentication-authorization-flow.png)

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Angular JWT App Diagram with Router and HttpInterceptor
![angular-16-jwt-authentication](angular-16-jwt-authentication.png)

## Build
For more detail, please visit the tutorial:
> [Angular 16 JWT Authentication & Authorization with Web API example](https://www.bezkoder.com/angular-16-jwt-auth/)
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
> [Angular 16 Logout when Token is expired](https://www.bezkoder.com/logout-when-token-expired-angular-16/)
## Running unit tests
> [Angular 16 Refresh Token with Interceptor & JWT example](https://www.bezkoder.com/angular-16-refresh-token/)
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## With Spring Boot back-end

## Running end-to-end tests
> [Angular 16 + Spring Boot: JWT Authentication and Authorization example](https://www.bezkoder.com/angular-16-spring-boot-jwt-auth/)
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## With Node.js Express back-end

## Further help
> [Angular 16 + Node.js Express: JWT Authentication and Authorization example](https://www.bezkoder.com/node-js-angular-16-jwt-auth/)
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
Run `ng serve --port 8081` for a dev server. Navigate to `http://localhost:8081/`.

## More practice
> [Angular 16 CRUD example with Rest API](https://www.bezkoder.com/angular-16-crud-example/)
> [Angular 16 Pagination example](https://www.bezkoder.com/angular-16-pagination-ngx/)
> [Angular 16 File upload example with Progress bar](https://www.bezkoder.com/angular-16-file-upload/)
> [Angular 16 Form Validation example](https://www.bezkoder.com/angular-16-form-validation/)
Fullstack with Node:
> [Angular 16 + Node Express + MySQL example](https://www.bezkoder.com/angular-16-node-js-express-mysql/)
> [Angular 16 + Node Express + PostgreSQL example](https://www.bezkoder.com/angular-16-node-js-express-postgresql/)
> [Angular 16 + Node Express + MongoDB example](https://www.bezkoder.com/angular-16-node-js-express-mongodb/)
> [Angular 16 + Node Express: File upload example](https://www.bezkoder.com/angular-16-node-express-file-upload/)
Fullstack with Spring Boot:
> [Angular 16 + Spring Boot example](https://www.bezkoder.com/spring-boot-angular-16-crud/)
> [Angular 16 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-16-mysql/)
> [Angular 16 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-16-postgresql/)
> [Angular 16 + Spring Boot + MongoDB example](https://www.bezkoder.com/spring-boot-angular-16-mongodb/)
> [Angular 16 + Spring Boot: File upload example](https://www.bezkoder.com/angular-16-spring-boot-file-upload/)
Fullstack with Django:
> [Angular + Django example](https://www.bezkoder.com/django-angular-13-crud-rest-framework/)
> [Angular + Django + MySQL](https://www.bezkoder.com/django-angular-mysql/)
> [Angular + Django + PostgreSQL](https://www.bezkoder.com/django-angular-postgresql/)
> [Angular + Django + MongoDB](https://www.bezkoder.com/django-angular-mongodb/)
Serverless with Firebase:
> [Angular 16 Firebase CRUD with Realtime DataBase](https://www.bezkoder.com/angular-16-firebase-crud/)
> [Angular 16 Firestore CRUD example](https://www.bezkoder.com/angular-16-firestore-crud/)
> [Angular 16 Firebase Storage: File Upload/Display/Delete example](https://www.bezkoder.com/angular-16-firebase-storage/)
Integration (run back-end & front-end on same server/port)
> [How to integrate Angular with Node Restful Services](https://www.bezkoder.com/integrate-angular-12-node-js/)
> [How to Integrate Angular with Spring Boot Rest API](https://www.bezkoder.com/integrate-angular-12-spring-boot/)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added angular-16-jwt-authentication.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@angular/platform-browser": "^16.0.0",
"@angular/platform-browser-dynamic": "^16.0.0",
"@angular/router": "^16.0.0",
"bootstrap": "^4.6.2",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.13.0"
Expand Down
53 changes: 53 additions & 0 deletions src/app/_helpers/http.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { StorageService } from '../_services/storage.service';
import { EventBusService } from '../_shared/event-bus.service';
import { EventData } from '../_shared/event.class';

@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
private isRefreshing = false;

constructor(private storageService: StorageService, private eventBusService: EventBusService) { }

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
withCredentials: true,
});

return next.handle(req).pipe(
catchError((error) => {
// logout when token is expired
/*
if (
error instanceof HttpErrorResponse &&
!req.url.includes('auth/signin') &&
error.status === 401
) {
return this.handle401Error(req, next);
}
*/
return throwError(() => error);
})
);
}

private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;

if (this.storageService.isLoggedIn()) {
this.eventBusService.emit(new EventData('logout', null));
}
}

return next.handle(request);
}
}

export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true },
];
16 changes: 16 additions & 0 deletions src/app/_services/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { AuthService } from './auth.service';

describe('AuthService', () => {
let service: AuthService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
43 changes: 43 additions & 0 deletions src/app/_services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

const AUTH_API = 'http://localhost:8080/api/auth/';

const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}

login(username: string, password: string): Observable<any> {
return this.http.post(
AUTH_API + 'signin',
{
username,
password,
},
httpOptions
);
}

register(username: string, email: string, password: string): Observable<any> {
return this.http.post(
AUTH_API + 'signup',
{
username,
email,
password,
},
httpOptions
);
}

logout(): Observable<any> {
return this.http.post(AUTH_API + 'signout', { }, httpOptions);
}
}
16 changes: 16 additions & 0 deletions src/app/_services/storage.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { StorageService } from './storage.service';

describe('StorageService', () => {
let service: StorageService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StorageService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
37 changes: 37 additions & 0 deletions src/app/_services/storage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';

const USER_KEY = 'auth-user';

@Injectable({
providedIn: 'root'
})
export class StorageService {
constructor() {}

clean(): void {
window.sessionStorage.clear();
}

public saveUser(user: any): void {
window.sessionStorage.removeItem(USER_KEY);
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
}

public getUser(): any {
const user = window.sessionStorage.getItem(USER_KEY);
if (user) {
return JSON.parse(user);
}

return null;
}

public isLoggedIn(): boolean {
const user = window.sessionStorage.getItem(USER_KEY);
if (user) {
return true;
}

return false;
}
}
16 changes: 16 additions & 0 deletions src/app/_services/user.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { UserService } from './user.service';

describe('UserService', () => {
let service: UserService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
28 changes: 28 additions & 0 deletions src/app/_services/user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

const API_URL = 'http://localhost:8080/api/test/';

@Injectable({
providedIn: 'root',
})
export class UserService {
constructor(private http: HttpClient) {}

getPublicContent(): Observable<any> {
return this.http.get(API_URL + 'all', { responseType: 'text' });
}

getUserBoard(): Observable<any> {
return this.http.get(API_URL + 'user', { responseType: 'text' });
}

getModeratorBoard(): Observable<any> {
return this.http.get(API_URL + 'mod', { responseType: 'text' });
}

getAdminBoard(): Observable<any> {
return this.http.get(API_URL + 'admin', { responseType: 'text' });
}
}
Loading

0 comments on commit 1ac0f4e

Please sign in to comment.