├── settings.gradle
├── client
├── src
│ ├── app
│ │ ├── app.scss
│ │ ├── app.html
│ │ ├── interceptors
│ │ │ ├── index.ts
│ │ │ └── token.interceptor.ts
│ │ ├── app.ts
│ │ ├── products
│ │ │ ├── product.ts
│ │ │ ├── products.spec.ts
│ │ │ ├── products.scss
│ │ │ ├── products.html
│ │ │ └── products.ts
│ │ ├── auth.spec.ts
│ │ ├── product.spec.ts
│ │ ├── auth
│ │ │ ├── login
│ │ │ │ ├── login.scss
│ │ │ │ ├── login.spec.ts
│ │ │ │ ├── login.html
│ │ │ │ └── login.ts
│ │ │ ├── register
│ │ │ │ ├── register.scss
│ │ │ │ ├── register.spec.ts
│ │ │ │ ├── register.html
│ │ │ │ └── register.ts
│ │ │ ├── auth-guard.spec.ts
│ │ │ └── auth-guard.ts
│ │ ├── app.config.ts
│ │ ├── app.spec.ts
│ │ ├── app.routes.ts
│ │ ├── product.ts
│ │ └── auth.ts
│ ├── styles.scss
│ ├── main.ts
│ └── index.html
├── public
│ └── favicon.ico
├── .editorconfig
├── tsconfig.spec.json
├── tsconfig.app.json
├── .gitignore
├── tsconfig.json
├── package.json
├── README.md
└── angular.json
├── .DS_Store
├── .angulardoc.json
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── src
├── main
│ ├── resources
│ │ └── application.properties
│ └── java
│ │ └── com
│ │ └── djamware
│ │ └── spring_angular_auth
│ │ ├── repositories
│ │ ├── RoleRepository.java
│ │ ├── UserRepository.java
│ │ └── ProductRepository.java
│ │ ├── SpringAngularAuthApplication.java
│ │ ├── controllers
│ │ ├── AuthBody.java
│ │ ├── ProductController.java
│ │ └── AuthController.java
│ │ ├── models
│ │ ├── Role.java
│ │ ├── Products.java
│ │ └── User.java
│ │ ├── configs
│ │ ├── JwtTokenFilter.java
│ │ ├── WebSecurityConfig.java
│ │ └── JwtTokenProvider.java
│ │ └── services
│ │ └── CustomUserDetailsService.java
└── test
│ └── java
│ └── com
│ └── djamware
│ └── spring_angular_auth
│ └── SpringAngularAuthApplicationTests.java
├── README.md
├── .gitignore
├── LICENSE
├── gradlew.bat
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'spring-angular-auth'
2 |
--------------------------------------------------------------------------------
/client/src/app/app.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 20px;
3 | }
--------------------------------------------------------------------------------
/client/src/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didinj/spring-boot-mongodb-security-angular8/HEAD/.DS_Store
--------------------------------------------------------------------------------
/.angulardoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "repoId": "77668690-ca90-42b3-98ba-8218d25d54b3",
3 | "lastSync": 0
4 | }
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didinj/spring-boot-mongodb-security-angular8/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didinj/spring-boot-mongodb-security-angular8/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=spring-angular-auth
2 | spring.data.mongodb.database=springmongodb
3 | spring.data.mongodb.host=localhost
4 | spring.data.mongodb.port=27017
5 |
--------------------------------------------------------------------------------
/client/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | html, body { height: 100%; }
4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Build Authentication with Spring Boot 3.5.0, MongoDB, and Angular 20
2 |
3 | Read more tutorial [here](https://www.djamware.com/post/5d3332980707cc65eac46c7b/build-authentication-with-spring-boot-350-mongodb-and-angular-20).
4 |
--------------------------------------------------------------------------------
/client/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { appConfig } from './app/app.config';
3 | import { App } from './app/app';
4 |
5 | bootstrapApplication(App, appConfig)
6 | .catch((err) => console.error(err));
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/client/src/app/interceptors/index.ts:
--------------------------------------------------------------------------------
1 | import { HTTP_INTERCEPTORS } from "@angular/common/http";
2 | import { Provider } from "@angular/core";
3 | import { TokenInterceptor } from "./token.interceptor";
4 |
5 | export const httpInterceptorProvider: Provider =
6 | { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true };
--------------------------------------------------------------------------------
/client/src/app/app.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterOutlet } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-root',
6 | imports: [RouterOutlet],
7 | templateUrl: './app.html',
8 | styleUrl: './app.scss'
9 | })
10 | export class App {
11 | protected title = 'client';
12 | }
13 |
--------------------------------------------------------------------------------
/client/src/app/products/product.ts:
--------------------------------------------------------------------------------
1 | export class Product {
2 | productId: number | undefined;
3 | isbn: string | undefined;
4 | title: string | undefined;
5 | author: string | undefined;
6 | description: string | undefined;
7 | publisher: string | undefined;
8 | publishedYear: number | undefined;
9 | price: number | undefined;
10 | }
--------------------------------------------------------------------------------
/src/test/java/com/djamware/spring_angular_auth/SpringAngularAuthApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SpringAngularAuthApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/repositories/RoleRepository.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.repositories;
2 |
3 | import org.springframework.data.mongodb.repository.MongoRepository;
4 |
5 | import com.djamware.spring_angular_auth.models.Role;
6 |
7 | public interface RoleRepository extends MongoRepository {
8 |
9 | Role findByRole(String role);
10 | }
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/repositories/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.repositories;
2 |
3 | import org.springframework.data.mongodb.repository.MongoRepository;
4 |
5 | import com.djamware.spring_angular_auth.models.User;
6 |
7 | public interface UserRepository extends MongoRepository {
8 |
9 | User findByEmail(String email);
10 | }
--------------------------------------------------------------------------------
/client/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 | ij_typescript_use_double_quotes = false
14 |
15 | [*.md]
16 | max_line_length = off
17 | trim_trailing_whitespace = false
18 |
--------------------------------------------------------------------------------
/client/src/app/auth.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { Auth } from './auth';
4 |
5 | describe('Auth', () => {
6 | let service: Auth;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(Auth);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/client/src/app/product.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { Product } from './product';
4 |
5 | describe('Product', () => {
6 | let service: Product;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(Product);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/SpringAngularAuthApplication.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SpringAngularAuthApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(SpringAngularAuthApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/repositories/ProductRepository.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.repositories;
2 |
3 | import org.springframework.data.mongodb.repository.MongoRepository;
4 | import org.springframework.lang.NonNull;
5 |
6 | import com.djamware.spring_angular_auth.models.Products;
7 |
8 | public interface ProductRepository extends MongoRepository {
9 |
10 | @Override
11 | void delete(@NonNull Products deleted);
12 | }
--------------------------------------------------------------------------------
/client/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/spec",
7 | "types": [
8 | "jasmine"
9 | ]
10 | },
11 | "include": [
12 | "src/**/*.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**
6 | !**/src/test/**
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 |
17 | ### IntelliJ IDEA ###
18 | .idea
19 | *.iws
20 | *.iml
21 | *.ipr
22 | out/
23 |
24 | ### NetBeans ###
25 | /nbproject/private/
26 | /nbbuild/
27 | /dist/
28 | /nbdist/
29 | /.nb-gradle/
30 |
31 | ### VS Code ###
32 | .vscode/
33 |
--------------------------------------------------------------------------------
/client/src/app/auth/login/login.scss:
--------------------------------------------------------------------------------
1 | /* Structure */
2 | .example-container {
3 | position: relative;
4 | padding: 5px;
5 | }
6 |
7 | .example-form {
8 | min-width: 150px;
9 | max-width: 500px;
10 | width: 100%;
11 | }
12 |
13 | .example-full-width {
14 | width: 100%;
15 | }
16 |
17 | .example-full-width:nth-last-child() {
18 | margin-bottom: 10px;
19 | }
20 |
21 | .button-row {
22 | margin: 10px 0;
23 | }
24 |
25 | .mat-flat-button {
26 | margin: 5px;
27 | }
--------------------------------------------------------------------------------
/client/src/app/auth/register/register.scss:
--------------------------------------------------------------------------------
1 | /* Structure */
2 | .example-container {
3 | position: relative;
4 | padding: 5px;
5 | }
6 |
7 | .example-form {
8 | min-width: 150px;
9 | max-width: 500px;
10 | width: 100%;
11 | }
12 |
13 | .example-full-width {
14 | width: 100%;
15 | }
16 |
17 | .example-full-width:nth-last-child() {
18 | margin-bottom: 10px;
19 | }
20 |
21 | .button-row {
22 | margin: 10px 0;
23 | }
24 |
25 | .mat-flat-button {
26 | margin: 5px;
27 | }
--------------------------------------------------------------------------------
/client/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "extends": "./tsconfig.json",
5 | "compilerOptions": {
6 | "outDir": "./out-tsc/app",
7 | "types": []
8 | },
9 | "include": [
10 | "src/**/*.ts"
11 | ],
12 | "exclude": [
13 | "src/**/*.spec.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 |
4 | import { routes } from './app.routes';
5 | import { httpInterceptorProvider } from './interceptors';
6 |
7 | export const appConfig: ApplicationConfig = {
8 | providers: [
9 | provideBrowserGlobalErrorListeners(),
10 | provideZoneChangeDetection({ eventCoalescing: true }),
11 | provideRouter(routes),
12 | httpInterceptorProvider,
13 | ]
14 | };
15 |
--------------------------------------------------------------------------------
/client/src/app/auth/auth-guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { CanActivateFn } from '@angular/router';
3 |
4 | import { authGuard } from './auth-guard';
5 |
6 | describe('authGuard', () => {
7 | const executeGuard: CanActivateFn = (...guardParameters) =>
8 | TestBed.runInInjectionContext(() => authGuard(...guardParameters));
9 |
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({});
12 | });
13 |
14 | it('should be created', () => {
15 | expect(executeGuard).toBeTruthy();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/client/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Client
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/controllers/AuthBody.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.controllers;
2 |
3 | public class AuthBody {
4 |
5 | private String email;
6 | private String password;
7 |
8 | public String getEmail() {
9 | return email;
10 | }
11 |
12 | public void setEmail(String email) {
13 | this.email = email;
14 | }
15 |
16 | public String getPassword() {
17 | return password;
18 | }
19 |
20 | public void setPassword(String password) {
21 | this.password = password;
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/client/src/app/auth/login/login.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { Login } from './login';
4 |
5 | describe('Login', () => {
6 | let component: Login;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [Login]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(Login);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/client/src/app/products/products.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { Products } from './products';
4 |
5 | describe('Products', () => {
6 | let component: Products;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [Products]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(Products);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/client/src/app/auth/register/register.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { Register } from './register';
4 |
5 | describe('Register', () => {
6 | let component: Register;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [Register]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(Register);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/client/src/app/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { App } from './app';
3 |
4 | describe('App', () => {
5 | beforeEach(async () => {
6 | await TestBed.configureTestingModule({
7 | imports: [App],
8 | }).compileComponents();
9 | });
10 |
11 | it('should create the app', () => {
12 | const fixture = TestBed.createComponent(App);
13 | const app = fixture.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it('should render title', () => {
18 | const fixture = TestBed.createComponent(App);
19 | fixture.detectChanges();
20 | const compiled = fixture.nativeElement as HTMLElement;
21 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, client');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/client/src/app/auth/auth-guard.ts:
--------------------------------------------------------------------------------
1 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
2 | import { Auth } from '../auth';
3 |
4 | export class AuthGuard implements CanActivate {
5 | constructor(private authService: Auth, private router: Router) { }
6 |
7 | canActivate(
8 | next: ActivatedRouteSnapshot,
9 | state: RouterStateSnapshot): boolean {
10 | const url: string = state.url;
11 |
12 | return this.checkLogin(url);
13 | }
14 |
15 | checkLogin(url: string): boolean {
16 | if (this.authService.isLoggedIn) { return true; }
17 |
18 | // Store the attempted URL for redirecting
19 | this.authService.redirectUrl = url;
20 |
21 | // Navigate to the login page with extras
22 | this.router.navigate(['/login']);
23 | return false;
24 | }
25 | }
--------------------------------------------------------------------------------
/client/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { Products } from './products/products';
3 | import { Login } from './auth/login/login';
4 | import { Register } from './auth/register/register';
5 | import { AuthGuard } from './auth/auth-guard';
6 |
7 | export const routes: Routes = [
8 | {
9 | path: '',
10 | redirectTo: 'products',
11 | pathMatch: 'full'
12 | },
13 | {
14 | path: 'products',
15 | canActivate: [AuthGuard],
16 | component: Products,
17 | data: { title: 'List of Products' }
18 | },
19 | {
20 | path: 'login',
21 | component: Login,
22 | data: { title: 'Login' }
23 | },
24 | {
25 | path: 'register',
26 | component: Register,
27 | data: { title: 'Register' }
28 | }
29 | ];
30 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/models/Role.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.models;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.mongodb.core.index.IndexDirection;
5 | import org.springframework.data.mongodb.core.index.Indexed;
6 | import org.springframework.data.mongodb.core.mapping.Document;
7 |
8 | @Document(collection = "roles")
9 | public class Role {
10 |
11 | @Id
12 | private String id;
13 | @Indexed(unique = true, direction = IndexDirection.DESCENDING)
14 |
15 | private String role;
16 |
17 | public String getId() {
18 | return id;
19 | }
20 |
21 | public void setId(String id) {
22 | this.id = id;
23 | }
24 |
25 | public String getRole() {
26 | return role;
27 | }
28 |
29 | public void setRole(String role) {
30 | this.role = role;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/client/src/app/products/products.scss:
--------------------------------------------------------------------------------
1 | /* Structure */
2 | .example-container {
3 | position: relative;
4 | padding: 5px;
5 | }
6 |
7 | .example-table-container {
8 | position: relative;
9 | max-height: 400px;
10 | overflow: auto;
11 | }
12 |
13 | table {
14 | width: 100%;
15 | }
16 |
17 | .example-loading-shade {
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | bottom: 56px;
22 | right: 0;
23 | background: rgba(0, 0, 0, 0.15);
24 | z-index: 1;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | }
29 |
30 | .example-rate-limit-reached {
31 | color: #980000;
32 | max-width: 360px;
33 | text-align: center;
34 | }
35 |
36 | /* Column Widths */
37 | .mat-column-number,
38 | .mat-column-state {
39 | max-width: 64px;
40 | }
41 |
42 | .mat-column-created {
43 | max-width: 124px;
44 | }
45 |
46 | .mat-flat-button {
47 | margin: 5px;
48 | }
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/controllers/ProductController.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.controllers;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.web.bind.annotation.CrossOrigin;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RequestMethod;
7 | import org.springframework.web.bind.annotation.RestController;
8 |
9 | import com.djamware.spring_angular_auth.models.Products;
10 | import com.djamware.spring_angular_auth.repositories.ProductRepository;
11 |
12 | @CrossOrigin(origins = "*")
13 | @RequestMapping("/api")
14 | @RestController
15 | public class ProductController {
16 |
17 | @Autowired
18 | ProductRepository productRepository;
19 |
20 | @RequestMapping(method = RequestMethod.GET, value = "/products")
21 | public Iterable product() {
22 | return productRepository.findAll();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/app/product.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { catchError, Observable, of, tap } from 'rxjs';
4 |
5 | const apiUrl = 'http://192.168.0.7:8080/api/products';
6 |
7 | @Injectable({
8 | providedIn: 'root'
9 | })
10 | export class Product {
11 |
12 | constructor(private http: HttpClient) { }
13 |
14 | getProducts(): Observable {
15 | return this.http.get(apiUrl + 'Product')
16 | .pipe(
17 | tap(_ => this.log('fetched Products')),
18 | catchError(this.handleError('getProducts', []))
19 | );
20 | }
21 |
22 | private handleError(operation = 'operation', result?: T) {
23 | return (error: any): Observable => {
24 |
25 | console.error(error); // log to console instead
26 | this.log(`${operation} failed: ${error.message}`);
27 |
28 | return of(result as T);
29 | };
30 | }
31 |
32 | private log(message: string) {
33 | console.log(message);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
3 | {
4 | "compileOnSave": false,
5 | "compilerOptions": {
6 | "strict": true,
7 | "noImplicitOverride": true,
8 | "noPropertyAccessFromIndexSignature": true,
9 | "noImplicitReturns": true,
10 | "noFallthroughCasesInSwitch": true,
11 | "skipLibCheck": true,
12 | "isolatedModules": true,
13 | "experimentalDecorators": true,
14 | "importHelpers": true,
15 | "target": "ES2022",
16 | "module": "preserve"
17 | },
18 | "angularCompilerOptions": {
19 | "enableI18nLegacyMessageIdFormat": false,
20 | "strictInjectionParameters": true,
21 | "strictInputAccessModifiers": true,
22 | "typeCheckHostBindings": true,
23 | "strictTemplates": true
24 | },
25 | "files": [],
26 | "references": [
27 | {
28 | "path": "./tsconfig.app.json"
29 | },
30 | {
31 | "path": "./tsconfig.spec.json"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Didin Jamaludin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/cdk": "^20.0.3",
14 | "@angular/common": "^20.0.0",
15 | "@angular/compiler": "^20.0.0",
16 | "@angular/core": "^20.0.0",
17 | "@angular/forms": "^20.0.0",
18 | "@angular/material": "^20.0.3",
19 | "@angular/platform-browser": "^20.0.0",
20 | "@angular/router": "^20.0.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.15.0"
24 | },
25 | "devDependencies": {
26 | "@angular/build": "^20.0.2",
27 | "@angular/cli": "^20.0.2",
28 | "@angular/compiler-cli": "^20.0.0",
29 | "@types/jasmine": "~5.1.0",
30 | "jasmine-core": "~5.7.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.2.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.1.0",
36 | "typescript": "~5.8.2"
37 | }
38 | }
--------------------------------------------------------------------------------
/client/src/app/products/products.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 | | Product Name |
15 | {{row.prodName}} |
16 |
17 |
18 |
19 |
20 | Product Description |
21 | {{row.prodDesc}} |
22 |
23 |
24 |
25 |
26 | Product Price |
27 | {{row.prodPrice}} |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/client/src/app/auth/login/login.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/configs/JwtTokenFilter.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.configs;
2 |
3 | import java.io.IOException;
4 |
5 | import org.springframework.lang.NonNull;
6 | import org.springframework.security.core.Authentication;
7 | import org.springframework.security.core.context.SecurityContextHolder;
8 | import org.springframework.web.filter.OncePerRequestFilter;
9 |
10 | import jakarta.servlet.FilterChain;
11 | import jakarta.servlet.ServletException;
12 | import jakarta.servlet.http.HttpServletRequest;
13 | import jakarta.servlet.http.HttpServletResponse;
14 |
15 | public class JwtTokenFilter extends OncePerRequestFilter {
16 |
17 | private final JwtTokenProvider jwtTokenProvider;
18 |
19 | public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
20 | this.jwtTokenProvider = jwtTokenProvider;
21 | }
22 |
23 | @Override
24 | protected void doFilterInternal(@NonNull HttpServletRequest request,
25 | @NonNull HttpServletResponse response,
26 | @NonNull FilterChain filterChain)
27 | throws ServletException, IOException {
28 |
29 | String token = jwtTokenProvider.resolveToken(request);
30 | if (token != null && jwtTokenProvider.validateToken(token)) {
31 | Authentication auth = jwtTokenProvider.getAuthentication(token);
32 | SecurityContextHolder.getContext().setAuthentication(auth);
33 | }
34 |
35 | filterChain.doFilter(request, response);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/app/auth.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { catchError, Observable, of, tap } from 'rxjs';
4 |
5 | const apiUrl = 'http://192.168.0.7:8080/api/auth/';
6 |
7 | @Injectable({
8 | providedIn: 'root'
9 | })
10 | export class Auth {
11 |
12 | isLoggedIn = false;
13 | redirectUrl: string | undefined;
14 |
15 | constructor(private http: HttpClient) { }
16 |
17 | login(data: any): Observable {
18 | return this.http.post(apiUrl + 'login', data)
19 | .pipe(
20 | tap(_ => this.isLoggedIn = true),
21 | catchError(this.handleError('login', []))
22 | );
23 | }
24 |
25 | logout(): Observable {
26 | return this.http.get(apiUrl + 'signout')
27 | .pipe(
28 | tap(_ => this.isLoggedIn = false),
29 | catchError(this.handleError('logout', []))
30 | );
31 | }
32 |
33 | register(data: any): Observable {
34 | return this.http.post(apiUrl + 'register', data)
35 | .pipe(
36 | tap(_ => this.log('login')),
37 | catchError(this.handleError('login', []))
38 | );
39 | }
40 |
41 | private handleError(operation = 'operation', result?: T) {
42 | return (error: any): Observable => {
43 |
44 | console.error(error); // log to console instead
45 | this.log(`${operation} failed: ${error.message}`);
46 |
47 | return of(result as T);
48 | };
49 | }
50 |
51 | private log(message: string) {
52 | console.log(message);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/client/src/app/auth/register/register.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/models/Products.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.models;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.mongodb.core.mapping.Document;
5 |
6 | @Document(collection = "products")
7 | public class Products {
8 |
9 | @Id
10 | String id;
11 | String prodName;
12 | String prodDesc;
13 | Double prodPrice;
14 | String prodImage;
15 |
16 | public Products() {
17 | }
18 |
19 | public Products(String prodName, String prodDesc, Double prodPrice, String prodImage) {
20 | super();
21 | this.prodName = prodName;
22 | this.prodDesc = prodDesc;
23 | this.prodPrice = prodPrice;
24 | this.prodImage = prodImage;
25 | }
26 |
27 | public String getId() {
28 | return id;
29 | }
30 |
31 | public void setId(String id) {
32 | this.id = id;
33 | }
34 |
35 | public String getProdName() {
36 | return prodName;
37 | }
38 |
39 | public void setProdName(String prodName) {
40 | this.prodName = prodName;
41 | }
42 |
43 | public String getProdDesc() {
44 | return prodDesc;
45 | }
46 |
47 | public void setProdDesc(String prodDesc) {
48 | this.prodDesc = prodDesc;
49 | }
50 |
51 | public Double getProdPrice() {
52 | return prodPrice;
53 | }
54 |
55 | public void setProdPrice(Double prodPrice) {
56 | this.prodPrice = prodPrice;
57 | }
58 |
59 | public String getProdImage() {
60 | return prodImage;
61 | }
62 |
63 | public void setProdImage(String prodImage) {
64 | this.prodImage = prodImage;
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Client
2 |
3 | This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.2.
4 |
5 | ## Development server
6 |
7 | To start a local development server, run:
8 |
9 | ```bash
10 | ng serve
11 | ```
12 |
13 | Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
14 |
15 | ## Code scaffolding
16 |
17 | Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
18 |
19 | ```bash
20 | ng generate component component-name
21 | ```
22 |
23 | For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
24 |
25 | ```bash
26 | ng generate --help
27 | ```
28 |
29 | ## Building
30 |
31 | To build the project run:
32 |
33 | ```bash
34 | ng build
35 | ```
36 |
37 | This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
38 |
39 | ## Running unit tests
40 |
41 | To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
42 |
43 | ```bash
44 | ng test
45 | ```
46 |
47 | ## Running end-to-end tests
48 |
49 | For end-to-end (e2e) testing, run:
50 |
51 | ```bash
52 | ng e2e
53 | ```
54 |
55 | Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
56 |
57 | ## Additional Resources
58 |
59 | For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
60 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/models/User.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.models;
2 |
3 | import java.util.Set;
4 |
5 | import org.springframework.data.annotation.Id;
6 | import org.springframework.data.mongodb.core.index.IndexDirection;
7 | import org.springframework.data.mongodb.core.index.Indexed;
8 | import org.springframework.data.mongodb.core.mapping.DBRef;
9 | import org.springframework.data.mongodb.core.mapping.Document;
10 |
11 | @Document(collection = "users")
12 | public class User {
13 |
14 | @Id
15 | private String id;
16 | @Indexed(unique = true, direction = IndexDirection.DESCENDING)
17 | private String email;
18 | private String password;
19 | private String fullname;
20 | private boolean enabled;
21 | @DBRef
22 | private Set roles;
23 |
24 | public String getId() {
25 | return id;
26 | }
27 |
28 | public void setId(String id) {
29 | this.id = id;
30 | }
31 |
32 | public String getEmail() {
33 | return email;
34 | }
35 |
36 | public void setEmail(String email) {
37 | this.email = email;
38 | }
39 |
40 | public String getPassword() {
41 | return password;
42 | }
43 |
44 | public void setPassword(String password) {
45 | this.password = password;
46 | }
47 |
48 | public String getFullname() {
49 | return fullname;
50 | }
51 |
52 | public void setFullname(String fullname) {
53 | this.fullname = fullname;
54 | }
55 |
56 | public boolean isEnabled() {
57 | return enabled;
58 | }
59 |
60 | public void setEnabled(boolean enabled) {
61 | this.enabled = enabled;
62 | }
63 |
64 | public Set getRoles() {
65 | return roles;
66 | }
67 |
68 | public void setRoles(Set roles) {
69 | this.roles = roles;
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/client/src/app/interceptors/token.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http";
2 | import { Injectable } from "@angular/core";
3 | import { Router } from "@angular/router";
4 | import { catchError, map, Observable, throwError } from "rxjs";
5 |
6 | @Injectable()
7 | export class TokenInterceptor implements HttpInterceptor {
8 | constructor(private router: Router) { }
9 |
10 | intercept(request: HttpRequest, next: HttpHandler): Observable> {
11 |
12 | const token = localStorage.getItem('token');
13 | if (token) {
14 | request = request.clone({
15 | setHeaders: {
16 | 'Authorization': 'Bearer ' + token
17 | }
18 | });
19 | }
20 | if (!request.headers.has('Content-Type')) {
21 | request = request.clone({
22 | setHeaders: {
23 | 'content-type': 'application/json'
24 | }
25 | });
26 | }
27 | request = request.clone({
28 | headers: request.headers.set('Accept', 'application/json')
29 | });
30 | return next.handle(request).pipe(
31 | map((event: HttpEvent) => {
32 | if (event instanceof HttpResponse) {
33 | console.log('event--->>>', event);
34 | }
35 | return event;
36 | }),
37 | catchError((error: HttpErrorResponse) => {
38 | console.log(error);
39 | if (error.status === 401) {
40 | this.router.navigate(['login']);
41 | }
42 | if (error.status === 400) {
43 | alert(error.error);
44 | }
45 | return throwError(error);
46 | }));
47 | }
48 | }
--------------------------------------------------------------------------------
/client/src/app/products/products.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Product } from '../product';
3 | import { Auth } from '../auth';
4 | import { Router } from '@angular/router';
5 | import { MatInputModule } from '@angular/material/input';
6 | import { MatPaginatorModule } from '@angular/material/paginator';
7 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
8 | import { MatSortModule } from '@angular/material/sort';
9 | import { MatTableModule } from '@angular/material/table';
10 | import { MatIconModule } from '@angular/material/icon';
11 | import { MatButtonModule } from '@angular/material/button';
12 | import { MatCardModule } from '@angular/material/card';
13 | import { MatFormFieldModule } from '@angular/material/form-field';
14 | import { CommonModule } from '@angular/common';
15 |
16 | @Component({
17 | selector: 'app-products',
18 | imports: [
19 | CommonModule,
20 | MatInputModule,
21 | MatPaginatorModule,
22 | MatProgressSpinnerModule,
23 | MatSortModule,
24 | MatTableModule,
25 | MatIconModule,
26 | MatButtonModule,
27 | MatCardModule,
28 | MatFormFieldModule
29 | ],
30 | templateUrl: './products.html',
31 | styleUrl: './products.scss'
32 | })
33 | export class Products {
34 | data: Product[] = [];
35 | displayedColumns: string[] = ['prodName', 'prodDesc', 'prodPrice'];
36 | isLoadingResults = true;
37 |
38 | constructor(private productService: Product, private authService: Auth, private router: Router) { }
39 |
40 | ngOnInit() {
41 | this.getProducts();
42 | }
43 |
44 | getProducts(): void {
45 | this.productService.getProducts()
46 | .subscribe({
47 | next: (products) => {
48 | this.data = products;
49 | console.log(this.data);
50 | this.isLoadingResults = false;
51 | },
52 | error: (err) => {
53 | console.log(err);
54 | this.isLoadingResults = false;
55 | }
56 | });
57 | }
58 |
59 | logout() {
60 | localStorage.removeItem('token');
61 | this.router.navigate(['login']);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/client/src/app/auth/register/register.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { FormControl, FormGroup, FormGroupDirective, FormsModule, NgForm, ReactiveFormsModule, Validators } from '@angular/forms';
3 | import { Router } from '@angular/router';
4 | import { Auth } from '../../auth';
5 | import { ErrorStateMatcher } from '@angular/material/core';
6 | import { CommonModule } from '@angular/common';
7 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
8 | import { MatCardModule } from '@angular/material/card';
9 | import { MatFormFieldModule } from '@angular/material/form-field';
10 | import { MatInputModule } from '@angular/material/input';
11 |
12 | @Component({
13 | selector: 'app-register',
14 | imports: [
15 | FormsModule,
16 | ReactiveFormsModule,
17 | CommonModule,
18 | MatProgressSpinnerModule,
19 | MatCardModule,
20 | MatFormFieldModule,
21 | MatInputModule
22 | ],
23 | templateUrl: './register.html',
24 | styleUrl: './register.scss'
25 | })
26 | export class Register {
27 | registerForm = new FormGroup({
28 | 'email': new FormControl(null, Validators.required),
29 | 'password': new FormControl(null, Validators.required),
30 | 'fullName': new FormControl(null, Validators.required)
31 | });
32 | fullName = '';
33 | email = '';
34 | password = '';
35 | isLoadingResults = false;
36 | matcher = new MyErrorStateMatcher();
37 |
38 | constructor(private router: Router, private authService: Auth) { }
39 |
40 | onFormSubmit() {
41 | const value = this.registerForm.value;
42 | this.authService.register(value)
43 | .subscribe({
44 | next: (res) => {
45 | this.router.navigate(['login']);
46 | }, error: (err) => {
47 | console.log(err);
48 | alert(err.error);
49 | }
50 | });
51 | }
52 |
53 | }
54 |
55 | /** Error when invalid control is dirty, touched, or submitted. */
56 | export class MyErrorStateMatcher implements ErrorStateMatcher {
57 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
58 | const isSubmitted = form && form.submitted;
59 | return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/src/app/auth/login/login.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component } from '@angular/core';
3 | import { FormBuilder, FormControl, FormGroup, FormGroupDirective, FormsModule, NgForm, ReactiveFormsModule, Validators } from '@angular/forms';
4 | import { Router } from '@angular/router';
5 | import { Auth } from '../../auth';
6 | import { ErrorStateMatcher } from '@angular/material/core';
7 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
8 | import { MatCardModule } from '@angular/material/card';
9 | import { MatFormFieldModule } from '@angular/material/form-field';
10 | import { MatInputModule } from '@angular/material/input';
11 |
12 | /** Error when invalid control is dirty, touched, or submitted. */
13 | export class MyErrorStateMatcher implements ErrorStateMatcher {
14 | isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
15 | const isSubmitted = form && form.submitted;
16 | return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
17 | }
18 | }
19 |
20 | @Component({
21 | selector: 'app-login',
22 | imports: [
23 | FormsModule,
24 | ReactiveFormsModule,
25 | CommonModule,
26 | MatProgressSpinnerModule,
27 | MatCardModule,
28 | MatFormFieldModule,
29 | MatInputModule
30 | ],
31 | templateUrl: './login.html',
32 | styleUrl: './login.scss'
33 | })
34 | export class Login {
35 | loginForm = new FormGroup({
36 | 'email': new FormControl(null, Validators.required),
37 | 'password': new FormControl(null, Validators.required)
38 | });
39 | email = '';
40 | password = '';
41 | matcher = new MyErrorStateMatcher();
42 | isLoadingResults = false;
43 |
44 | constructor(private formBuilder: FormBuilder, private router: Router, private authService: Auth) { }
45 |
46 | onFormSubmit() {
47 | const value = this.loginForm.value;
48 | this.authService.login(value)
49 | .subscribe({
50 | next: (res) => {
51 | console.log(res);
52 | if (res.token) {
53 | localStorage.setItem('token', res.token);
54 | this.router.navigate(['products']);
55 | }
56 | },
57 | error: (err) => {
58 | console.log(err);
59 | }
60 | });
61 | }
62 |
63 | register() {
64 | this.router.navigate(['register']);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/djamware/spring_angular_auth/controllers/AuthController.java:
--------------------------------------------------------------------------------
1 | package com.djamware.spring_angular_auth.controllers;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.security.authentication.AuthenticationManager;
9 | import org.springframework.security.authentication.BadCredentialsException;
10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
11 | import org.springframework.security.core.AuthenticationException;
12 | import org.springframework.web.bind.annotation.CrossOrigin;
13 | import org.springframework.web.bind.annotation.PostMapping;
14 | import org.springframework.web.bind.annotation.RequestBody;
15 | import org.springframework.web.bind.annotation.RequestMapping;
16 | import org.springframework.web.bind.annotation.RestController;
17 |
18 | import com.djamware.spring_angular_auth.configs.JwtTokenProvider;
19 | import com.djamware.spring_angular_auth.models.User;
20 | import com.djamware.spring_angular_auth.repositories.UserRepository;
21 | import com.djamware.spring_angular_auth.services.CustomUserDetailsService;
22 |
23 | @CrossOrigin(origins = "*")
24 | @RestController
25 | @RequestMapping("/api/auth")
26 | public class AuthController {
27 |
28 | @Autowired
29 | AuthenticationManager authenticationManager;
30 |
31 | @Autowired
32 | JwtTokenProvider jwtTokenProvider;
33 |
34 | @Autowired
35 | UserRepository users;
36 |
37 | @Autowired
38 | private CustomUserDetailsService userService;
39 |
40 | @PostMapping("/login")
41 | public ResponseEntity> login(@RequestBody AuthBody data) {
42 | try {
43 | String username = data.getEmail();
44 | authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword()));
45 | String token = jwtTokenProvider.createToken(username, this.users.findByEmail(username).getRoles());
46 | Map