diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 77b3745..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 - "recommendations": ["angular.ng-template"] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 740e35a..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "ng serve", - "type": "pwa-chrome", - "request": "launch", - "preLaunchTask": "npm: start", - "url": "http://localhost:4200/" - }, - { - "name": "ng test", - "type": "chrome", - "request": "launch", - "preLaunchTask": "npm: test", - "url": "http://localhost:9876/debug.html" - } - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index a298b5b..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "start", - "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } - } - }, - { - "type": "npm", - "script": "test", - "isBackground": true, - "problemMatcher": { - "owner": "typescript", - "pattern": "$tsc", - "background": { - "activeOnStart": true, - "beginsPattern": { - "regexp": "(.*?)" - }, - "endsPattern": { - "regexp": "bundle generation complete" - } - } - } - } - ] -} diff --git a/README.md b/README.md index 7be4313..333b42f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ -# JwtAuth +# Ngx JWT Auth + + + -A library for Token-Based Authentication. This library is configurable for any use cases. +A library for Token-Based Authentication (JWT Authentication). + +This library is configurable for any use cases. ## Other languages -- [Russian](./projects/ngx-jwt-auth/src/doc/ru/README.md) +- [Russian](./doc/ru/README.md) ## Content - [Description](#description) @@ -34,7 +39,7 @@ Features: 1. Import `JwtAuthModule` into the App/Core module of your application with a call to the `forRoot` method, and pass parameters to this method: ```typescript -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; @NgModule({ imports: [ @@ -44,7 +49,7 @@ import { JwtAuthModule } from 'jwt-auth'; export class AppModule {} ``` -2. You need to create an Api-service by implementing the [BaseAuthApiService](./projects/ngx-jwt-auth/src/lib/services/base-auth-http-service.ts) base class. This class obliges to implement 3 methods `login`, `logout` and `refresh`. The `login` and `refresh` methods must return an Observable with the value `{ accessToken: string; refreshToken?: string; }`, if your server in the `login` authorization method and\or in the `refresh` access token refresh method returns a different format, then it is quite easy to map the value with the `map` operator from rxjs to the desired format. An example of such a service: +2. You need to create an Api-service by implementing the [BaseAuthApiService](./projects/ngx-jwt-auth/src/lib/services/base-auth-api-service.ts) base class. This class obliges to implement 3 methods `login`, `logout` and `refresh`. The `login` and `refresh` methods must return an Observable with the value `{ accessToken: string; refreshToken?: string; }`, if your server in the `login` authorization method and\or in the `refresh` access token refresh method returns a different format, then it is quite easy to map the value with the `map` operator from rxjs to the desired format. An example of such a service: ```typescript @Injectable({ @@ -97,7 +102,7 @@ import { JwtAuthModule, InMemoryTokenStorage, LocalStorageTokenStorage -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -127,7 +132,7 @@ import { InMemoryTokenStorage, LocalStorageTokenStorage, JwtAuthInterceptor -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -219,7 +224,7 @@ In the example below, only an unauthorized user can access the `/auth/login` and ```typescript import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard, UnAuthGuard } from 'jwt-auth'; +import { AuthGuard, UnAuthGuard } from '@dekh/ngx-jwt-auth'; import { LoginComponent, RegistrationComponent } from '../auth'; import { DashboardComponent } from '../dashboard'; @@ -254,7 +259,7 @@ export class AppRoutingModule {} ``` ## Description of all library parameters -- `authApiService: Type` - A class that implements BaseAuthApiService and makes requests to the server. +- `authApiService: Type` - A class that implements `BaseAuthApiService` and makes requests to the server. - `tokenStorage: Type` - Storage of regular jwt tokens (not authorization ones). @@ -304,7 +309,7 @@ In order to create your own token storage, it is enough to implement the [BaseTo ```typescript // my-custom-token-storage.ts -import { BaseTokenStorage } from 'jwt-auth'; +import { BaseTokenStorage } from '@dekh/ngx-jwt-auth'; export class MyCustomTokenStorage extends BaseTokenStorage { public get(key: string): string | null { @@ -332,7 +337,7 @@ We define our storage in the parameters of the `JwtAuthModule` module: ```typescript // app.module.ts -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; @NgModule({ @@ -345,15 +350,26 @@ import { MyCustomTokenStorage } from '../auth'; export class AppModule {} ``` -Or we can register our storage using the `TokenStorage Registry` service: +Or we can register our storage using the `TokenStorageRegistry` service: ```typescript // app.service.ts -import { TokenStorageRegistry } from 'jwt-auth'; +import { + LocalStorageTokenStorage, + InMemoryTokenStorage, + TokenStorageRegistry +} from '@dekh/ngx-jwt-auth'; +import { AuthApiService } from '../services'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@NgModule({ + imports: [ + JwtAuthModule.forRoot({ + authApiService: AuthApiService, + tokenStorage: LocalStorageTokenStorage, + authTokenStorage: InMemoryTokenStorage, + }), + ], }) export class AppModule { constructor(private readonly _tokenStorageRegistry: TokenStorageRegistry) { @@ -362,28 +378,26 @@ export class AppModule { } ``` -Source code of the `TokenStorageRegistry` [here](./projects/ngx-jwt-auth/src/lib/services/token-storage-registry.service.ts). - ## Changing token storage at runtime -In rare cases, you may need to change the token storage at runtime, for this there are two services [TokenStorageManager](./projects/ngx-jwt-auth/src/lib/services/token-storage-manager.service.ts) and [AuthTokenStorageManager](./projects/ngx-jwt-auth/src/lib/services /auth-token-storage-manager.service.ts), both of these services have the same interaction interface. `TokenStorageManager` is used to manage the storage of __non-authorization__ tokens, and `AuthTokenStorageManager` is used to manage the storage of __authorization__ tokens. +In rare cases, you may need to change the token storage at runtime, for this there are two services [TokenStorageManager](./projects/ngx-jwt-auth/src/lib/services/token-storage-manager.service.ts) and [AuthTokenStorageManager](./projects/ngx-jwt-auth/src/lib/services/auth-token-storage-manager.service.ts), both of these services have the same interaction interface. `TokenStorageManager` is used to manage the storage of __non-authorization__ tokens, and `AuthTokenStorageManager` is used to manage the storage of __authorization__ tokens. -Пример: +Example: ```typescript -// app.service.ts +// token-storage-changer.service.ts import { AuthTokenStorageManager, TokenStorageRegistry, CookiesTokenStorage, BaseTokenStorage, -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@Injectable({ + provideIn: 'root' }) -export class AppModule { +export class TokenStorageChangerService { constructor( private readonly _authTokenStorageManager: AuthTokenStorageManager, private readonly _tokenStorageRegistry: TokenStrageRegistry, @@ -425,4 +439,4 @@ export class AppModule { The reason for this error is a cyclic call to `JwtAuthInterceptor`. Since the interceptor handles every request, except for those url requests specified in the `unsecuredUrls` config parameter, the token refresh request creates a circular dependency. - The solution to this problem is to specify in the `unsecuredUrls` array the URL or path of the accessToken update request, or specify the root path for all requests related to user authorization/registration, for example: `"/auth/"`, then all requests c path auth will be excluded from the interceptor check - `server.api/auth/login`, `server.api/auth/register`, `server.api/auth/refresh` and the like. \ No newline at end of file + The solution to this problem is to specify in the `unsecuredUrls` array the URL or path of the accessToken update request, or specify the root path for all requests related to user authorization/registration, for example: `"/auth/"`, then all requests c path auth will be excluded from the interceptor check - `server.api/auth/login`, `server.api/auth/register`, `server.api/auth/refresh` and etc. \ No newline at end of file diff --git a/projects/ngx-jwt-auth/src/doc/ru/README.md b/doc/ru/README.md similarity index 85% rename from projects/ngx-jwt-auth/src/doc/ru/README.md rename to doc/ru/README.md index e6efeab..4b9b29c 100644 --- a/projects/ngx-jwt-auth/src/doc/ru/README.md +++ b/doc/ru/README.md @@ -1,9 +1,11 @@ -# JwtAuth +# Ngx JWT Auth + + + Библиотека для Token-Based Authentication на основе Access и Refresh токенов. -## На других языках -- [English](../en/README.md) +Эта библиотека настраивается для любых вариантов использования. ## Содержание - [Описание](#описание) @@ -22,19 +24,19 @@ - выбирать где будут храниться токены, выбирая хранилище токенов (подробнее далее); - изменять хранилища токенов прямо в рантайме (подробнее далее); - создать свое кастомное хранилище токенов (подробнее далее); -- автоматически обновлять токен доступа (access token). Обновление происходит либо по истечению срока валидности токена доступа, либо указать коэффициент протухания токена `refreshThreshold` по достижению которого будет выполнено обновление токена, для этих целей используется interceptor [JwtAuthInterceptor](../../lib/interceptors/jwt-auth.interceptor.ts). -- ограничивать доступ на определенные роуты для не авторизованных пользователей, используя [AuthGuard](../../lib/guards/auth.guard.ts); -- ограничивать доступ на определенные роуты для авторизованных пользователей, используя [UnAuthGuard](../../lib/guards/un-auth.guard.ts); -- подписаться на поток `isLoggedIn$`, в котором храниться текущий статус аутентификации пользователя [JwtAuthService](../../lib/services/jwt-auth.service.ts); -- самому управлять токенами (получит, удалить, сохранить токен) через сервис [AuthTokenManager](../../lib/services/auth-token-manager.service.ts); -- управлять не только авторизационнами токенами, а любыми другими JWT токенами для этих целей выделены отдельные настройки в `JwtAuthModule`, отдельное хранилище токенов (можно использовать те же предопределенные хранилища, либо создать свое), отдельный сервис для работы с токенами [TokenManager](../../lib/services/token-manager.service.ts) и отдельный сервис для управления хранилищем токенов [TokenStorageManager](../../lib/services/token-storage-manager.service.ts). -- расширить базовые возможности путем создания кастомных хранилищ токенов, кастомных решений для управления токенами (расширить [BaseTokenManager](../../lib/services/base-token-manager.ts)) и хранилищами токенов (расширить [BaseTokenStorageManager](../../lib/services/base-token-storage-manager.ts)). +- автоматически обновлять токен доступа (access token). Обновление происходит либо по истечению срока валидности токена доступа, либо указать коэффициент протухания токена `refreshThreshold` по достижению которого будет выполнено обновление токена, для этих целей используется interceptor [JwtAuthInterceptor](../../projects/ngx-jwt-auth/src/lib/interceptors/jwt-auth.interceptor.ts). +- ограничивать доступ на определенные роуты для не авторизованных пользователей, используя [AuthGuard](../../projects/ngx-jwt-auth/src/lib/guards/auth.guard.ts); +- ограничивать доступ на определенные роуты для авторизованных пользователей, используя [UnAuthGuard](../../projects/ngx-jwt-auth/src/lib/guards/un-auth.guard.ts); +- подписаться на поток `isLoggedIn$`, в котором храниться текущий статус аутентификации пользователя [JwtAuthService](../../projects/ngx-jwt-auth/src/lib/services/jwt-auth.service.ts); +- самому управлять токенами (получит, удалить, сохранить токен) через сервис [AuthTokenManager](../../projects/ngx-jwt-auth/src/lib/services/auth-token-manager.service.ts); +- управлять не только авторизационнами токенами, а любыми другими JWT токенами для этих целей выделены отдельные настройки в `JwtAuthModule`, отдельное хранилище токенов (можно использовать те же предопределенные хранилища, либо создать свое), отдельный сервис для работы с токенами [TokenManager](../../projects/ngx-jwt-auth/src/lib/services/token-manager.service.ts) и отдельный сервис для управления хранилищем токенов [TokenStorageManager](../../projects/ngx-jwt-auth/src/lib/services/token-storage-manager.service.ts). +- расширить базовые возможности путем создания кастомных хранилищ токенов, кастомных решений для управления токенами (расширить [BaseTokenManager](../../projects/ngx-jwt-auth/src/lib/services/base-token-manager.ts)) и хранилищами токенов (расширить [BaseTokenStorageManager](../../projects/ngx-jwt-auth/src/lib/services/base-token-storage-manager.ts)). ## Настройка и применение 1. Импортировать `JwtAuthModule` в root/core модуль вашего приложения с вызовом метода `forRoot`, и в данный метод передать параметры: ```typescript -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; @NgModule({ imports: [ @@ -44,7 +46,7 @@ import { JwtAuthModule } from 'jwt-auth'; export class AppModule {} ``` -2. Необходимо создать Api-сервис, реализуя базовый класс [BaseAuthApiService](../../lib/services/base-auth-http-service.ts). Данный класс обязует реализовать 3 метода `login`, `logout` и `refresh`. Методы `login` и `refresh` должны возвращать Observable cо значение `{ accessToken: string; refreshToken?: string; }`, если ваш сервер в методе авторизации `login` и\или в методе обновления токена доступа `refresh` возвращает другой формат, то достаточно просто можно смаппить значение оператором `map` из rxjs в нужный формат. Пример такого сервиса: +2. Необходимо создать Api-сервис, реализуя базовый класс [BaseAuthApiService](../../projects/ngx-jwt-auth/src/lib/services/base-auth-api-service.ts). Данный класс обязует реализовать 3 метода `login`, `logout` и `refresh`. Методы `login` и `refresh` должны возвращать Observable cо значение `{ accessToken: string; refreshToken?: string; }`, если ваш сервер в методе авторизации `login` и\или в методе обновления токена доступа `refresh` возвращает другой формат, то достаточно просто можно смаппить значение оператором `map` из rxjs в нужный формат. Пример такого сервиса: ```typescript @Injectable({ @@ -98,7 +100,7 @@ import { JwtAuthModule, InMemoryTokenStorage, LocalStorageTokenStorage -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -114,7 +116,7 @@ import { AuthApiService } from '../services'; export class AppModule {} ``` -4. Запровайдить Interceptor [JwtAuthInterceptor](../../lib/interceptors/jwt-auth.interceptor.ts). +4. Запровайдить Interceptor [JwtAuthInterceptor](../../projects/ngx-jwt-auth/src/lib/interceptors/jwt-auth.interceptor.ts). > `JwtAuthInterceptor` реализует механизм обновления токена доступа путем проверки валидности токена и порога валидности `refreshTreshold` перед каждым запросом за исключением url запросов, которые указаны в параметре `unsecuredUrls`. Если токен не валиден, то будет произведена попытка обновления токена с последующим выполнением оригинального запроса, но если токен не сможет обновиться тогда пользователя разлогинет методом `logout` из `BaseAuthApiService`. @@ -128,7 +130,7 @@ import { InMemoryTokenStorage, LocalStorageTokenStorage, JwtAuthInterceptor -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -220,7 +222,7 @@ export class LoginComponent implements OnDestroy { ```typescript import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard, UnAuthGuard } from 'jwt-auth'; +import { AuthGuard, UnAuthGuard } from '@dekh/ngx-jwt-auth'; import { LoginComponent, RegistrationComponent } from '../auth'; import { DashboardComponent } from '../dashboard'; @@ -301,11 +303,11 @@ export class AppRoutingModule {} ## Создание своего хранилища токенов -Для того чтобы создать свое хранилище токенов достаточно реализовать базовый класс [BaseTokenStorage](../../lib/token-storages/base-token-storage.ts) и указать в параметре `customTokenStorages` модуля `JwtAuthModule.forRoot()` массив кастомных хранилище токенов. Пример: +Для того чтобы создать свое хранилище токенов достаточно реализовать базовый класс [BaseTokenStorage](../../projects/ngx-jwt-auth/src/lib/token-storages/base-token-storage.ts) и указать в параметре `customTokenStorages` модуля `JwtAuthModule.forRoot()` массив кастомных хранилище токенов. Пример: ```typescript // my-custom-token-storage.ts -import { BaseTokenStorage } from 'jwt-auth'; +import { BaseTokenStorage } from '@dekh/ngx-jwt-auth'; export class MyCustomTokenStorage extends BaseTokenStorage { public get(key: string): string | null { @@ -333,7 +335,7 @@ export class MyCustomTokenStorage extends BaseTokenStorage { ```typescript // app.module.ts -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; @NgModule({ @@ -350,11 +352,22 @@ export class AppModule {} ```typescript // app.service.ts -import { TokenStorageRegistry } from 'jwt-auth'; +import { + LocalStorageTokenStorage, + InMemoryTokenStorage, + TokenStorageRegistry +} from '@dekh/ngx-jwt-auth'; +import { AuthApiService } from '../services'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@NgModule({ + imports: [ + JwtAuthModule.forRoot({ + authApiService: AuthApiService, + tokenStorage: LocalStorageTokenStorage, + authTokenStorage: InMemoryTokenStorage, + }), + ], }) export class AppModule { constructor(private readonly _tokenStorageRegistry: TokenStorageRegistry) { @@ -363,28 +376,26 @@ export class AppModule { } ``` -Исходный код `TokenStorageRegistry` [здесь](../../lib/services/token-storage-registry.service.ts). - ## Смена хранилища токенов в рантайме -В редких случаях может понадобится в рантайме изменить хранилище токенов, для этого существует два сервиса [TokenStorageManager](../../lib/services/token-storage-manager.service.ts) и [AuthTokenStorageManager](../../lib/services/auth-token-storage-manager.service.ts), оба этих сервиса имеют одинаковый интерфейс взаимодествия. `TokenStorageManager` используется для управление хранилищем __не авторизационных__ токенов, а `AuthTokenStorageManager` для управление хранилищем __авторизационных__ токенов. +В редких случаях может понадобится в рантайме изменить хранилище токенов, для этого существует два сервиса [TokenStorageManager](../../projects/ngx-jwt-auth/src/lib/services/token-storage-manager.service.ts) и [AuthTokenStorageManager](../../projects/ngx-jwt-auth/src/lib/services/auth-token-storage-manager.service.ts), оба этих сервиса имеют одинаковый интерфейс взаимодествия. `TokenStorageManager` используется для управление хранилищем __не авторизационных__ токенов, а `AuthTokenStorageManager` для управление хранилищем __авторизационных__ токенов. Пример: ```typescript -// app.service.ts +// token-storage-changer.service.ts import { AuthTokenStorageManager, TokenStorageRegistry, CookiesTokenStorage, BaseTokenStorage, -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@Injectable({ + provideIn: 'root' }) -export class AppModule { +export class TokenStorageChangerService { constructor( private readonly _authTokenStorageManager: AuthTokenStorageManager, private readonly _tokenStorageRegistry: TokenStrageRegistry, diff --git a/projects/ngx-jwt-auth/README.md b/projects/ngx-jwt-auth/README.md index a77c301..c2ac540 100644 --- a/projects/ngx-jwt-auth/README.md +++ b/projects/ngx-jwt-auth/README.md @@ -1,9 +1,14 @@ -# JwtAuth +# Ngx JWT Auth + + + -A library for Token-Based Authentication. This library is configurable for any use cases. +A library for Token-Based Authentication (JWT Authentication). + +This library is configurable for any use cases. ## Other languages -- [Russian](./src/doc/ru/README.md) +- [Russian](../../doc/ru/README.md) ## Content - [Description](#description) @@ -34,7 +39,7 @@ Features: 1. Import `JwtAuthModule` into the App/Core module of your application with a call to the `forRoot` method, and pass parameters to this method: ```typescript -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; @NgModule({ imports: [ @@ -44,7 +49,7 @@ import { JwtAuthModule } from 'jwt-auth'; export class AppModule {} ``` -2. You need to create an Api-service by implementing the [BaseAuthApiService](./src/lib/services/base-auth-http-service.ts) base class. This class obliges to implement 3 methods `login`, `logout` and `refresh`. The `login` and `refresh` methods must return an Observable with the value `{ accessToken: string; refreshToken?: string; }`, if your server in the `login` authorization method and\or in the `refresh` access token refresh method returns a different format, then it is quite easy to map the value with the `map` operator from rxjs to the desired format. An example of such a service: +2. You need to create an Api-service by implementing the [BaseAuthApiService](./src/lib/services/base-auth-api-service.ts) base class. This class obliges to implement 3 methods `login`, `logout` and `refresh`. The `login` and `refresh` methods must return an Observable with the value `{ accessToken: string; refreshToken?: string; }`, if your server in the `login` authorization method and\or in the `refresh` access token refresh method returns a different format, then it is quite easy to map the value with the `map` operator from rxjs to the desired format. An example of such a service: ```typescript @Injectable({ @@ -97,7 +102,7 @@ import { JwtAuthModule, InMemoryTokenStorage, LocalStorageTokenStorage -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -127,7 +132,7 @@ import { InMemoryTokenStorage, LocalStorageTokenStorage, JwtAuthInterceptor -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { AuthApiService } from '../services'; @NgModule({ @@ -219,7 +224,7 @@ In the example below, only an unauthorized user can access the `/auth/login` and ```typescript import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard, UnAuthGuard } from 'jwt-auth'; +import { AuthGuard, UnAuthGuard } from '@dekh/ngx-jwt-auth'; import { LoginComponent, RegistrationComponent } from '../auth'; import { DashboardComponent } from '../dashboard'; @@ -254,7 +259,7 @@ export class AppRoutingModule {} ``` ## Description of all library parameters -- `authApiService: Type` - A class that implements BaseAuthApiService and makes requests to the server. +- `authApiService: Type` - A class that implements `BaseAuthApiService` and makes requests to the server. - `tokenStorage: Type` - Storage of regular jwt tokens (not authorization ones). @@ -304,7 +309,7 @@ In order to create your own token storage, it is enough to implement the [BaseTo ```typescript // my-custom-token-storage.ts -import { BaseTokenStorage } from 'jwt-auth'; +import { BaseTokenStorage } from '@dekh/ngx-jwt-auth'; export class MyCustomTokenStorage extends BaseTokenStorage { public get(key: string): string | null { @@ -332,7 +337,7 @@ We define our storage in the parameters of the `JwtAuthModule` module: ```typescript // app.module.ts -import { JwtAuthModule } from 'jwt-auth'; +import { JwtAuthModule } from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; @NgModule({ @@ -345,15 +350,26 @@ import { MyCustomTokenStorage } from '../auth'; export class AppModule {} ``` -Or we can register our storage using the `TokenStorage Registry` service: +Or we can register our storage using the `TokenStorageRegistry` service: ```typescript // app.service.ts -import { TokenStorageRegistry } from 'jwt-auth'; +import { + LocalStorageTokenStorage, + InMemoryTokenStorage, + TokenStorageRegistry +} from '@dekh/ngx-jwt-auth'; +import { AuthApiService } from '../services'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@NgModule({ + imports: [ + JwtAuthModule.forRoot({ + authApiService: AuthApiService, + tokenStorage: LocalStorageTokenStorage, + authTokenStorage: InMemoryTokenStorage, + }), + ], }) export class AppModule { constructor(private readonly _tokenStorageRegistry: TokenStorageRegistry) { @@ -362,8 +378,6 @@ export class AppModule { } ``` -Source code of the `TokenStorageRegistry` [here](./src/lib/services/token-storage-registry.service.ts). - ## Changing token storage at runtime In rare cases, you may need to change the token storage at runtime, for this there are two services [TokenStorageManager](./src/lib/services/token-storage-manager.service.ts) and [AuthTokenStorageManager](./src/lib/services /auth-token-storage-manager.service.ts), both of these services have the same interaction interface. `TokenStorageManager` is used to manage the storage of __non-authorization__ tokens, and `AuthTokenStorageManager` is used to manage the storage of __authorization__ tokens. @@ -371,19 +385,19 @@ In rare cases, you may need to change the token storage at runtime, for this the Пример: ```typescript -// app.service.ts +// token-storage-changer.service.ts import { AuthTokenStorageManager, TokenStorageRegistry, CookiesTokenStorage, BaseTokenStorage, -} from 'jwt-auth'; +} from '@dekh/ngx-jwt-auth'; import { MyCustomTokenStorage } from '../auth'; -@Injactable({ - provideIn: 'root' +@Injectable({ + provideIn: 'root' }) -export class AppModule { +export class TokenStorageChangerService { constructor( private readonly _authTokenStorageManager: AuthTokenStorageManager, private readonly _tokenStorageRegistry: TokenStrageRegistry, diff --git a/projects/ngx-jwt-auth/package.json b/projects/ngx-jwt-auth/package.json index cbef244..004a6a4 100644 --- a/projects/ngx-jwt-auth/package.json +++ b/projects/ngx-jwt-auth/package.json @@ -1,6 +1,6 @@ { "name": "@dekh/ngx-jwt-auth", - "version": "1.0.0", + "version": "1.0.2", "license": "MIT", "repository": { "type": "GitHub", diff --git a/projects/ngx-jwt-auth/src/doc/en/README.md b/projects/ngx-jwt-auth/src/doc/en/README.md deleted file mode 100644 index b2831f1..0000000 --- a/projects/ngx-jwt-auth/src/doc/en/README.md +++ /dev/null @@ -1,428 +0,0 @@ -# JwtAuth - -A library for Token-Based Authentication. This library is configurable for any use cases. - -## Other languages -- [Russian](../ru/README.md) - -## Content -- [Description](#description) -- [Setup and use](#setup-and-use) -- [Description of all library parameters](#description-of-all-library-parameters) -- [List of predefined Token Storages](#list-of-predefined-token-storages) -- [Creating your own Token Storage](#creating-your-own-token-storage) -- [Changing token storage at runtime](#changing-token-storage-at-runtime) -- [Troubleshooting](#troubleshooting) - -## Description - -This library implements the main features and basic requirements for Token-Based Authentication in an Angular application. - -Features: -- choose where tokens will be stored by choosing a token storage; -- change the storage of tokens directly in runtime; -- create your own custom storage of tokens; -- automatically refresh the access token. Refresh occurs either after the validity period of the access token expires, or specify the `refreshThreshold` token decay coefficient, upon reaching which the token will be updated, for these purposes the interceptor [JwtAuthInterceptor](../../lib/interceptors/jwt-auth.interceptor.ts) is used). -- restrict access to certain routes for unauthorized users using [AuthGuard](../../lib/guards/auth.guard.ts); -- restrict access to certain routes for authorized users using [UnAuthGuard](../../lib/guards/un-auth.guard.ts); -- subscribe to the `isLoggedIn$` stream, which stores the current user authentication status [JwtAuthService](../../lib/services/jwt-auth.service.ts); -- manage tokens yourself (get, delete, save a token) through the service [AuthTokenManager](../../lib/services/auth-token-manager.service.ts); -- manage not only authorization tokens, but any other JWT tokens for these purposes, there are separate settings in `JwtAuthModule`, a separate token storage (you can use the same predefined storages, or create your own), a separate service for working with tokens [TokenManager] (. /src/lib/services/token-manager.service.ts) and a separate service for managing token storage [TokenStorageManager](../../lib/services/token-storage-manager.service.ts). -- extend the basic features by creating custom token stores, custom token management solutions (extend [BaseTokenManager](../../lib/services/base-token-manager.ts)) and token stores (extend [BaseTokenStorageManager](../../lib /lib/services/base-token-storage-manager.ts)). - -## Setup and use -1. Import `JwtAuthModule` into the App/Core module of your application with a call to the `forRoot` method, and pass parameters to this method: - -```typescript -import { JwtAuthModule } from 'jwt-auth'; - -@NgModule({ - imports: [ - JwtAuthModule.forRoot(options), - ], -}) -export class AppModule {} -``` - -2. You need to create an Api-service by implementing the [BaseAuthApiService](../../lib/services/base-auth-http-service.ts) base class. This class obliges to implement 3 methods `login`, `logout` and `refresh`. The `login` and `refresh` methods must return an Observable with the value `{ accessToken: string; refreshToken?: string; }`, if your server in the `login` authorization method and\or in the `refresh` access token refresh method returns a different format, then it is quite easy to map the value with the `map` operator from rxjs to the desired format. An example of such a service: - -```typescript -@Injectable({ - providedIn: 'root', -}) -export class AuthApiService extends BaseAuthApiService { - constructor(private readonly _httpClient: HttpClient) { - super(); - } - - // This method returns AuthResponseTokens which has the structure - // { accessToken: string; refreshToken?: string; }, so you don't need to map anything! - public login(credentials: Login): Observable { - return this._httpClient.post( - environments.apiUrl + '/auth/login', - credentials, - { withCredentials: true } - ); - } - - public logout(): Observable { - return this._httpClient.post(environments.apiUrl + '/auth/logout', null, { - withCredentials: true, - }); - } - - // Since this method does not return the model we need from the server, - // we map it using the map operator in { accessToken: string; refreshToken?: string; } - public refresh(): Observable { - return this._httpClient.post(environments.apiUrl + '/auth/refresh', null, { - withCredentials: true, - }).pipe( - map((res) => ({ - accessToken: res.tokens.newAccessToken, - refreshToken: res.tokens.newRefreshToken, - })) - ); - } - - public register(credentials: Registration): Observable { - return this._httpClient.post(environments.apiUrl + '/auth/register', credentials); - } -} -``` - -3. Next, you need to pass the required parameters to the `JwtAuthModule.forRoot(options)` parameters: - -```typescript -import { - JwtAuthModule, - InMemoryTokenStorage, - LocalStorageTokenStorage -} from 'jwt-auth'; -import { AuthApiService } from '../services'; - -@NgModule({ - imports: [ - JwtAuthModule.forRoot({ - // Our previously created AuthApiService - authApiService: AuthApiService, - tokenStorage: LocalStorageTokenStorage, - authTokenStorage: InMemoryTokenStorage, - }), - ], -}) -export class AppModule {} -``` - -4. Provide Interceptor [JwtAuthInterceptor](../../lib/interceptors/jwt-auth.interceptor.ts). - -> `JwtAuthInterceptor` implements an access token refresh mechanism by checking the validity of the token and the `refreshTreshold` validity threshold before each request, except for requests whose URL is specified in the `unsecuredUrls` parameter. If the token is not valid, then an attempt will be made to refresh the token followed by the original request, but if the token cannot be refreshed, then the user will be logged out using the `logout` method from `BaseAuthApiService`. - -> It is not mandatory to use `JwtAuthInterceptor`, you can implement your own mechanism for intercepting requests with subsequent refresh of the access token. - -Example: - -```typescript -import { - JwtAuthModule, - InMemoryTokenStorage, - LocalStorageTokenStorage, - JwtAuthInterceptor -} from 'jwt-auth'; -import { AuthApiService } from '../services'; - -@NgModule({ - imports: [ - JwtAuthModule.forRoot({ - authApiService: AuthApiService, - tokenStorage: LocalStorageTokenStorage, - authTokenStorage: InMemoryTokenStorage, - }), - ], - providers: [ - { - provide: HTTP_INTERCEPTORS, - useClass: JwtAuthInterceptor, - multi: true, - }, - ], -}) -export class AppModule {} -``` - -5. If in the application we need to get authorization or log out, then we must use the `JwtAuthService` proxy service, which under the hood offers methods from our `AuthApiService` service, and performs additional actions - saves accessToken and refreshToken in storage, updates the authorization status in ` isLoggedIn$`. - -Example: - -On the authorization form, when sending it, you need to use `JwtAuthService` and call the `login(...args[]: any)` method; all arguments passed to this method will be passed to the `login(...args[]: any)` method our previously created Api-service for authorization `AuthApiService` (all parameters are passed for each method defined in `BaseAuthApiService`): - -```typescript -@Component({ - selector: 'app-login', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class LoginComponent implements OnDestroy { - public form: FormGroup; - - private readonly _isLoading$ = new BehaviorSubject(false); - public readonly isLoading$ = this._isLoading$.asObservable(); - - private readonly _loginError$ = new BehaviorSubject(null); - public readonly loginError$ = this._loginError$.asObservable(); - - private readonly _destroy$ = new Subject(); - - constructor( - private readonly _fb: FormBuilder, - private readonly _jwtAuthService: JwtAuthService, - ) { - this._createForm(); - } - - public ngOnDestroy(): void { - this._destroy$.next(); - this._destroy$.complete(); - } - - public login(): void { - this._isLoading$.next(true); - - // Login class it`s Domain model - const credentials = new Login(this.form.value); - - this._authService - .login(credentials) - .pipe( - tap(() => this._loginError$.next(null)), - catchError((error: HttpError) => { - this._loginError$.next(error.error.message); - return EMPTY; - }), - finalize(() => this._isLoading$.next(false)) - ) - .subscribe(); - } - - private _createForm(): void { - this.form = this._fb.group({ - email: [null, [Validators.required, Validators.email]], - password: [null, [Validators.required]], - }); - } -} -``` - -6. Restrict access to routes that can only be accessed by an authorized user or vice versa only by an unauthorized user. -In the example below, only an unauthorized user can access the `/auth/login` and `/auth/registration` pages, and only an authorized user can open the `/dashboard` page: - -```typescript -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard, UnAuthGuard } from 'jwt-auth'; -import { LoginComponent, RegistrationComponent } from '../auth'; -import { DashboardComponent } from '../dashboard'; - -const routes: Routes = [ - { - path: 'auth', - children: [ - { - path: 'login', - component: LoginComponent, - canActivate: [UnAuthGuard], - }, - { - path: 'registration', - component: RegistrationComponent, - canActivate: [UnAuthGuard], - }, - ], - }, - { - path: 'dashboard', - component: DashboardComponent, - canActivate: [AuthGuard], - }, -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], -}) -export class AppRoutingModule {} -``` - -## Description of all library parameters -- `authApiService: Type` - A class that implements BaseAuthApiService and makes requests to the server. - -- `tokenStorage: Type` - Storage of regular jwt tokens (not authorization ones). - -- `authTokenStorage: Type` - Storage of authorization tokens. - -- `authHeaderName?: string` - The name of the Http Header that will be used for authorization. By default `Authorization`. - -- `authScheme?: string` - A prefix in the Http Header value that defines the authorization scheme. By default `Bearer`. - -- `tokenExpField?: string` - The field in the payload of the token, in which the timestamp is stored when the token expires. By default `exp`. - -- `tokenIatField?: string` - A field in the payload of the token that stores the timestamp when the token was issued. By default `iat`. - -- `customTokenStorages?: BaseTokenStorage[]` - An array of custom (custom) token storages. By default empty array `[]`. - -- `unsecuredUrls?: string[]` - An array of URLs and Paths that will not be processed by the AuthInterceptor. those. the access token will not be checked on the specified URL and Path and the access token will be updated if it has expired. By default empty array `[]`. - > **Notice:** specify your URL for authorization, for example `http://localhost:5000/auth` or part of the URL - `/auth/refresh`,`/auth/login`, `/auth/registration` or all URLs for authentication `/auth ` to avoid an infinitely recursive token refresh call when a 401 status code is issued. - - > **Notice:** always specify the URL for updating the token, otherwise there will be a circular dependency: - >> ERROR Error: NG0200: Circular dependency in DI detected for JwtAuthService. - > - > This is because `HttpClient` depends on `Interceptor (JwtAuthInterceptor)` depends on `AuthApiService` depends on `HttpClient`. - > - > [The way to fix this error is here.](#troubleshooting) - -- `refreshThreshold?: number` - The coefficient of the token refresh threshold, if the expireIn access token approaches this coefficient, then the token will be refreshed. By default `0.8`. - -- `saveRefreshTokenInStorage?: boolean` - Whether to save the refresh token that comes with authorization and / or when refreshing the token. By default `false`. - > You should enable this option only if the server does not store the refresh token in the cookie, then to refresh the token you need to pass it in the refresh request, and you will have to store it on the client, which is initially a bad practice and can lead to problems in protecting the application by stealing access and refresh tokens. - - > **Notice:** if this option is enabled, then you should change `authTokenStorage` from `InMemoryTokenStorage` to any other predefined or custom `TokenStorage`. If this is not done, then the user will have to log in every time the page is updated, since when the page is updated, the memory and, accordingly, `InMemoryTokenStorage` will be cleared, this is how JS is arranged. - -- `unAuthGuardRedirectUrl?: string` - The URL where the authorized user will be redirected to if he tries to access the route protected by UnAuthGuard. If you do not set a value, then routes protected by UnAuthGuard will simply reject the transition to this route. - -- `authGuardRedirectUrl?: string` - The URL where an unauthorized user will be redirected to if they try to access a route protected by AuthGuard. If not set, then routes protected by AuthGuard will simply reject the transition to this route. - -## List of predefined Token Storages - -- `CookiesTokenStorage` - abstraction over cookies, saves tokens in cookies; -- `LocalStorageTokenStorage` - abstraction over localStorage, stores tokens in localStorage; -- `SessionStorageTokenStorage` - abstraction over sessionStorage, saves tokens in sessionStorage; -- `InMemoryTokenStorage` - saves tokens in the application memory, there are some drawbacks, when using this storage for authorization tokens, after reloading the page, a request will be made to update the access token (for SPA applications this is not critical), but the most secure storage for authorization tokens; - -## Creating your own Token Storage - -In order to create your own token storage, it is enough to implement the [BaseTokenStorage](../../lib/token-storages/base-token-storage.ts) base class and specify an array of custom storage of tokens. Example: - -```typescript -// my-custom-token-storage.ts -import { BaseTokenStorage } from 'jwt-auth'; - -export class MyCustomTokenStorage extends BaseTokenStorage { - public get(key: string): string | null { - // custom realisation - } - - public set(key: string, token: string): void { - // custom realisation - } - - public delete(key: string): void { - // custom realisation - } - - // We can override the method to check the validity of tokens - // but this is not recommended! - public override isValid(key: string): boolean { - // super.isValid(); - // custom realisation - } -} -``` - -We define our storage in the parameters of the `JwtAuthModule` module: - -```typescript -// app.module.ts -import { JwtAuthModule } from 'jwt-auth'; -import { MyCustomTokenStorage } from '../auth'; - -@NgModule({ - imports: [ - JwtAuthModule.forRoot({ - customTokenStorages: [new MyCustomTokenStorage()], - }), - ], -}) -export class AppModule {} -``` - -Or we can register our storage using the `TokenStorage Registry` service: - -```typescript -// app.service.ts -import { TokenStorageRegistry } from 'jwt-auth'; -import { MyCustomTokenStorage } from '../auth'; - -@Injactable({ - provideIn: 'root' -}) -export class AppModule { - constructor(private readonly _tokenStorageRegistry: TokenStorageRegistry) { - this._tokenStorageRegistry.register(new MyCustomTokenStorage()); - } -} -``` - -Source code of the `TokenStorageRegistry` [here](../../lib/services/token-storage-registry.service.ts). - -## Changing token storage at runtime - -In rare cases, you may need to change the token storage at runtime, for this there are two services [TokenStorageManager](../../lib/services/token-storage-manager.service.ts) and [AuthTokenStorageManager](../../lib/services /auth-token-storage-manager.service.ts), both of these services have the same interaction interface. `TokenStorageManager` is used to manage the storage of __non-authorization__ tokens, and `AuthTokenStorageManager` is used to manage the storage of __authorization__ tokens. - -Пример: - -```typescript -// app.service.ts -import { - AuthTokenStorageManager, - TokenStorageRegistry, - CookiesTokenStorage, - BaseTokenStorage, -} from 'jwt-auth'; -import { MyCustomTokenStorage } from '../auth'; - -@Injactable({ - provideIn: 'root' -}) -export class AppModule { - constructor( - private readonly _authTokenStorageManager: AuthTokenStorageManager, - private readonly _tokenStorageRegistry: TokenStrageRegistry, - ) { - this._tokenStorageRegistry.register(new MyCustomTokenStorage()); - } - - public setMyCustomStorage(): void { - if (!this._tokenStorageRegistry.isRegistered(MyCustomTokenStorage)) { - throw new Error('MyCustomTokenStorage is not registered!'); - } - - const myCustomStorage = this._tokenStorageRegistry.get(MyCustomTokenStorage); - // or - // const myCustomStorage = this._tokenStorageRegistry.get(new MyCustomTokenStorage()); - // or - // const myCustomStorage = this._tokenStorageRegistry.get('MyCustomTokenStorage'); - this.changeAuthStorage(myCustomStorage); - } - - public setCookiesStorage(): void { - const cookiesStorage = this._tokenStorageRegistry.get(CookiesTokenStorage); - // or - // const cookiesStorage = this._tokenStorageRegistry.get(new CookiesTokenStorage()); - // or - // const cookiesStorage = this._tokenStorageRegistry.get('CookiesTokenStorage'); - this.changeAuthStorage(storage); - } - - public changeAuthStorage(storage: BaseTokenStorage): void { - this._authTokenStorageManager.setStorage(storage); - } -} -``` - -## Troubleshooting - -- When starting the application, it gives an error __"ERROR Error: NG0200: Circular dependency in DI detected for JwtAuthService."__ - - The reason for this error is a cyclic call to `JwtAuthInterceptor`. Since the interceptor handles every request, except for those url requests specified in the `unsecuredUrls` config parameter, the token refresh request creates a circular dependency. - - The solution to this problem is to specify in the `unsecuredUrls` array the URL or path of the accessToken update request, or specify the root path for all requests related to user authorization/registration, for example: `"/auth/"`, then all requests c path auth will be excluded from the interceptor check - `server.api/auth/login`, `server.api/auth/register`, `server.api/auth/refresh` and the like. \ No newline at end of file diff --git a/projects/ngx-jwt-auth/src/lib/guards/auth.guard.ts b/projects/ngx-jwt-auth/src/lib/guards/auth.guard.ts index 4be34e2..d93de97 100644 --- a/projects/ngx-jwt-auth/src/lib/guards/auth.guard.ts +++ b/projects/ngx-jwt-auth/src/lib/guards/auth.guard.ts @@ -6,7 +6,6 @@ import { JWT_AUTH_CONFIG } from '../injection-tokens'; import { JwtAuthConfig } from '../jwt-auth-config'; import { JwtAuthService } from '../services'; - /** * Guard to restrict route access to unauthorized users. */ diff --git a/projects/ngx-jwt-auth/src/lib/interfaces/auth-api-service.ts b/projects/ngx-jwt-auth/src/lib/interfaces/auth-api-service.ts index 65aa767..6d815b2 100644 --- a/projects/ngx-jwt-auth/src/lib/interfaces/auth-api-service.ts +++ b/projects/ngx-jwt-auth/src/lib/interfaces/auth-api-service.ts @@ -6,7 +6,7 @@ export interface IAuthApiService { /** * Make request to logIn user. * - * Request should return object `{ accessToken: string, refreshToken: string }`. + * Request should return object `{ accessToken: string, refreshToken?: string }`. * If yours backend return different json, just map your data to necessary object (use`map` RxJs operator). * * @param args all arguments. @@ -23,7 +23,7 @@ export interface IAuthApiService { /** * Make request to refresh access token. * - * Request should return object `{ accessToken: string, refreshToken: string }`. + * Request should return object `{ accessToken: string, refreshToken?: string }`. * If yours backend return different json, just map your data to necessary object (use`map` RxJs operator). * * @param args all arguments. diff --git a/projects/ngx-jwt-auth/src/lib/jwt-auth-config.ts b/projects/ngx-jwt-auth/src/lib/jwt-auth-config.ts index 48fcc93..9033799 100644 --- a/projects/ngx-jwt-auth/src/lib/jwt-auth-config.ts +++ b/projects/ngx-jwt-auth/src/lib/jwt-auth-config.ts @@ -4,7 +4,7 @@ import { BaseTokenStorage } from "./token-storages"; export class JwtAuthConfig { /** - * A class that implements BaseAuthApiService and makes requests to the server for authorization. + * A class that implements `BaseAuthApiService` and makes requests to the server for authorization. */ public authApiService!: Type; /**