├── _buid.cmd ├── src ├── ASPNETCore2JwtAuthentication.AngularClient │ ├── ng │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── welcome │ │ │ │ ├── welcome.component.css │ │ │ │ ├── welcome.component.ts │ │ │ │ └── welcome.component.html │ │ │ ├── authentication │ │ │ │ ├── login │ │ │ │ │ ├── login.component.css │ │ │ │ │ ├── login.component.ts │ │ │ │ │ └── login.component.html │ │ │ │ ├── access-denied │ │ │ │ │ ├── access-denied.component.css │ │ │ │ │ ├── access-denied.component.html │ │ │ │ │ └── access-denied.component.ts │ │ │ │ ├── change-password │ │ │ │ │ ├── change-password.component.css │ │ │ │ │ ├── models │ │ │ │ │ │ └── change-password.ts │ │ │ │ │ ├── services │ │ │ │ │ │ └── change-password.service.ts │ │ │ │ │ └── change-password.component.ts │ │ │ │ ├── authentication.module.ts │ │ │ │ └── authentication-routing.module.ts │ │ │ ├── core │ │ │ │ ├── component │ │ │ │ │ └── header │ │ │ │ │ │ ├── header.component.css │ │ │ │ │ │ ├── header.component.ts │ │ │ │ │ │ └── header.component.html │ │ │ │ ├── models │ │ │ │ │ ├── auth-token-type.ts │ │ │ │ │ ├── credentials.ts │ │ │ │ │ ├── auth-guard-permission.ts │ │ │ │ │ └── auth-user.ts │ │ │ │ ├── services │ │ │ │ │ ├── app.config.ts │ │ │ │ │ ├── utils.service.ts │ │ │ │ │ ├── xsrf.interceptor.ts │ │ │ │ │ ├── api-config.service.ts │ │ │ │ │ └── browser-storage.service.ts │ │ │ │ └── index.ts │ │ │ ├── page-not-found │ │ │ │ ├── page-not-found.component.css │ │ │ │ ├── page-not-found.component.html │ │ │ │ └── page-not-found.component.ts │ │ │ ├── dashboard │ │ │ │ ├── protected-page │ │ │ │ │ ├── protected-page.component.css │ │ │ │ │ ├── protected-page.component.html │ │ │ │ │ └── protected-page.component.ts │ │ │ │ ├── call-protected-api │ │ │ │ │ ├── call-protected-api.component.css │ │ │ │ │ └── call-protected-api.component.html │ │ │ │ ├── dashboard.module.ts │ │ │ │ └── dashboard-routing.module.ts │ │ │ ├── app.component.html │ │ │ ├── app-routing.module.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ └── shared │ │ │ │ ├── directives │ │ │ │ ├── is-visible-for-auth-user.directive.ts │ │ │ │ └── has-auth-user-view-permission.directive.ts │ │ │ │ └── shared.module.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── typings.d.ts │ │ ├── favicon.ico │ │ ├── tsconfig.app.json │ │ ├── styles.css │ │ ├── index.html │ │ ├── main.ts │ │ ├── tsconfig.spec.json │ │ └── test.ts │ ├── _1-ng-serve.bat │ ├── _2-ng-build-dev.bat │ ├── _2-ng-build-prod.bat │ ├── _0-restore.bat │ ├── e2e │ │ ├── app.po.ts │ │ ├── tsconfig.e2e.json │ │ └── app.e2e-spec.ts │ ├── .editorconfig │ ├── .gitignore │ ├── protractor.conf.js │ ├── karma.conf.js │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── ASPNETCore2JwtAuthentication.WebApp │ ├── wwwroot │ │ ├── app_data │ │ │ └── git.keep │ │ ├── lib │ │ │ ├── bootstrap │ │ │ │ ├── scss │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── _clearfix.scss │ │ │ │ │ │ ├── _text-truncation.scss │ │ │ │ │ │ ├── _vr.scss │ │ │ │ │ │ ├── _visually-hidden.scss │ │ │ │ │ │ ├── _stretched-link.scss │ │ │ │ │ │ ├── _stacks.scss │ │ │ │ │ │ ├── _color-bg.scss │ │ │ │ │ │ ├── _colored-links.scss │ │ │ │ │ │ ├── _ratio.scss │ │ │ │ │ │ └── _position.scss │ │ │ │ │ ├── mixins │ │ │ │ │ │ ├── _clearfix.scss │ │ │ │ │ │ ├── _lists.scss │ │ │ │ │ │ ├── _color-scheme.scss │ │ │ │ │ │ ├── _text-truncate.scss │ │ │ │ │ │ ├── _resize.scss │ │ │ │ │ │ ├── _banner.scss │ │ │ │ │ │ ├── _backdrop.scss │ │ │ │ │ │ ├── _pagination.scss │ │ │ │ │ │ ├── _alert.scss │ │ │ │ │ │ ├── _container.scss │ │ │ │ │ │ ├── _image.scss │ │ │ │ │ │ ├── _box-shadow.scss │ │ │ │ │ │ ├── _reset-text.scss │ │ │ │ │ │ ├── _list-group.scss │ │ │ │ │ │ ├── _deprecate.scss │ │ │ │ │ │ ├── _transition.scss │ │ │ │ │ │ ├── _visually-hidden.scss │ │ │ │ │ │ ├── _table-variants.scss │ │ │ │ │ │ └── _caret.scss │ │ │ │ │ ├── bootstrap-reboot.scss │ │ │ │ │ ├── forms │ │ │ │ │ │ ├── _form-text.scss │ │ │ │ │ │ ├── _validation.scss │ │ │ │ │ │ └── _labels.scss │ │ │ │ │ ├── _forms.scss │ │ │ │ │ ├── _helpers.scss │ │ │ │ │ ├── bootstrap-utilities.scss │ │ │ │ │ ├── _transitions.scss │ │ │ │ │ ├── _grid.scss │ │ │ │ │ ├── _mixins.scss │ │ │ │ │ ├── _placeholders.scss │ │ │ │ │ ├── bootstrap.scss │ │ │ │ │ ├── _badge.scss │ │ │ │ │ ├── _close.scss │ │ │ │ │ ├── _images.scss │ │ │ │ │ ├── _containers.scss │ │ │ │ │ ├── bootstrap-grid.scss │ │ │ │ │ ├── _maps.scss │ │ │ │ │ ├── _breadcrumb.scss │ │ │ │ │ └── utilities │ │ │ │ │ │ └── _api.scss │ │ │ │ ├── LICENSE │ │ │ │ └── js │ │ │ │ │ └── src │ │ │ │ │ ├── util │ │ │ │ │ └── component-functions.js │ │ │ │ │ ├── dom │ │ │ │ │ ├── data.js │ │ │ │ │ └── manipulator.js │ │ │ │ │ └── button.js │ │ │ ├── jquery │ │ │ │ ├── src │ │ │ │ │ ├── var │ │ │ │ │ │ ├── arr.js │ │ │ │ │ │ ├── document.js │ │ │ │ │ │ ├── getProto.js │ │ │ │ │ │ ├── push.js │ │ │ │ │ │ ├── rcheckableType.js │ │ │ │ │ │ ├── slice.js │ │ │ │ │ │ ├── class2type.js │ │ │ │ │ │ ├── indexOf.js │ │ │ │ │ │ ├── pnum.js │ │ │ │ │ │ ├── fnToString.js │ │ │ │ │ │ ├── toString.js │ │ │ │ │ │ ├── documentElement.js │ │ │ │ │ │ ├── hasOwn.js │ │ │ │ │ │ ├── support.js │ │ │ │ │ │ ├── ObjectFunctionString.js │ │ │ │ │ │ ├── isWindow.js │ │ │ │ │ │ ├── whitespace.js │ │ │ │ │ │ ├── rcssNum.js │ │ │ │ │ │ ├── rtrimCSS.js │ │ │ │ │ │ ├── rnothtmlwhite.js │ │ │ │ │ │ ├── flat.js │ │ │ │ │ │ └── isFunction.js │ │ │ │ │ ├── selector.js │ │ │ │ │ ├── ajax │ │ │ │ │ │ ├── var │ │ │ │ │ │ │ ├── rquery.js │ │ │ │ │ │ │ ├── location.js │ │ │ │ │ │ │ └── nonce.js │ │ │ │ │ │ └── script.js │ │ │ │ │ ├── css │ │ │ │ │ │ ├── var │ │ │ │ │ │ │ ├── rcustomProp.js │ │ │ │ │ │ │ ├── cssExpand.js │ │ │ │ │ │ │ ├── rboxStyle.js │ │ │ │ │ │ │ ├── rnumnonpx.js │ │ │ │ │ │ │ ├── getStyles.js │ │ │ │ │ │ │ ├── swap.js │ │ │ │ │ │ │ └── isHiddenWithinTree.js │ │ │ │ │ │ ├── hiddenVisibleSelectors.js │ │ │ │ │ │ ├── addGetHookIf.js │ │ │ │ │ │ └── finalPropName.js │ │ │ │ │ ├── data │ │ │ │ │ │ └── var │ │ │ │ │ │ │ ├── dataPriv.js │ │ │ │ │ │ │ ├── dataUser.js │ │ │ │ │ │ │ └── acceptData.js │ │ │ │ │ ├── manipulation │ │ │ │ │ │ ├── var │ │ │ │ │ │ │ ├── rscriptType.js │ │ │ │ │ │ │ └── rtagName.js │ │ │ │ │ │ ├── setGlobalEval.js │ │ │ │ │ │ ├── getAll.js │ │ │ │ │ │ ├── _evalUrl.js │ │ │ │ │ │ ├── wrapMap.js │ │ │ │ │ │ └── support.js │ │ │ │ │ ├── traversing │ │ │ │ │ │ └── var │ │ │ │ │ │ │ ├── rneedsContext.js │ │ │ │ │ │ │ ├── siblings.js │ │ │ │ │ │ │ └── dir.js │ │ │ │ │ ├── event │ │ │ │ │ │ ├── support.js │ │ │ │ │ │ └── focusin.js │ │ │ │ │ ├── core │ │ │ │ │ │ ├── nodeName.js │ │ │ │ │ │ ├── readyException.js │ │ │ │ │ │ ├── var │ │ │ │ │ │ │ └── rsingleTag.js │ │ │ │ │ │ ├── stripAndCollapse.js │ │ │ │ │ │ ├── toType.js │ │ │ │ │ │ ├── camelCase.js │ │ │ │ │ │ ├── support.js │ │ │ │ │ │ ├── parseXML.js │ │ │ │ │ │ ├── isAttached.js │ │ │ │ │ │ ├── DOMEval.js │ │ │ │ │ │ ├── access.js │ │ │ │ │ │ └── parseHTML.js │ │ │ │ │ ├── attributes.js │ │ │ │ │ ├── effects │ │ │ │ │ │ └── animatedSelector.js │ │ │ │ │ ├── deprecated │ │ │ │ │ │ ├── ajax-event-alias.js │ │ │ │ │ │ └── event.js │ │ │ │ │ ├── selector-sizzle.js │ │ │ │ │ ├── queue │ │ │ │ │ │ └── delay.js │ │ │ │ │ ├── deferred │ │ │ │ │ │ └── exceptionHook.js │ │ │ │ │ ├── exports │ │ │ │ │ │ ├── global.js │ │ │ │ │ │ └── amd.js │ │ │ │ │ ├── jquery.js │ │ │ │ │ ├── attributes │ │ │ │ │ │ └── support.js │ │ │ │ │ └── wrap.js │ │ │ │ ├── bower.json │ │ │ │ ├── LICENSE.txt │ │ │ │ └── external │ │ │ │ │ └── sizzle │ │ │ │ │ └── LICENSE.txt │ │ │ └── jwt-decode │ │ │ │ ├── index.d.ts │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE │ │ │ │ └── package.json │ │ ├── favicon.ico │ │ └── images │ │ │ └── jwtauth.png │ ├── _1-dotnet_run.bat │ ├── _0-restore.bat │ ├── appsettings.Development.json │ ├── libman.json │ ├── Controllers │ │ ├── MyProtectedApiController.cs │ │ ├── MyProtectedEditorsApiController.cs │ │ ├── ApiSettingsController.cs │ │ ├── ChangePasswordController.cs │ │ └── MyProtectedAdminApiController.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.json │ └── ASPNETCore2JwtAuthentication.WebApp.csproj ├── ASPNETCore2JwtAuthentication.ConsoleClient │ ├── _1-dotnet_run.bat │ ├── _0-restore.bat │ ├── User.cs │ ├── Token.cs │ └── ASPNETCore2JwtAuthentication.ConsoleClient.csproj ├── ASPNETCore2JwtAuthentication.IntegrationTests │ ├── _1-dotnet_test.bat │ ├── _0-restore.bat │ ├── Models │ │ ├── MyProtectedApiResponse.cs │ │ └── Token.cs │ ├── Base │ │ ├── TestsHttpClientModel.cs │ │ ├── TestsHttpClient.cs │ │ └── CustomWebApplicationFactory.cs │ └── ASPNETCore2JwtAuthentication.IntegrationTests.csproj ├── ASPNETCore2JwtAuthentication.Postman │ ├── run.strest.bat │ └── JWT.humao.rest-client.rest ├── ASPNETCore2JwtAuthentication.DataLayer │ ├── Migrations │ │ └── .editorconfig │ ├── _02-update_db.cmd │ ├── _01-add_migrations.cmd │ ├── Context │ │ ├── IUnitOfWork.cs │ │ └── ApplicationDbContextFactory.cs │ └── ASPNETCore2JwtAuthentication.DataLayer.csproj ├── ASPNETCore2JwtAuthentication.Common │ ├── ASPNETCore2JwtAuthentication.Common.csproj │ └── ServerPath.cs ├── ASPNETCore2JwtAuthentication.Models │ ├── ASPNETCore2JwtAuthentication.Models.csproj │ ├── Token.cs │ ├── AdminUserSeed.cs │ ├── JwtTokensData.cs │ ├── ApiSettings.cs │ ├── BearerTokensOptions.cs │ └── ChangePasswordViewModel.cs ├── ASPNETCore2JwtAuthentication.DomainClasses │ ├── ASPNETCore2JwtAuthentication.DomainClasses.csproj │ ├── UserRole.cs │ ├── Role.cs │ ├── UserToken.cs │ └── User.cs ├── ASPNETCore2JwtAuthentication.Services │ ├── ISecurityService.cs │ ├── ITokenValidatorService.cs │ ├── CustomRoles.cs │ ├── IAntiForgeryCookieService.cs │ ├── IRolesService.cs │ ├── ITokenFactoryService.cs │ ├── IDbInitializerService.cs │ ├── IUsersService.cs │ ├── SecurityService.cs │ ├── IDeviceDetectionService.cs │ ├── ITokenStoreService.cs │ ├── ASPNETCore2JwtAuthentication.Services.csproj │ └── RolesService.cs └── ASPNETCore2JwtAuthentication.IoCConfig │ └── ASPNETCore2JwtAuthentication.IoCConfig.csproj ├── common └── AssemblyInfo.cs ├── global.json ├── tag-it.bat ├── update-dependencies.bat ├── .github ├── workflows │ ├── codeql │ │ └── codeql-config.yml │ └── build.yml ├── dependabot.yml ├── issue_template.md ├── lock.yml └── FUNDING.yml ├── BannedSymbols.txt └── .config └── dotnet-tools.json /_buid.cmd: -------------------------------------------------------------------------------- 1 | dotnet build 2 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/ng: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: CLSCompliant(false)] 2 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/app_data/git.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/_1-ng-serve.bat: -------------------------------------------------------------------------------- 1 | ng serve -o -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/_1-dotnet_run.bat: -------------------------------------------------------------------------------- 1 | dotnet watch run -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/welcome/welcome.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.ConsoleClient/_1-dotnet_run.bat: -------------------------------------------------------------------------------- 1 | dotnet run 2 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/_1-dotnet_test.bat: -------------------------------------------------------------------------------- 1 | dotnet test 2 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Postman/run.strest.bat: -------------------------------------------------------------------------------- 1 | cmd /k strest JWT.strest.yml 2 | pause -------------------------------------------------------------------------------- /tag-it.bat: -------------------------------------------------------------------------------- 1 | git tag -a 8.0.400 -m "Published V8.0.400" 2 | git push --follow-tags 3 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/login/login.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/component/header/header.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/page-not-found/page-not-found.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /update-dependencies.bat: -------------------------------------------------------------------------------- 1 | dotnet restore 2 | dotnet list package --outdated 3 | dotnet restore 4 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/protected-page/protected-page.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/Migrations/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | generated_code = true 3 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/_2-ng-build-dev.bat: -------------------------------------------------------------------------------- 1 | ng build --base-href /AngularClient/ --watch -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/access-denied/access-denied.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/_2-ng-build-prod.bat: -------------------------------------------------------------------------------- 1 | ng build --base-href /AngularClient/ --prod --watch -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/change-password/change-password.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/call-protected-api/call-protected-api.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/_0-restore.bat: -------------------------------------------------------------------------------- 1 | rmdir /S /Q bin 2 | rmdir /S /Q obj 3 | npm install 4 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.ConsoleClient/_0-restore.bat: -------------------------------------------------------------------------------- 1 | rmdir /S /Q bin 2 | rmdir /S /Q obj 3 | dotnet restore 4 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/_0-restore.bat: -------------------------------------------------------------------------------- 1 | rmdir /S /Q bin 2 | rmdir /S /Q obj 3 | dotnet restore 4 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_clearfix.scss: -------------------------------------------------------------------------------- 1 | .clearfix { 2 | @include clearfix(); 3 | } 4 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/page-not-found/page-not-found.component.html: -------------------------------------------------------------------------------- 1 |

2 | page-not-found works! 3 |

4 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/arr.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return []; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/selector.js: -------------------------------------------------------------------------------- 1 | define( [ "./selector-sizzle" ], function() { 2 | "use strict"; 3 | } ); 4 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/models/auth-token-type.ts: -------------------------------------------------------------------------------- 1 | export enum AuthTokenType { 2 | AccessToken, 3 | RefreshToken 4 | } 5 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/ajax/var/rquery.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return ( /\?/ ); 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/document.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return window.document; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/ajax/var/location.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return window.location; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/ajax/var/nonce.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return { guid: Date.now() }; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/rcustomProp.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | return /^--/; 6 | 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/getProto.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return Object.getPrototypeOf; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/push.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./arr" 3 | ], function( arr ) { 4 | "use strict"; 5 | 6 | return arr.push; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/rcheckableType.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return ( /^(?:checkbox|radio)$/i ); 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/slice.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./arr" 3 | ], function( arr ) { 4 | "use strict"; 5 | 6 | return arr.slice; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/class2type.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | // [[Class]] -> type pairs 5 | return {}; 6 | } ); 7 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/indexOf.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./arr" 3 | ], function( arr ) { 4 | "use strict"; 5 | 6 | return arr.indexOf; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/ASPNETCore2JwtAuthentication/HEAD/src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/cssExpand.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return [ "Top", "Right", "Bottom", "Left" ]; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/models/credentials.ts: -------------------------------------------------------------------------------- 1 | export interface Credentials { 2 | username: string; 3 | password: string; 4 | rememberMe: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/ASPNETCore2JwtAuthentication/HEAD/src/ASPNETCore2JwtAuthentication.AngularClient/src/favicon.ico -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/_0-restore.bat: -------------------------------------------------------------------------------- 1 | rmdir /S /Q bin 2 | rmdir /S /Q obj 3 | dotnet restore 4 | dotnet tool update -g Microsoft.Web.LibraryManager.Cli 5 | libman restore 6 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_text-truncation.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Text truncation 3 | // 4 | 5 | .text-truncate { 6 | @include text-truncate(); 7 | } 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/data/var/dataPriv.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../Data" 3 | ], function( Data ) { 4 | "use strict"; 5 | 6 | return new Data(); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/data/var/dataUser.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../Data" 3 | ], function( Data ) { 4 | "use strict"; 5 | 6 | return new Data(); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/pnum.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/models/auth-guard-permission.ts: -------------------------------------------------------------------------------- 1 | export interface AuthGuardPermission { 2 | permittedRoles?: string[]; 3 | deniedRoles?: string[]; 4 | } 5 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/images/jwtauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/ASPNETCore2JwtAuthentication/HEAD/src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/images/jwtauth.png -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/fnToString.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./hasOwn" 3 | ], function( hasOwn ) { 4 | "use strict"; 5 | 6 | return hasOwn.toString; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/var/rscriptType.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return ( /^$|^module$|\/(?:java|ecma)script/i ); 5 | } ); 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/toString.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./class2type" 3 | ], function( class2type ) { 4 | "use strict"; 5 | 6 | return class2type.toString; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/documentElement.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./document" 3 | ], function( document ) { 4 | "use strict"; 5 | 6 | return document.documentElement; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/hasOwn.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./class2type" 3 | ], function( class2type ) { 4 | "use strict"; 5 | 6 | return class2type.hasOwnProperty; 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/support.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | // All support tests are defined in their respective modules. 5 | return {}; 6 | } ); 7 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/models/auth-user.ts: -------------------------------------------------------------------------------- 1 | export interface AuthUser { 2 | userId: string; 3 | userName: string; 4 | displayName: string; 5 | roles: string[] | null; 6 | } 7 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/ObjectFunctionString.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./fnToString" 3 | ], function( fnToString ) { 4 | "use strict"; 5 | 6 | return fnToString.call( Object ); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Common/ASPNETCore2JwtAuthentication.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/ASPNETCore2JwtAuthentication.Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/rboxStyle.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./cssExpand" 3 | ], function( cssExpand ) { 4 | "use strict"; 5 | 6 | return new RegExp( cssExpand.join( "|" ), "i" ); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/isWindow.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return function isWindow( obj ) { 5 | return obj != null && obj === obj.window; 6 | }; 7 | 8 | } ); 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/whitespace.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | // https://www.w3.org/TR/css3-selectors/#whitespace 6 | return "[\\x20\\t\\r\\n\\f]"; 7 | 8 | } ); 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/change-password/models/change-password.ts: -------------------------------------------------------------------------------- 1 | export interface ChangePassword { 2 | oldPassword: string; 3 | newPassword: string; 4 | confirmPassword: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/rnumnonpx.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../../var/pnum" 3 | ], function( pnum ) { 4 | "use strict"; 5 | 6 | return new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DomainClasses/ASPNETCore2JwtAuthentication.DomainClasses.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/_02-update_db.cmd: -------------------------------------------------------------------------------- 1 | dotnet tool update --global dotnet-ef --version 9.0.0 2 | dotnet tool restore 3 | dotnet build 4 | dotnet ef --startup-project ../ASPNETCore2JwtAuthentication.WebApp/ database update 5 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/Token.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Models; 2 | 3 | public class Token 4 | { 5 | [JsonPropertyName("refreshToken")] 6 | [Required] 7 | public required string RefreshToken { get; set; } 8 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/ISecurityService.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Services; 2 | 3 | public interface ISecurityService 4 | { 5 | string GetSha256Hash(string input); 6 | Guid CreateCryptographicallySecureGuid(); 7 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/traversing/var/rneedsContext.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../../core", 3 | "../../selector" 4 | ], function( jQuery ) { 5 | "use strict"; 6 | 7 | return jQuery.expr.match.needsContext; 8 | } ); 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.ConsoleClient/User.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.ConsoleClient; 2 | 3 | public class User 4 | { 5 | public required string Username { get; set; } 6 | 7 | public required string Password { get; set; } 8 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_vr.scss: -------------------------------------------------------------------------------- 1 | .vr { 2 | display: inline-block; 3 | align-self: stretch; 4 | width: 1px; 5 | min-height: 1em; 6 | background-color: currentcolor; 7 | opacity: $hr-opacity; 8 | } 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/rcssNum.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/pnum" 3 | ], function( pnum ) { 4 | 5 | "use strict"; 6 | 7 | return new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); 8 | 9 | } ); 10 | -------------------------------------------------------------------------------- /.github/workflows/codeql/codeql-config.yml: -------------------------------------------------------------------------------- 1 | name: "Security and Quality" 2 | 3 | queries: 4 | - uses: security-and-quality 5 | 6 | query-filters: 7 | - exclude: 8 | id: cs/useless-if-statement 9 | - exclude: 10 | id: cs/empty-block 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_visually-hidden.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Visually hidden 3 | // 4 | 5 | .visually-hidden, 6 | .visually-hidden-focusable:not(:focus):not(:focus-within) { 7 | @include visually-hidden(); 8 | } 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/event/support.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/support" 3 | ], function( support ) { 4 | 5 | "use strict"; 6 | 7 | support.focusin = "onfocusin" in window; 8 | 9 | return support; 10 | 11 | } ); 12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start clearfix 2 | @mixin clearfix() { 3 | &::after { 4 | display: block; 5 | clear: both; 6 | content: ""; 7 | } 8 | } 9 | // scss-docs-end clearfix 10 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_lists.scss: -------------------------------------------------------------------------------- 1 | // Lists 2 | 3 | // Unstyled keeps list items block level, just removes default browser padding and list-style 4 | @mixin list-unstyled { 5 | padding-left: 0; 6 | list-style: none; 7 | } 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_color-scheme.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start mixin-color-scheme 2 | @mixin color-scheme($name) { 3 | @media (prefers-color-scheme: #{$name}) { 4 | @content; 5 | } 6 | } 7 | // scss-docs-end mixin-color-scheme 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | open-pull-requests-limit: 10 8 | groups: 9 | tests: 10 | patterns: ["*"] 11 | update-types: ["minor", "patch"] 12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_text-truncate.scss: -------------------------------------------------------------------------------- 1 | // Text truncate 2 | // Requires inline-block or block for proper styling 3 | 4 | @mixin text-truncate() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/ITokenValidatorService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.JwtBearer; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public interface ITokenValidatorService 6 | { 7 | Task ValidateAsync(TokenValidatedContext context); 8 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/bootstrap-reboot.scss: -------------------------------------------------------------------------------- 1 | @import "mixins/banner"; 2 | @include bsBanner(Reboot); 3 | 4 | @import "functions"; 5 | @import "variables"; 6 | @import "maps"; 7 | @import "mixins"; 8 | @import "root"; 9 | @import "reboot"; 10 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/AdminUserSeed.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Models; 2 | 3 | public class AdminUserSeed 4 | { 5 | public required string Username { get; set; } 6 | public required string Password { get; set; } 7 | public string? DisplayName { get; set; } 8 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/CustomRoles.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Services; 2 | 3 | public static class CustomRoles 4 | { 5 | public const string Admin = nameof(Admin); 6 | public const string User = nameof(User); 7 | public const string Editor = nameof(Editor); 8 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_resize.scss: -------------------------------------------------------------------------------- 1 | // Resize anything 2 | 3 | @mixin resizable($direction) { 4 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` 5 | resize: $direction; // Options: horizontal, vertical, both 6 | } 7 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/rtrimCSS.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./whitespace" 3 | ], function( whitespace ) { 4 | 5 | "use strict"; 6 | 7 | return new RegExp( 8 | "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", 9 | "g" 10 | ); 11 | 12 | } ); 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/rnothtmlwhite.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | // Only count HTML whitespace 5 | // Other whitespace should count in values 6 | // https://infra.spec.whatwg.org/#ascii-whitespace 7 | return ( /[^\x20\t\r\n\f]+/g ); 8 | } ); 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/nodeName.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | function nodeName( elem, name ) { 6 | 7 | return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); 8 | 9 | } 10 | 11 | return nodeName; 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/readyException.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | jQuery.readyException = function( error ) { 8 | window.setTimeout( function() { 9 | throw error; 10 | } ); 11 | }; 12 | 13 | } ); 14 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ] 14 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/IAntiForgeryCookieService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public interface IAntiForgeryCookieService 6 | { 7 | void RegenerateAntiForgeryCookies(IEnumerable claims); 8 | void DeleteAntiForgeryCookies(); 9 | } -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | # Summary of the issue 2 | 3 | 4 | 5 | ## Environment 6 | 7 | ``` 8 | .NET Core version: 9 | ``` 10 | 11 | ## Example code/Steps to reproduce: 12 | 13 | ``` 14 | paste your core code 15 | ``` 16 | 17 | ## Output: 18 | 19 | ``` 20 | Exception message: 21 | Full Stack trace: 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/Models/MyProtectedApiResponse.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.IntegrationTests.Models; 2 | 3 | public class MyProtectedApiResponse 4 | { 5 | public int Id { set; get; } 6 | 7 | public string? Title { set; get; } 8 | 9 | public string? Username { set; get; } 10 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/var/rsingleTag.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | // rsingleTag matches a string consisting of a single HTML element with no attributes 5 | // and captures the element's name 6 | return ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); 7 | } ); 8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/forms/_form-text.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Form text 3 | // 4 | 5 | .form-text { 6 | margin-top: $form-text-margin-top; 7 | @include font-size($form-text-font-size); 8 | font-style: $form-text-font-style; 9 | font-weight: $form-text-font-weight; 10 | color: $form-text-color; 11 | } 12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/attributes.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./core", 3 | "./attributes/attr", 4 | "./attributes/prop", 5 | "./attributes/classes", 6 | "./attributes/val" 7 | ], function( jQuery ) { 8 | 9 | "use strict"; 10 | 11 | // Return jQuery for attributes-only inclusion 12 | return jQuery; 13 | } ); 14 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | daysUntilLock: 90 2 | 3 | skipCreatedBefore: false 4 | 5 | exemptLabels: [] 6 | 7 | lockLabel: false 8 | 9 | lockComment: > 10 | This thread has been automatically locked since there has not been 11 | any recent activity after it was closed. Please open a new issue for 12 | related problems. 13 | 14 | setLockReason: true 15 | -------------------------------------------------------------------------------- /BannedSymbols.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md 2 | P:System.DateTime.Now;Use System.DateTime.UtcNow instead 3 | P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead 4 | P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead 5 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DomainClasses/UserRole.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | public class UserRole 4 | { 5 | public int UserId { get; set; } 6 | public int RoleId { get; set; } 7 | 8 | public virtual User User { get; set; } = default!; 9 | public virtual Role Role { get; set; } = default!; 10 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DomainClasses/Role.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | public class Role 4 | { 5 | public Role() => UserRoles = []; 6 | 7 | public int Id { get; set; } 8 | 9 | public required string Name { get; set; } = default!; 10 | 11 | public virtual ICollection UserRoles { get; set; } 12 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/Models/Token.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.IntegrationTests.Models; 2 | 3 | public class Token 4 | { 5 | [JsonPropertyName(name: "access_token")] 6 | public string? AccessToken { get; set; } 7 | 8 | [JsonPropertyName(name: "refresh_token")] 9 | public string? RefreshToken { get; set; } 10 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | @import "forms/labels"; 2 | @import "forms/form-text"; 3 | @import "forms/form-control"; 4 | @import "forms/form-select"; 5 | @import "forms/form-check"; 6 | @import "forms/form-range"; 7 | @import "forms/floating-labels"; 8 | @import "forms/input-group"; 9 | @import "forms/validation"; 10 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "9.0.0", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | }, 11 | "microsoft.web.librarymanager.cli": { 12 | "version": "2.1.175", 13 | "commands": [ 14 | "libman" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/traversing/var/siblings.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | return function( n, elem ) { 6 | var matched = []; 7 | 8 | for ( ; n; n = n.nextSibling ) { 9 | if ( n.nodeType === 1 && n !== elem ) { 10 | matched.push( n ); 11 | } 12 | } 13 | 14 | return matched; 15 | }; 16 | 17 | } ); 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_stretched-link.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Stretched link 3 | // 4 | 5 | .stretched-link { 6 | &::#{$stretched-link-pseudo-element} { 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | z-index: $stretched-link-z-index; 13 | content: ""; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_banner.scss: -------------------------------------------------------------------------------- 1 | @mixin bsBanner($file) { 2 | /*! 3 | * Bootstrap #{$file} v5.2.3 (https://getbootstrap.com/) 4 | * Copyright 2011-2022 The Bootstrap Authors 5 | * Copyright 2011-2022 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 7 | */ 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.ConsoleClient/Token.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace ASPNETCore2JwtAuthentication.ConsoleClient; 4 | 5 | public class Token 6 | { 7 | [JsonProperty(propertyName: "access_token")] 8 | public string? AccessToken { get; set; } 9 | 10 | [JsonProperty(propertyName: "refresh_token")] 11 | public string? RefreshToken { get; set; } 12 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/protected-page/protected-page.component.html: -------------------------------------------------------------------------------- 1 |

2 | Decoded Access Token 3 |

4 | 5 |
6 | {{accessTokenExpirationDate}} 7 |
8 | 9 |
10 |
{{decodedAccessToken | json}}
11 |
12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_stacks.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start stacks 2 | .hstack { 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | align-self: stretch; 7 | } 8 | 9 | .vstack { 10 | display: flex; 11 | flex: 1 1 auto; 12 | flex-direction: column; 13 | align-self: stretch; 14 | } 15 | // scss-docs-end stacks 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/effects/animatedSelector.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../selector", 4 | "../effects" 5 | ], function( jQuery ) { 6 | 7 | "use strict"; 8 | 9 | jQuery.expr.pseudos.animated = function( elem ) { 10 | return jQuery.grep( jQuery.timers, function( fn ) { 11 | return elem === fn.elem; 12 | } ).length; 13 | }; 14 | 15 | } ); 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/IRolesService.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public interface IRolesService 6 | { 7 | Task> FindUserRolesAsync(int userId); 8 | Task IsUserInRoleAsync(int userId, string roleName); 9 | Task> FindUsersInRoleAsync(string roleName); 10 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/ITokenFactoryService.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.DomainClasses; 2 | using ASPNETCore2JwtAuthentication.Models; 3 | 4 | namespace ASPNETCore2JwtAuthentication.Services; 5 | 6 | public interface ITokenFactoryService 7 | { 8 | Task CreateJwtTokensAsync(User user); 9 | string? GetRefreshTokenSerial(string refreshTokenValue); 10 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_helpers.scss: -------------------------------------------------------------------------------- 1 | @import "helpers/clearfix"; 2 | @import "helpers/color-bg"; 3 | @import "helpers/colored-links"; 4 | @import "helpers/ratio"; 5 | @import "helpers/position"; 6 | @import "helpers/stacks"; 7 | @import "helpers/visually-hidden"; 8 | @import "helpers/stretched-link"; 9 | @import "helpers/text-truncation"; 10 | @import "helpers/vr"; 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/var/rtagName.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | // rtagName captures the name from the first start tag in a string of HTML 5 | // https://html.spec.whatwg.org/multipage/syntax.html#tag-open-state 6 | // https://html.spec.whatwg.org/multipage/syntax.html#tag-name-state 7 | return ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); 8 | } ); 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/welcome/welcome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-welcome", 5 | templateUrl: "./welcome.component.html", 6 | styleUrls: ["./welcome.component.css"] 7 | }) 8 | export class WelcomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | .top15 { 3 | margin-top: 1.5rem; 4 | margin-right: 0; 5 | margin-left: 0; 6 | } 7 | 8 | .highlight { 9 | padding: 1rem; 10 | margin-top: 1rem; 11 | margin-bottom: 1rem; 12 | background-color: #f7f7f9; 13 | -ms-overflow-style: -ms-autohiding-scrollbar; 14 | } 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/JwtTokensData.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Models; 4 | 5 | public class JwtTokensData 6 | { 7 | public required string AccessToken { get; set; } 8 | public required string RefreshToken { get; set; } 9 | public required string RefreshTokenSerial { get; set; } 10 | public required IEnumerable Claims { get; set; } 11 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/_01-add_migrations.cmd: -------------------------------------------------------------------------------- 1 | dotnet tool update --global dotnet-ef --version 9.0.0 2 | dotnet tool restore 3 | dotnet build 4 | For /f "tokens=2-4 delims=/ " %%a in ('date /t') do (set mydate=%%c_%%a_%%b) 5 | For /f "tokens=1-2 delims=/:" %%a in ("%TIME: =0%") do (set mytime=%%a%%b) 6 | dotnet ef --verbose migrations --startup-project ../ASPNETCore2JwtAuthentication.WebApp/ add V%mydate%_%mytime% 7 | pause -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ASPNETCore2JwtAuthentication.AngularClient 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/bootstrap-utilities.scss: -------------------------------------------------------------------------------- 1 | @import "mixins/banner"; 2 | @include bsBanner(Utilities); 3 | 4 | // Configuration 5 | @import "functions"; 6 | @import "variables"; 7 | @import "maps"; 8 | @import "mixins"; 9 | @import "utilities"; 10 | 11 | // Layout & components 12 | @import "root"; 13 | 14 | // Helpers 15 | @import "helpers"; 16 | 17 | // Utilities 18 | @import "utilities/api"; 19 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('aspnetcore2-jwt-authentication.angular-client App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/services/app.config.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from "@angular/core"; 2 | 3 | export let APP_CONFIG = new InjectionToken("app.config"); 4 | 5 | export interface IAppConfig { 6 | apiEndpoint: string; 7 | apiSettingsPath: string; 8 | } 9 | 10 | export const AppConfig: IAppConfig = { 11 | apiEndpoint: "https://localhost:5001/api", 12 | apiSettingsPath: "ApiSettings" 13 | }; 14 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/page-not-found/page-not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "app-page-not-found", 5 | templateUrl: "./page-not-found.component.html", 6 | styleUrls: ["./page-not-found.component.css"] 7 | }) 8 | export class PageNotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/ApiSettings.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Models; 2 | 3 | public class ApiSettings 4 | { 5 | public string? LoginPath { get; set; } 6 | public string? LogoutPath { get; set; } 7 | public string? RefreshTokenPath { get; set; } 8 | public string? AccessTokenObjectKey { get; set; } 9 | public string? RefreshTokenObjectKey { get; set; } 10 | public string? AdminRoleName { get; set; } 11 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.ConsoleClient/ASPNETCore2JwtAuthentication.ConsoleClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | RCS1090 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_backdrop.scss: -------------------------------------------------------------------------------- 1 | // Shared between modals and offcanvases 2 | @mixin overlay-backdrop($zindex, $backdrop-bg, $backdrop-opacity) { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | z-index: $zindex; 7 | width: 100vw; 8 | height: 100vh; 9 | background-color: $backdrop-bg; 10 | 11 | // Fade for backdrop 12 | &.fade { opacity: 0; } 13 | &.show { opacity: $backdrop-opacity; } 14 | } 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/hiddenVisibleSelectors.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../selector" 4 | ], function( jQuery ) { 5 | 6 | "use strict"; 7 | 8 | jQuery.expr.pseudos.hidden = function( elem ) { 9 | return !jQuery.expr.pseudos.visible( elem ); 10 | }; 11 | jQuery.expr.pseudos.visible = function( elem ) { 12 | return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); 13 | }; 14 | 15 | } ); 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from "@angular/core"; 2 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 3 | 4 | import { AppModule } from "./app/app.module"; 5 | import { environment } from "./environments/environment"; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "unpkg", 4 | "libraries": [ 5 | { 6 | "library":"bootstrap@5.2.3", 7 | "destination": "wwwroot/lib/bootstrap" 8 | }, 9 | { 10 | "library":"jquery@3.6.3", 11 | "destination": "wwwroot/lib/jquery" 12 | }, 13 | { 14 | "library": "jwt-decode@3.1.2", 15 | "destination": "wwwroot/lib/jwt-decode" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | # Code files 5 | [*.{cs,csx,vb,vbx,razor,html,htm,js,md,cshtml,xaml,vbhtml,aspx,txt,asax,ashx,asmx,master,config}] 6 | charset = utf-8-bom 7 | 8 | [*] 9 | indent_style = space 10 | indent_size = 2 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts", 15 | "polyfills.ts" 16 | ], 17 | "include": [ 18 | "**/*.spec.ts", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/data/var/acceptData.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | /** 6 | * Determines whether an object can have data 7 | */ 8 | return function( owner ) { 9 | 10 | // Accepts only: 11 | // - Node 12 | // - Node.ELEMENT_NODE 13 | // - Node.DOCUMENT_NODE 14 | // - Object 15 | // - Any 16 | return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); 17 | }; 18 | 19 | } ); 20 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/deprecated/ajax-event-alias.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../ajax", 4 | "../event" 5 | ], function( jQuery ) { 6 | 7 | "use strict"; 8 | 9 | jQuery.each( [ 10 | "ajaxStart", 11 | "ajaxStop", 12 | "ajaxComplete", 13 | "ajaxError", 14 | "ajaxSuccess", 15 | "ajaxSend" 16 | ], function( _i, type ) { 17 | jQuery.fn[ type ] = function( fn ) { 18 | return this.on( type, fn ); 19 | }; 20 | } ); 21 | 22 | } ); 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/stripAndCollapse.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/rnothtmlwhite" 3 | ], function( rnothtmlwhite ) { 4 | "use strict"; 5 | 6 | // Strip and collapse whitespace according to HTML spec 7 | // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace 8 | function stripAndCollapse( value ) { 9 | var tokens = value.match( rnothtmlwhite ) || []; 10 | return tokens.join( " " ); 11 | } 12 | 13 | return stripAndCollapse; 14 | } ); 15 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_pagination.scss: -------------------------------------------------------------------------------- 1 | // Pagination 2 | 3 | // scss-docs-start pagination-mixin 4 | @mixin pagination-size($padding-y, $padding-x, $font-size, $border-radius) { 5 | --#{$prefix}pagination-padding-x: #{$padding-x}; 6 | --#{$prefix}pagination-padding-y: #{$padding-y}; 7 | @include rfs($font-size, --#{$prefix}pagination-font-size); 8 | --#{$prefix}pagination-border-radius: #{$border-radius}; 9 | } 10 | // scss-docs-end pagination-mixin 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/flat.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./arr" 3 | ], function( arr ) { 4 | 5 | "use strict"; 6 | 7 | // Support: IE 9 - 11+, Edge 18+, Android Browser 4.0 - 4.3 only, iOS 7 - 11 only, Safari 11 only, 8 | // Firefox <= 61 only 9 | // Provide fallback for browsers without Array#flat. 10 | return arr.flat ? function( array ) { 11 | return arr.flat.call( array ); 12 | } : function( array ) { 13 | return arr.concat.apply( [], array ); 14 | }; 15 | 16 | } ); 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v3 14 | with: 15 | dotnet-version: 9.0.x 16 | - name: Build ASPNETCore2JwtAuthentication 17 | run: dotnet build ./src/ASPNETCore2JwtAuthentication.WebApp/ASPNETCore2JwtAuthentication.WebApp.csproj --configuration Release -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/IDbInitializerService.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Services; 2 | 3 | public interface IDbInitializerService 4 | { 5 | /// 6 | /// Applies any pending migrations for the context to the database. 7 | /// Will create the database if it does not already exist. 8 | /// 9 | void Initialize(); 10 | 11 | /// 12 | /// Adds some default values to the Db 13 | /// 14 | void SeedData(); 15 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_alert.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start alert-variant-mixin 2 | @mixin alert-variant($background, $border, $color) { 3 | --#{$prefix}alert-color: #{$color}; 4 | --#{$prefix}alert-bg: #{$background}; 5 | --#{$prefix}alert-border-color: #{$border}; 6 | 7 | @if $enable-gradients { 8 | background-image: var(--#{$prefix}gradient); 9 | } 10 | 11 | .alert-link { 12 | color: shade-color($color, 20%); 13 | } 14 | } 15 | // scss-docs-end alert-variant-mixin 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_container.scss: -------------------------------------------------------------------------------- 1 | // Container mixins 2 | 3 | @mixin make-container($gutter: $container-padding-x) { 4 | --#{$prefix}gutter-x: #{$gutter}; 5 | --#{$prefix}gutter-y: 0; 6 | width: 100%; 7 | padding-right: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 8 | padding-left: calc(var(--#{$prefix}gutter-x) * .5); // stylelint-disable-line function-disallowed-list 9 | margin-right: auto; 10 | margin-left: auto; 11 | } 12 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/toType.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/class2type", 3 | "../var/toString" 4 | ], function( class2type, toString ) { 5 | 6 | "use strict"; 7 | 8 | function toType( obj ) { 9 | if ( obj == null ) { 10 | return obj + ""; 11 | } 12 | 13 | // Support: Android <=2.3 only (functionish RegExp) 14 | return typeof obj === "object" || typeof obj === "function" ? 15 | class2type[ toString.call( obj ) ] || "object" : 16 | typeof obj; 17 | } 18 | 19 | return toType; 20 | } ); 21 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_image.scss: -------------------------------------------------------------------------------- 1 | // Image Mixins 2 | // - Responsive image 3 | // - Retina image 4 | 5 | 6 | // Responsive image 7 | // 8 | // Keep images from scaling beyond the width of their parents. 9 | 10 | @mixin img-fluid { 11 | // Part 1: Set a maximum relative to the parent 12 | max-width: 100%; 13 | // Part 2: Override the height to auto, otherwise images will be stretched 14 | // when setting a width and height attribute on the img element. 15 | height: auto; 16 | } 17 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/traversing/var/dir.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../../core" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | return function( elem, dir, until ) { 8 | var matched = [], 9 | truncate = until !== undefined; 10 | 11 | while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { 12 | if ( elem.nodeType === 1 ) { 13 | if ( truncate && jQuery( elem ).is( until ) ) { 14 | break; 15 | } 16 | matched.push( elem ); 17 | } 18 | } 19 | return matched; 20 | }; 21 | 22 | } ); 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/access-denied/access-denied.component.html: -------------------------------------------------------------------------------- 1 |

2 | Access Denied 3 |

4 |

Sorry! You don't have access to this page.

5 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/getStyles.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return function( elem ) { 5 | 6 | // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) 7 | // IE throws on elements created in popups 8 | // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" 9 | var view = elem.ownerDocument.defaultView; 10 | 11 | if ( !view || !view.opener ) { 12 | view = window; 13 | } 14 | 15 | return view.getComputedStyle( elem ); 16 | }; 17 | } ); 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_box-shadow.scss: -------------------------------------------------------------------------------- 1 | @mixin box-shadow($shadow...) { 2 | @if $enable-shadows { 3 | $result: (); 4 | 5 | @each $value in $shadow { 6 | @if $value != null { 7 | $result: append($result, $value, "comma"); 8 | } 9 | @if $value == none and length($shadow) > 1 { 10 | @warn "The keyword 'none' must be used as a single argument."; 11 | } 12 | } 13 | 14 | @if (length($result) > 0) { 15 | box-shadow: $result; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/setGlobalEval.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../data/var/dataPriv" 3 | ], function( dataPriv ) { 4 | 5 | "use strict"; 6 | 7 | // Mark scripts as having already been evaluated 8 | function setGlobalEval( elems, refElements ) { 9 | var i = 0, 10 | l = elems.length; 11 | 12 | for ( ; i < l; i++ ) { 13 | dataPriv.set( 14 | elems[ i ], 15 | "globalEval", 16 | !refElements || dataPriv.get( refElements[ i ], "globalEval" ) 17 | ); 18 | } 19 | } 20 | 21 | return setGlobalEval; 22 | } ); 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/selector-sizzle.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./core", 3 | "../external/sizzle/dist/sizzle" 4 | ], function( jQuery, Sizzle ) { 5 | 6 | "use strict"; 7 | 8 | jQuery.find = Sizzle; 9 | jQuery.expr = Sizzle.selectors; 10 | 11 | // Deprecated 12 | jQuery.expr[ ":" ] = jQuery.expr.pseudos; 13 | jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; 14 | jQuery.text = Sizzle.getText; 15 | jQuery.isXMLDoc = Sizzle.isXML; 16 | jQuery.contains = Sizzle.contains; 17 | jQuery.escapeSelector = Sizzle.escape; 18 | 19 | } ); 20 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/Context/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace ASPNETCore2JwtAuthentication.DataLayer.Context; 4 | 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | DbSet Set() where TEntity : class; 8 | 9 | int SaveChanges(bool acceptAllChangesOnSuccess); 10 | int SaveChanges(); 11 | Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new()); 12 | Task SaveChangesAsync(CancellationToken cancellationToken = new()); 13 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/Base/TestsHttpClientModel.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.Models; 2 | using Microsoft.AspNetCore.Routing; 3 | 4 | namespace ASPNETCore2JwtAuthentication.IntegrationTests.Base; 5 | 6 | public class TestsHttpClientModel 7 | { 8 | public HttpClient HttpClient { set; get; } = default!; 9 | 10 | public IServiceProvider ServiceProvider { set; get; } = default!; 11 | 12 | public AdminUserSeed AdminUserSeed { set; get; } = default!; 13 | 14 | public LinkGenerator LinkGenerator { set; get; } = default!; 15 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/BearerTokensOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Models; 2 | 3 | public class BearerTokensOptions 4 | { 5 | public required string Key { set; get; } 6 | public required string Issuer { set; get; } 7 | public required string Audience { set; get; } 8 | public int AccessTokenExpirationMinutes { set; get; } 9 | public int RefreshTokenExpirationMinutes { set; get; } 10 | public bool AllowMultipleLoginsFromTheSameUser { set; get; } 11 | public bool AllowSignoutAllUserActiveClients { set; get; } 12 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_color-bg.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable function-name-case 2 | 3 | // All-caps `RGBA()` function used because of this Sass bug: https://github.com/sass/node-sass/issues/2251 4 | @each $color, $value in $theme-colors { 5 | $color-rgb: to-rgb($value); 6 | .text-bg-#{$color} { 7 | color: color-contrast($value) if($enable-important-utilities, !important, null); 8 | background-color: RGBA($color-rgb, var(--#{$prefix}bg-opacity, 1)) if($enable-important-utilities, !important, null); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_colored-links.scss: -------------------------------------------------------------------------------- 1 | @each $color, $value in $theme-colors { 2 | .link-#{$color} { 3 | color: $value !important; // stylelint-disable-line declaration-no-important 4 | 5 | @if $link-shade-percentage != 0 { 6 | &:hover, 7 | &:focus { 8 | color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)) !important; // stylelint-disable-line declaration-no-important 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_ratio.scss: -------------------------------------------------------------------------------- 1 | // Credit: Nicolas Gallagher and SUIT CSS. 2 | 3 | .ratio { 4 | position: relative; 5 | width: 100%; 6 | 7 | &::before { 8 | display: block; 9 | padding-top: var(--#{$prefix}aspect-ratio); 10 | content: ""; 11 | } 12 | 13 | > * { 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | width: 100%; 18 | height: 100%; 19 | } 20 | } 21 | 22 | @each $key, $ratio in $aspect-ratios { 23 | .ratio-#{$key} { 24 | --#{$prefix}aspect-ratio: #{$ratio}; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/forms/_validation.scss: -------------------------------------------------------------------------------- 1 | // Form validation 2 | // 3 | // Provide feedback to users when form field values are valid or invalid. Works 4 | // primarily for client-side validation via scoped `:invalid` and `:valid` 5 | // pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for 6 | // server-side validation. 7 | 8 | // scss-docs-start form-validation-states-loop 9 | @each $state, $data in $form-validation-states { 10 | @include form-validation-state($state, $data...); 11 | } 12 | // scss-docs-end form-validation-states-loop 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jwt-decode/index.d.ts: -------------------------------------------------------------------------------- 1 | export class InvalidTokenError extends Error {} 2 | 3 | export interface JwtDecodeOptions { 4 | header?: boolean; 5 | } 6 | 7 | export interface JwtHeader { 8 | type?: string; 9 | alg?: string; 10 | } 11 | 12 | export interface JwtPayload { 13 | iss?: string; 14 | sub?: string; 15 | aud?: string[] | string; 16 | exp?: number; 17 | nbf?: number; 18 | iat?: number; 19 | jti?: string; 20 | } 21 | 22 | export default function jwtDecode( 23 | token: string, 24 | options?: JwtDecodeOptions 25 | ): T; 26 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Models/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Models; 2 | 3 | public class ChangePasswordViewModel 4 | { 5 | [Required] 6 | [DataType(DataType.Password)] 7 | public required string OldPassword { get; set; } 8 | 9 | [Required] 10 | [StringLength(100, MinimumLength = 4)] 11 | [DataType(DataType.Password)] 12 | public required string NewPassword { get; set; } 13 | 14 | [Required] 15 | [DataType(DataType.Password)] 16 | [Compare(nameof(NewPassword))] 17 | public required string ConfirmPassword { get; set; } 18 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_transitions.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | @include transition($transition-fade); 3 | 4 | &:not(.show) { 5 | opacity: 0; 6 | } 7 | } 8 | 9 | // scss-docs-start collapse-classes 10 | .collapse { 11 | &:not(.show) { 12 | display: none; 13 | } 14 | } 15 | 16 | .collapsing { 17 | height: 0; 18 | overflow: hidden; 19 | @include transition($transition-collapse); 20 | 21 | &.collapse-horizontal { 22 | width: 0; 23 | height: auto; 24 | @include transition($transition-collapse-width); 25 | } 26 | } 27 | // scss-docs-end collapse-classes 28 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./models/auth-guard-permission"; 2 | export * from "./models/auth-token-type"; 3 | export * from "./models/auth-user"; 4 | export * from "./models/credentials"; 5 | 6 | export * from "./services/api-config.service"; 7 | export * from "./services/app.config"; 8 | export * from "./services/auth.guard"; 9 | export * from "./services/auth.service"; 10 | export * from "./services/browser-storage.service"; 11 | export * from "./services/refresh-token.service"; 12 | export * from "./services/token-store.service"; 13 | export * from "./services/utils.service"; 14 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/IUsersService.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public interface IUsersService 6 | { 7 | Task GetSerialNumberAsync(int userId); 8 | Task FindUserAsync(string username, string password); 9 | ValueTask FindUserAsync(int userId); 10 | Task UpdateUserLastActivityDateAsync(int userId); 11 | ValueTask GetCurrentUserAsync(); 12 | int GetCurrentUserId(); 13 | Task<(bool Succeeded, string Error)> ChangePasswordAsync(User user, string currentPassword, string newPassword); 14 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_reset-text.scss: -------------------------------------------------------------------------------- 1 | @mixin reset-text { 2 | font-family: $font-family-base; 3 | // We deliberately do NOT reset font-size or overflow-wrap / word-wrap. 4 | font-style: normal; 5 | font-weight: $font-weight-normal; 6 | line-height: $line-height-base; 7 | text-align: left; // Fallback for where `start` is not supported 8 | text-align: start; 9 | text-decoration: none; 10 | text-shadow: none; 11 | text-transform: none; 12 | letter-spacing: normal; 13 | word-break: normal; 14 | white-space: normal; 15 | word-spacing: normal; 16 | line-break: auto; 17 | } 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DomainClasses/UserToken.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | public class UserToken 4 | { 5 | public int Id { get; set; } 6 | 7 | public string? AccessTokenHash { get; set; } 8 | 9 | public DateTimeOffset AccessTokenExpiresDateTime { get; set; } 10 | 11 | public required string RefreshTokenIdHash { get; set; } = default!; 12 | 13 | public string? RefreshTokenIdHashSource { get; set; } 14 | 15 | public DateTimeOffset RefreshTokenExpiresDateTime { get; set; } 16 | 17 | public int UserId { get; set; } 18 | public virtual User User { get; set; } = default!; 19 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Controllers/MyProtectedApiController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Cors; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace ASPNETCore2JwtAuthentication.WebApp.Controllers; 6 | 7 | [Route("api/[controller]"), EnableCors("CorsPolicy"), Authorize] 8 | public class MyProtectedApiController : Controller 9 | { 10 | [HttpGet] 11 | public IActionResult Get() 12 | { 13 | return Ok(new 14 | { 15 | Id = 1, 16 | Title = "Hello from My Protected Controller! [Authorize]", 17 | Username = User.Identity?.Name 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/swap.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | // A method for quickly swapping in/out CSS properties to get correct calculations. 6 | return function( elem, options, callback ) { 7 | var ret, name, 8 | old = {}; 9 | 10 | // Remember the old values, and insert the new ones 11 | for ( name in options ) { 12 | old[ name ] = elem.style[ name ]; 13 | elem.style[ name ] = options[ name ]; 14 | } 15 | 16 | ret = callback.call( elem ); 17 | 18 | // Revert the old values 19 | for ( name in options ) { 20 | elem.style[ name ] = old[ name ]; 21 | } 22 | 23 | return ret; 24 | }; 25 | 26 | } ); 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | 4 | import { PageNotFoundComponent } from "./page-not-found/page-not-found.component"; 5 | import { WelcomeComponent } from "./welcome/welcome.component"; 6 | 7 | const routes: Routes = [ 8 | { path: "welcome", component: WelcomeComponent }, 9 | { path: "", redirectTo: "welcome", pathMatch: "full" }, 10 | { path: "**", component: PageNotFoundComponent } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AppRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_list-group.scss: -------------------------------------------------------------------------------- 1 | // List Groups 2 | 3 | // scss-docs-start list-group-mixin 4 | @mixin list-group-item-variant($state, $background, $color) { 5 | .list-group-item-#{$state} { 6 | color: $color; 7 | background-color: $background; 8 | 9 | &.list-group-item-action { 10 | &:hover, 11 | &:focus { 12 | color: $color; 13 | background-color: shade-color($background, 10%); 14 | } 15 | 16 | &.active { 17 | color: $white; 18 | background-color: $color; 19 | border-color: $color; 20 | } 21 | } 22 | } 23 | } 24 | // scss-docs-end list-group-mixin 25 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/queue/delay.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../queue", 4 | "../effects" // Delay is optional because of this dependency 5 | ], function( jQuery ) { 6 | 7 | "use strict"; 8 | 9 | // Based off of the plugin by Clint Helfers, with permission. 10 | jQuery.fn.delay = function( time, type ) { 11 | time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 12 | type = type || "fx"; 13 | 14 | return this.queue( type, function( next, hooks ) { 15 | var timeout = window.setTimeout( next, time ); 16 | hooks.stop = function() { 17 | window.clearTimeout( timeout ); 18 | }; 19 | } ); 20 | }; 21 | 22 | return jQuery.fn.delay; 23 | } ); 24 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/addGetHookIf.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | 3 | "use strict"; 4 | 5 | function addGetHookIf( conditionFn, hookFn ) { 6 | 7 | // Define the hook, we'll check on the first run if it's really needed. 8 | return { 9 | get: function() { 10 | if ( conditionFn() ) { 11 | 12 | // Hook not needed (or it's not possible to use it due 13 | // to missing dependency), remove it. 14 | delete this.get; 15 | return; 16 | } 17 | 18 | // Hook needed; redefine it so that the support test is not executed again. 19 | return ( this.get = hookFn ).apply( this, arguments ); 20 | } 21 | }; 22 | } 23 | 24 | return addGetHookIf; 25 | 26 | } ); 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/SecurityService.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public class SecurityService : ISecurityService 6 | { 7 | private readonly RandomNumberGenerator _rand = RandomNumberGenerator.Create(); 8 | 9 | public string GetSha256Hash(string input) 10 | { 11 | var byteValue = Encoding.UTF8.GetBytes(input); 12 | var byteHash = SHA256.HashData(byteValue); 13 | return Convert.ToBase64String(byteHash); 14 | } 15 | 16 | public Guid CreateCryptographicallySecureGuid() 17 | { 18 | var bytes = new byte[16]; 19 | _rand.GetBytes(bytes); 20 | return new Guid(bytes); 21 | } 22 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/camelCase.js: -------------------------------------------------------------------------------- 1 | define( [], function() { 2 | 3 | "use strict"; 4 | 5 | // Matches dashed string for camelizing 6 | var rmsPrefix = /^-ms-/, 7 | rdashAlpha = /-([a-z])/g; 8 | 9 | // Used by camelCase as callback to replace() 10 | function fcamelCase( _all, letter ) { 11 | return letter.toUpperCase(); 12 | } 13 | 14 | // Convert dashed to camelCase; used by the css and data modules 15 | // Support: IE <=9 - 11, Edge 12 - 15 16 | // Microsoft forgot to hump their vendor prefix (trac-9572) 17 | function camelCase( string ) { 18 | return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); 19 | } 20 | 21 | return camelCase; 22 | 23 | } ); 24 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_deprecate.scss: -------------------------------------------------------------------------------- 1 | // Deprecate mixin 2 | // 3 | // This mixin can be used to deprecate mixins or functions. 4 | // `$enable-deprecation-messages` is a global variable, `$ignore-warning` is a variable that can be passed to 5 | // some deprecated mixins to suppress the warning (for example if the mixin is still be used in the current version of Bootstrap) 6 | @mixin deprecate($name, $deprecate-version, $remove-version, $ignore-warning: false) { 7 | @if ($enable-deprecation-messages != false and $ignore-warning != true) { 8 | @warn "#{$name} has been deprecated as of #{$deprecate-version}. It will be removed entirely in #{$remove-version}."; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from "@angular/common"; 2 | import { NgModule } from "@angular/core"; 3 | 4 | import { SharedModule } from "../shared/shared.module"; 5 | import { CallProtectedApiComponent } from "./call-protected-api/call-protected-api.component"; 6 | import { DashboardRoutingModule } from "./dashboard-routing.module"; 7 | import { ProtectedPageComponent } from "./protected-page/protected-page.component"; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | SharedModule, 13 | DashboardRoutingModule 14 | ], 15 | declarations: [ProtectedPageComponent, CallProtectedApiComponent] 16 | }) 17 | export class DashboardModule { } 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/IDeviceDetectionService.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace ASPNETCore2JwtAuthentication.Services; 5 | 6 | public interface IDeviceDetectionService 7 | { 8 | string GetDeviceDetails(HttpContext? context); 9 | string GetCurrentRequestDeviceDetails(); 10 | 11 | string GetDeviceDetailsHash(HttpContext? context); 12 | string GetCurrentRequestDeviceDetailsHash(); 13 | 14 | string? GetUserTokenDeviceDetailsHash(ClaimsIdentity? claimsIdentity); 15 | string? GetCurrentUserTokenDeviceDetailsHash(); 16 | 17 | bool HasUserTokenValidDeviceDetails(ClaimsIdentity? claimsIdentity); 18 | bool HasCurrentUserTokenValidDeviceDetails(); 19 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, HostListener} from "@angular/core"; 2 | import { RefreshTokenService } from "app/core"; 3 | 4 | @Component({ 5 | selector: "app-root", 6 | templateUrl: "./app.component.html", 7 | styleUrls: ["./app.component.css"] 8 | }) 9 | export class AppComponent { 10 | constructor(private refreshTokenService: RefreshTokenService) {} 11 | 12 | @HostListener("window:unload", ["$event"]) 13 | unloadHandler() { 14 | // Invalidate current tab as active RefreshToken timer 15 | this.refreshTokenService.invalidateCurrentTabId(); 16 | } 17 | 18 | @HostListener("window:beforeunload", ["$event"]) 19 | beforeUnloadHander() { 20 | // ... 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.coffeete.ir/vnasiri', 'https://www.buymeacoffee.com/vahidn'] 13 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/support.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/document", 3 | "../var/support" 4 | ], function( document, support ) { 5 | 6 | "use strict"; 7 | 8 | // Support: Safari 8 only 9 | // In Safari 8 documents created via document.implementation.createHTMLDocument 10 | // collapse sibling forms: the second one becomes a child of the first one. 11 | // Because of that, this security measure has to be disabled in Safari 8. 12 | // https://bugs.webkit.org/show_bug.cgi?id=137337 13 | support.createHTMLDocument = ( function() { 14 | var body = document.implementation.createHTMLDocument( "" ).body; 15 | body.innerHTML = "
"; 16 | return body.childNodes.length === 2; 17 | } )(); 18 | 19 | return support; 20 | } ); 21 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_grid.scss: -------------------------------------------------------------------------------- 1 | // Row 2 | // 3 | // Rows contain your columns. 4 | 5 | @if $enable-grid-classes { 6 | .row { 7 | @include make-row(); 8 | 9 | > * { 10 | @include make-col-ready(); 11 | } 12 | } 13 | } 14 | 15 | @if $enable-cssgrid { 16 | .grid { 17 | display: grid; 18 | grid-template-rows: repeat(var(--#{$prefix}rows, 1), 1fr); 19 | grid-template-columns: repeat(var(--#{$prefix}columns, #{$grid-columns}), 1fr); 20 | gap: var(--#{$prefix}gap, #{$grid-gutter-width}); 21 | 22 | @include make-cssgrid(); 23 | } 24 | } 25 | 26 | 27 | // Columns 28 | // 29 | // Common styles for small and large grid columns 30 | 31 | @if $enable-grid-classes { 32 | @include make-grid-columns(); 33 | } 34 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Controllers/MyProtectedEditorsApiController.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.Services; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Cors; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace ASPNETCore2JwtAuthentication.WebApp.Controllers; 7 | 8 | [Route("api/[controller]"), EnableCors("CorsPolicy"), Authorize(Policy = CustomRoles.Editor)] 9 | public class MyProtectedEditorsApiController : Controller 10 | { 11 | [HttpGet] 12 | public IActionResult Get() 13 | { 14 | return Ok(new 15 | { 16 | Id = 1, 17 | Title = "Hello from My Protected Editors Controller! [Authorize(Policy = CustomRoles.Editor)]", 18 | Username = User.Identity?.Name 19 | }); 20 | } 21 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/deferred/exceptionHook.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../deferred" 4 | ], function( jQuery ) { 5 | 6 | "use strict"; 7 | 8 | // These usually indicate a programmer mistake during development, 9 | // warn about them ASAP rather than swallowing them by default. 10 | var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; 11 | 12 | jQuery.Deferred.exceptionHook = function( error, stack ) { 13 | 14 | // Support: IE 8 - 9 only 15 | // Console exists when dev tools are open, which can happen at any time 16 | if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { 17 | window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); 18 | } 19 | }; 20 | 21 | } ); 22 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/var/isFunction.js: -------------------------------------------------------------------------------- 1 | define( function() { 2 | "use strict"; 3 | 4 | return function isFunction( obj ) { 5 | 6 | // Support: Chrome <=57, Firefox <=52 7 | // In some browsers, typeof returns "function" for HTML elements 8 | // (i.e., `typeof document.createElement( "object" ) === "function"`). 9 | // We don't want to classify *any* DOM node as a function. 10 | // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 11 | // Plus for old WebKit, typeof returns "function" for HTML collections 12 | // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) 13 | return typeof obj === "function" && typeof obj.nodeType !== "number" && 14 | typeof obj.item !== "function"; 15 | }; 16 | 17 | } ); 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/services/utils.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | import { BrowserStorageService } from "./browser-storage.service"; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class UtilsService { 9 | 10 | constructor(private browserStorageService: BrowserStorageService) { } 11 | 12 | isEmptyString(value: string): boolean { 13 | return !value || 0 === value.length; 14 | } 15 | 16 | getCurrentTabId(): number { 17 | const tabIdToken = "currentTabId"; 18 | let tabId = this.browserStorageService.getSession(tabIdToken); 19 | if (tabId) { 20 | return tabId; 21 | } 22 | tabId = Math.random(); 23 | this.browserStorageService.setSession(tabIdToken, tabId); 24 | return tabId; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/protected-page/protected-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from "@angular/core"; 2 | import { TokenStoreService } from "@app/core"; 3 | 4 | @Component({ 5 | selector: "app-protected-page", 6 | templateUrl: "./protected-page.component.html", 7 | styleUrls: ["./protected-page.component.css"] 8 | }) 9 | export class ProtectedPageComponent implements OnInit { 10 | 11 | decodedAccessToken: any = {}; 12 | accessTokenExpirationDate: Date | null = null; 13 | 14 | constructor(private tokenStoreService: TokenStoreService) { } 15 | 16 | ngOnInit() { 17 | this.decodedAccessToken = this.tokenStoreService.getDecodedAccessToken(); 18 | this.accessTokenExpirationDate = this.tokenStoreService.getAccessTokenExpirationDateUtc(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Controllers/ApiSettingsController.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.Models; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Cors; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace ASPNETCore2JwtAuthentication.WebApp.Controllers; 8 | 9 | [Route(template: "api/[controller]")] 10 | [EnableCors(policyName: "CorsPolicy")] 11 | public class ApiSettingsController(IOptionsSnapshot apiSettingsConfig) : Controller 12 | { 13 | private readonly IOptionsSnapshot _apiSettingsConfig = 14 | apiSettingsConfig ?? throw new ArgumentNullException(nameof(apiSettingsConfig)); 15 | 16 | [AllowAnonymous] [HttpGet] public IActionResult Get() => Ok(_apiSettingsConfig.Value); // For the Angular Client 17 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3381/", 7 | "sslPort": 44381 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AngularTemplateDrivenFormsLab": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_transition.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable property-disallowed-list 2 | @mixin transition($transition...) { 3 | @if length($transition) == 0 { 4 | $transition: $transition-base; 5 | } 6 | 7 | @if length($transition) > 1 { 8 | @each $value in $transition { 9 | @if $value == null or $value == none { 10 | @warn "The keyword 'none' or 'null' must be used as a single argument."; 11 | } 12 | } 13 | } 14 | 15 | @if $enable-transitions { 16 | @if nth($transition, 1) != null { 17 | transition: $transition; 18 | } 19 | 20 | @if $enable-reduced-motion and nth($transition, 1) != null and nth($transition, 1) != none { 21 | @media (prefers-reduced-motion: reduce) { 22 | transition: none; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/exports/global.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | var 8 | 9 | // Map over jQuery in case of overwrite 10 | _jQuery = window.jQuery, 11 | 12 | // Map over the $ in case of overwrite 13 | _$ = window.$; 14 | 15 | jQuery.noConflict = function( deep ) { 16 | if ( window.$ === jQuery ) { 17 | window.$ = _$; 18 | } 19 | 20 | if ( deep && window.jQuery === jQuery ) { 21 | window.jQuery = _jQuery; 22 | } 23 | 24 | return jQuery; 25 | }; 26 | 27 | // Expose jQuery and $ identifiers, even in AMD 28 | // (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557) 29 | // and CommonJS for browser emulators (trac-13566) 30 | if ( typeof noGlobal === "undefined" ) { 31 | window.jQuery = window.$ = jQuery; 32 | } 33 | 34 | } ); 35 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/access-denied/access-denied.component.ts: -------------------------------------------------------------------------------- 1 | import { Location } from "@angular/common"; 2 | import { Component, OnInit } from "@angular/core"; 3 | import { AuthService } from "@app/core"; 4 | 5 | @Component({ 6 | selector: "app-access-denied", 7 | templateUrl: "./access-denied.component.html", 8 | styleUrls: ["./access-denied.component.css"] 9 | }) 10 | export class AccessDeniedComponent implements OnInit { 11 | 12 | isAuthenticated = false; 13 | 14 | constructor( 15 | private location: Location, 16 | private authService: AuthService 17 | ) { 18 | } 19 | 20 | ngOnInit() { 21 | this.isAuthenticated = this.authService.isAuthUserLoggedIn(); 22 | } 23 | 24 | goBack() { 25 | this.location.back(); // <-- go back to previous location on cancel 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/ITokenStoreService.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | namespace ASPNETCore2JwtAuthentication.Services; 4 | 5 | public interface ITokenStoreService 6 | { 7 | Task AddUserTokenAsync(UserToken userToken); 8 | Task AddUserTokenAsync(User user, string refreshTokenSerial, string accessToken, string? refreshTokenSourceSerial); 9 | Task IsValidTokenAsync(string accessToken, int userId); 10 | Task DeleteExpiredTokensAsync(); 11 | Task FindTokenAsync(string refreshTokenValue); 12 | Task DeleteTokenAsync(string refreshTokenValue); 13 | Task DeleteTokensWithSameRefreshTokenSourceAsync(string? refreshTokenIdHashSource); 14 | Task InvalidateUserTokensAsync(int userId); 15 | Task RevokeUserBearerTokensAsync(string? userIdValue, string refreshTokenValue); 16 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/helpers/_position.scss: -------------------------------------------------------------------------------- 1 | // Shorthand 2 | 3 | .fixed-top { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | left: 0; 8 | z-index: $zindex-fixed; 9 | } 10 | 11 | .fixed-bottom { 12 | position: fixed; 13 | right: 0; 14 | bottom: 0; 15 | left: 0; 16 | z-index: $zindex-fixed; 17 | } 18 | 19 | // Responsive sticky top and bottom 20 | @each $breakpoint in map-keys($grid-breakpoints) { 21 | @include media-breakpoint-up($breakpoint) { 22 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 23 | 24 | .sticky#{$infix}-top { 25 | position: sticky; 26 | top: 0; 27 | z-index: $zindex-sticky; 28 | } 29 | 30 | .sticky#{$infix}-bottom { 31 | position: sticky; 32 | bottom: 0; 33 | z-index: $zindex-sticky; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/getAll.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../core/nodeName" 4 | ], function( jQuery, nodeName ) { 5 | 6 | "use strict"; 7 | 8 | function getAll( context, tag ) { 9 | 10 | // Support: IE <=9 - 11 only 11 | // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) 12 | var ret; 13 | 14 | if ( typeof context.getElementsByTagName !== "undefined" ) { 15 | ret = context.getElementsByTagName( tag || "*" ); 16 | 17 | } else if ( typeof context.querySelectorAll !== "undefined" ) { 18 | ret = context.querySelectorAll( tag || "*" ); 19 | 20 | } else { 21 | ret = []; 22 | } 23 | 24 | if ( tag === undefined || tag && nodeName( context, tag ) ) { 25 | return jQuery.merge( [ context ], ret ); 26 | } 27 | 28 | return ret; 29 | } 30 | 31 | return getAll; 32 | } ); 33 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/jquery.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./core", 3 | "./selector", 4 | "./traversing", 5 | "./callbacks", 6 | "./deferred", 7 | "./deferred/exceptionHook", 8 | "./core/ready", 9 | "./data", 10 | "./queue", 11 | "./queue/delay", 12 | "./attributes", 13 | "./event", 14 | "./event/focusin", 15 | "./manipulation", 16 | "./manipulation/_evalUrl", 17 | "./wrap", 18 | "./css", 19 | "./css/hiddenVisibleSelectors", 20 | "./serialize", 21 | "./ajax", 22 | "./ajax/xhr", 23 | "./ajax/script", 24 | "./ajax/jsonp", 25 | "./ajax/load", 26 | "./core/parseXML", 27 | "./core/parseHTML", 28 | "./effects", 29 | "./effects/animatedSelector", 30 | "./offset", 31 | "./dimensions", 32 | "./deprecated", 33 | "./exports/amd", 34 | "./exports/global" 35 | ], function( jQuery ) { 36 | 37 | "use strict"; 38 | 39 | return jQuery; 40 | 41 | } ); 42 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/_evalUrl.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../ajax" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | jQuery._evalUrl = function( url, options, doc ) { 8 | return jQuery.ajax( { 9 | url: url, 10 | 11 | // Make this explicit, since user can override this through ajaxSetup (trac-11264) 12 | type: "GET", 13 | dataType: "script", 14 | cache: true, 15 | async: false, 16 | global: false, 17 | 18 | // Only evaluate the response if it is successful (gh-4126) 19 | // dataFilter is not invoked for failure responses, so using it instead 20 | // of the default converter is kludgy but it works. 21 | converters: { 22 | "text script": function() {} 23 | }, 24 | dataFilter: function( response ) { 25 | jQuery.globalEval( response, options, doc ); 26 | } 27 | } ); 28 | }; 29 | 30 | return jQuery._evalUrl; 31 | 32 | } ); 33 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/parseXML.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | // Cross-browser xml parsing 8 | jQuery.parseXML = function( data ) { 9 | var xml, parserErrorElem; 10 | if ( !data || typeof data !== "string" ) { 11 | return null; 12 | } 13 | 14 | // Support: IE 9 - 11 only 15 | // IE throws on parseFromString with invalid input. 16 | try { 17 | xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); 18 | } catch ( e ) {} 19 | 20 | parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; 21 | if ( !xml || parserErrorElem ) { 22 | jQuery.error( "Invalid XML: " + ( 23 | parserErrorElem ? 24 | jQuery.map( parserErrorElem.childNodes, function( el ) { 25 | return el.textContent; 26 | } ).join( "\n" ) : 27 | data 28 | ) ); 29 | } 30 | return xml; 31 | }; 32 | 33 | return jQuery.parseXML; 34 | 35 | } ); 36 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/isAttached.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../var/documentElement", 4 | "../selector" // jQuery.contains 5 | ], function( jQuery, documentElement ) { 6 | "use strict"; 7 | 8 | var isAttached = function( elem ) { 9 | return jQuery.contains( elem.ownerDocument, elem ); 10 | }, 11 | composed = { composed: true }; 12 | 13 | // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only 14 | // Check attachment across shadow DOM boundaries when possible (gh-3504) 15 | // Support: iOS 10.0-10.2 only 16 | // Early iOS 10 versions support `attachShadow` but not `getRootNode`, 17 | // leading to errors. We need to check for `getRootNode`. 18 | if ( documentElement.getRootNode ) { 19 | isAttached = function( elem ) { 20 | return jQuery.contains( elem.ownerDocument, elem ) || 21 | elem.getRootNode( composed ) === elem.ownerDocument; 22 | }; 23 | } 24 | 25 | return isAttached; 26 | } ); 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/attributes/support.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/document", 3 | "../var/support" 4 | ], function( document, support ) { 5 | 6 | "use strict"; 7 | 8 | ( function() { 9 | var input = document.createElement( "input" ), 10 | select = document.createElement( "select" ), 11 | opt = select.appendChild( document.createElement( "option" ) ); 12 | 13 | input.type = "checkbox"; 14 | 15 | // Support: Android <=4.3 only 16 | // Default value for a checkbox should be "on" 17 | support.checkOn = input.value !== ""; 18 | 19 | // Support: IE <=11 only 20 | // Must access selectedIndex to make default options select 21 | support.optSelected = opt.selected; 22 | 23 | // Support: IE <=11 only 24 | // An input loses its value after becoming a radio 25 | input = document.createElement( "input" ); 26 | input.value = "t"; 27 | input.type = "radio"; 28 | support.radioValue = input.value === "t"; 29 | } )(); 30 | 31 | return support; 32 | 33 | } ); 34 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IoCConfig/ASPNETCore2JwtAuthentication.IoCConfig.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/wrapMap.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./support" 3 | ], function( support ) { 4 | 5 | "use strict"; 6 | 7 | // We have to close these tags to support XHTML (trac-13200) 8 | var wrapMap = { 9 | 10 | // XHTML parsers do not magically insert elements in the 11 | // same way that tag soup parsers do. So we cannot shorten 12 | // this by omitting or other required elements. 13 | thead: [ 1, "", "
" ], 14 | col: [ 2, "", "
" ], 15 | tr: [ 2, "", "
" ], 16 | td: [ 3, "", "
" ], 17 | 18 | _default: [ 0, "", "" ] 19 | }; 20 | 21 | wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 22 | wrapMap.th = wrapMap.td; 23 | 24 | // Support: IE <=9 only 25 | if ( !support.option ) { 26 | wrapMap.optgroup = wrapMap.option = [ 1, "" ]; 27 | } 28 | 29 | return wrapMap; 30 | } ); 31 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/ASPNETCore2JwtAuthentication.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | RCS1090 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/ASPNETCore2JwtAuthentication.Services.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | RCS1090 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/authentication.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from "@angular/common"; 2 | import { NgModule } from "@angular/core"; 3 | import { FormsModule } from "@angular/forms"; 4 | import { SharedModule } from "@app/shared/shared.module"; 5 | 6 | import { AccessDeniedComponent } from "./access-denied/access-denied.component"; 7 | import { AuthenticationRoutingModule } from "./authentication-routing.module"; 8 | import { ChangePasswordComponent } from "./change-password/change-password.component"; 9 | import { ChangePasswordService } from "./change-password/services/change-password.service"; 10 | import { LoginComponent } from "./login/login.component"; 11 | 12 | @NgModule({ 13 | imports: [ 14 | CommonModule, 15 | FormsModule, 16 | SharedModule, 17 | AuthenticationRoutingModule 18 | ], 19 | declarations: [LoginComponent, AccessDeniedComponent, ChangePasswordComponent], 20 | providers: [ChangePasswordService] 21 | }) 22 | export class AuthenticationModule { } 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DomainClasses/User.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.DomainClasses; 2 | 3 | public class User 4 | { 5 | public User() 6 | { 7 | UserRoles = []; 8 | UserTokens = []; 9 | } 10 | 11 | public int Id { get; set; } 12 | 13 | public required string Username { get; set; } = default!; 14 | 15 | public required string Password { get; set; } = default!; 16 | 17 | public string? DisplayName { get; set; } 18 | 19 | public bool IsActive { get; set; } 20 | 21 | public DateTimeOffset? LastLoggedIn { get; set; } 22 | 23 | /// 24 | /// every time the user changes his Password, 25 | /// or an admin changes his Roles or stat/IsActive, 26 | /// create a new `SerialNumber` GUID and store it in the DB. 27 | /// 28 | public string? SerialNumber { get; set; } 29 | 30 | public virtual ICollection UserRoles { get; set; } 31 | 32 | public virtual ICollection UserTokens { get; set; } 33 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/authentication-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | import { AuthGuard, AuthGuardPermission } from "@app/core"; 4 | 5 | import { AccessDeniedComponent } from "./access-denied/access-denied.component"; 6 | import { ChangePasswordComponent } from "./change-password/change-password.component"; 7 | import { LoginComponent } from "./login/login.component"; 8 | 9 | const routes: Routes = [ 10 | { path: "login", component: LoginComponent }, 11 | { path: "accessDenied", component: AccessDeniedComponent }, 12 | { 13 | path: "changePassword", component: ChangePasswordComponent, 14 | data: { 15 | permission: { 16 | permittedRoles: ["Admin", "User"] 17 | } as AuthGuardPermission 18 | }, 19 | canActivate: [AuthGuard] 20 | } 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [RouterModule.forChild(routes)], 25 | exports: [RouterModule] 26 | }) 27 | export class AuthenticationRoutingModule { } 28 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { BrowserModule } from "@angular/platform-browser"; 3 | 4 | import { AppRoutingModule } from "./app-routing.module"; 5 | import { AppComponent } from "./app.component"; 6 | import { AuthenticationModule } from "./authentication/authentication.module"; 7 | import { CoreModule } from "./core/core.module"; 8 | import { DashboardModule } from "./dashboard/dashboard.module"; 9 | import { PageNotFoundComponent } from "./page-not-found/page-not-found.component"; 10 | import { SharedModule } from "./shared/shared.module"; 11 | import { WelcomeComponent } from "./welcome/welcome.component"; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent, 16 | WelcomeComponent, 17 | PageNotFoundComponent 18 | ], 19 | imports: [ 20 | BrowserModule, 21 | CoreModule, 22 | SharedModule.forRoot(), 23 | AuthenticationModule, 24 | DashboardModule, 25 | AppRoutingModule 26 | ], 27 | providers: [], 28 | bootstrap: [AppComponent] 29 | }) 30 | export class AppModule { } 31 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/change-password/services/change-password.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpErrorResponse, HttpHeaders } from "@angular/common/http"; 2 | import { Inject, Injectable } from "@angular/core"; 3 | import { APP_CONFIG, IAppConfig } from "@app/core"; 4 | import { Observable, throwError } from "rxjs"; 5 | import { catchError, map } from "rxjs/operators"; 6 | 7 | import { ChangePassword } from "./../models/change-password"; 8 | 9 | @Injectable() 10 | export class ChangePasswordService { 11 | 12 | constructor( 13 | private http: HttpClient, 14 | @Inject(APP_CONFIG) private appConfig: IAppConfig) { } 15 | 16 | changePassword(model: ChangePassword): Observable { 17 | const headers = new HttpHeaders({ "Content-Type": "application/json" }); 18 | const url = `${this.appConfig.apiEndpoint}/ChangePassword`; 19 | return this.http 20 | .post(url, model, { headers: headers }) 21 | .pipe( 22 | map(response => response || {}), 23 | catchError((error: HttpErrorResponse) => throwError(error)) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/finalPropName.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/document", 3 | "../core" 4 | ], function( document, jQuery ) { 5 | 6 | "use strict"; 7 | 8 | var cssPrefixes = [ "Webkit", "Moz", "ms" ], 9 | emptyStyle = document.createElement( "div" ).style, 10 | vendorProps = {}; 11 | 12 | // Return a vendor-prefixed property or undefined 13 | function vendorPropName( name ) { 14 | 15 | // Check for vendor prefixed names 16 | var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), 17 | i = cssPrefixes.length; 18 | 19 | while ( i-- ) { 20 | name = cssPrefixes[ i ] + capName; 21 | if ( name in emptyStyle ) { 22 | return name; 23 | } 24 | } 25 | } 26 | 27 | // Return a potentially-mapped jQuery.cssProps or vendor prefixed property 28 | function finalPropName( name ) { 29 | var final = jQuery.cssProps[ name ] || vendorProps[ name ]; 30 | 31 | if ( final ) { 32 | return final; 33 | } 34 | if ( name in emptyStyle ) { 35 | return name; 36 | } 37 | return vendorProps[ name ] = vendorPropName( name ) || name; 38 | } 39 | 40 | return finalPropName; 41 | 42 | } ); 43 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/dashboard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { RouterModule, Routes } from "@angular/router"; 3 | import { AuthGuard, AuthGuardPermission } from "@app/core"; 4 | 5 | import { CallProtectedApiComponent } from "./call-protected-api/call-protected-api.component"; 6 | import { ProtectedPageComponent } from "./protected-page/protected-page.component"; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: "protectedPage", 11 | component: ProtectedPageComponent, 12 | data: { 13 | permission: { 14 | permittedRoles: ["Admin"] 15 | } as AuthGuardPermission 16 | }, 17 | canActivate: [AuthGuard] 18 | }, 19 | { 20 | path: "callProtectedApi", 21 | component: CallProtectedApiComponent, 22 | data: { 23 | permission: { 24 | permittedRoles: ["Admin", "User"] 25 | } as AuthGuardPermission 26 | }, 27 | canActivate: [AuthGuard] 28 | } 29 | ]; 30 | 31 | @NgModule({ 32 | imports: [RouterModule.forChild(routes)], 33 | exports: [RouterModule] 34 | }) 35 | export class DashboardRoutingModule { } 36 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Toggles 2 | // 3 | // Used in conjunction with global variables to enable certain theme features. 4 | 5 | // Vendor 6 | @import "vendor/rfs"; 7 | 8 | // Deprecate 9 | @import "mixins/deprecate"; 10 | 11 | // Helpers 12 | @import "mixins/breakpoints"; 13 | @import "mixins/color-scheme"; 14 | @import "mixins/image"; 15 | @import "mixins/resize"; 16 | @import "mixins/visually-hidden"; 17 | @import "mixins/reset-text"; 18 | @import "mixins/text-truncate"; 19 | 20 | // Utilities 21 | @import "mixins/utilities"; 22 | 23 | // Components 24 | @import "mixins/alert"; 25 | @import "mixins/backdrop"; 26 | @import "mixins/buttons"; 27 | @import "mixins/caret"; 28 | @import "mixins/pagination"; 29 | @import "mixins/lists"; 30 | @import "mixins/list-group"; 31 | @import "mixins/forms"; 32 | @import "mixins/table-variants"; 33 | 34 | // Skins 35 | @import "mixins/border-radius"; 36 | @import "mixins/box-shadow"; 37 | @import "mixins/gradients"; 38 | @import "mixins/transition"; 39 | 40 | // Layout 41 | @import "mixins/clearfix"; 42 | @import "mixins/container"; 43 | @import "mixins/grid"; 44 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_placeholders.scss: -------------------------------------------------------------------------------- 1 | .placeholder { 2 | display: inline-block; 3 | min-height: 1em; 4 | vertical-align: middle; 5 | cursor: wait; 6 | background-color: currentcolor; 7 | opacity: $placeholder-opacity-max; 8 | 9 | &.btn::before { 10 | display: inline-block; 11 | content: ""; 12 | } 13 | } 14 | 15 | // Sizing 16 | .placeholder-xs { 17 | min-height: .6em; 18 | } 19 | 20 | .placeholder-sm { 21 | min-height: .8em; 22 | } 23 | 24 | .placeholder-lg { 25 | min-height: 1.2em; 26 | } 27 | 28 | // Animation 29 | .placeholder-glow { 30 | .placeholder { 31 | animation: placeholder-glow 2s ease-in-out infinite; 32 | } 33 | } 34 | 35 | @keyframes placeholder-glow { 36 | 50% { 37 | opacity: $placeholder-opacity-min; 38 | } 39 | } 40 | 41 | .placeholder-wave { 42 | mask-image: linear-gradient(130deg, $black 55%, rgba(0, 0, 0, (1 - $placeholder-opacity-min)) 75%, $black 95%); 43 | mask-size: 200% 100%; 44 | animation: placeholder-wave 2s linear infinite; 45 | } 46 | 47 | @keyframes placeholder-wave { 48 | 100% { 49 | mask-position: -200% 0%; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/exports/amd.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core" 3 | ], function( jQuery ) { 4 | 5 | "use strict"; 6 | 7 | // Register as a named AMD module, since jQuery can be concatenated with other 8 | // files that may use define, but not via a proper concatenation script that 9 | // understands anonymous AMD modules. A named AMD is safest and most robust 10 | // way to register. Lowercase jquery is used because AMD module names are 11 | // derived from file names, and jQuery is normally delivered in a lowercase 12 | // file name. Do this after creating the global so that if an AMD module wants 13 | // to call noConflict to hide this version of jQuery, it will work. 14 | 15 | // Note that for maximum portability, libraries that are not jQuery should 16 | // declare themselves as anonymous modules, and avoid setting a global if an 17 | // AMD loader is present. jQuery is a special case. For more information, see 18 | // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon 19 | 20 | if ( typeof define === "function" && define.amd ) { 21 | define( "jquery", [], function() { 22 | return jQuery; 23 | } ); 24 | } 25 | 26 | } ); 27 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jwt-decode/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## Version [3.1.2](https://github.com/auth0/jwt-decode/releases/tag/v3.1.2) 4 | 5 | [Full Changelog](https://github.com/auth0/jwt-decode/compare/v3.1.1..v3.1.2) 6 | 7 | - Add a generic as return type so the user can specify what's expected, this will still be `unknown` by default 8 | - Export `JwtHeader`and `JwtPayload` that can be used with the generic return type as-is or extended. 9 | 10 | ## Version [3.1.0](https://github.com/auth0/jwt-decode/releases/tag/v3.1.0) 11 | 12 | [Full Changelog](https://github.com/auth0/jwt-decode/compare/v3.0.0..v3.1.0) 13 | 14 | - Add TypeScript type definition 15 | - Fix CJS default export 16 | 17 | ## Version [3.0.0](https://github.com/auth0/jwt-decode/releases/tag/v3.0.0) 18 | 19 | [Full Changelog](https://github.com/auth0/jwt-decode/compare/v2.0.0..v3.0.0) 20 | 21 | - Include an ESM build for native JavaScript imports 22 | 23 | **Warning: this version has some potentially breaking changes!** 24 | 25 | - If you've previously imported the library as `import * as jwt_decode from 'jwt-decode'`, you'll have to change your import to `import jwt_decode from 'jwt-decode';`. 26 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/README.md: -------------------------------------------------------------------------------- 1 | # ASPNETCore2JwtAuthentication.AngularClient 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AdminUserSeed": { 3 | "Username": "Vahid", 4 | "Password": "1234", 5 | "DisplayName": "وحيد" 6 | }, 7 | "Logging": { 8 | "IncludeScopes": false, 9 | "LogLevel": { 10 | "Default": "Warning" 11 | } 12 | }, 13 | "ConnectionStrings": { 14 | "DefaultConnection": "Data Source=(LocalDB)\\MSSQLLocalDB;Initial Catalog=ASPNETCore70JwtAuthDB;AttachDbFilename=|DataDirectory|\\ASPNETCore70JwtAuthDB.mdf;Integrated Security=True;MultipleActiveResultSets=True;" 15 | }, 16 | "BearerTokens": { 17 | "Key": "This is my shared key, not so secret, secret!", 18 | "Issuer": "https://localhost:5001/", 19 | "Audience": "Any", 20 | "AccessTokenExpirationMinutes": 2, 21 | "RefreshTokenExpirationMinutes": 60, 22 | "AllowMultipleLoginsFromTheSameUser": false, 23 | "AllowSignoutAllUserActiveClients": true 24 | }, 25 | "ApiSettings": { 26 | "LoginPath": "account/login", 27 | "LogoutPath": "account/logout", 28 | "RefreshTokenPath": "account/RefreshToken", 29 | "AccessTokenObjectKey": "access_token", 30 | "RefreshTokenObjectKey": "refresh_token", 31 | "AdminRoleName": "Admin" 32 | } 33 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import "mixins/banner"; 2 | @include bsBanner(""); 3 | 4 | 5 | // scss-docs-start import-stack 6 | // Configuration 7 | @import "functions"; 8 | @import "variables"; 9 | @import "maps"; 10 | @import "mixins"; 11 | @import "utilities"; 12 | 13 | // Layout & components 14 | @import "root"; 15 | @import "reboot"; 16 | @import "type"; 17 | @import "images"; 18 | @import "containers"; 19 | @import "grid"; 20 | @import "tables"; 21 | @import "forms"; 22 | @import "buttons"; 23 | @import "transitions"; 24 | @import "dropdown"; 25 | @import "button-group"; 26 | @import "nav"; 27 | @import "navbar"; 28 | @import "card"; 29 | @import "accordion"; 30 | @import "breadcrumb"; 31 | @import "pagination"; 32 | @import "badge"; 33 | @import "alert"; 34 | @import "progress"; 35 | @import "list-group"; 36 | @import "close"; 37 | @import "toasts"; 38 | @import "modal"; 39 | @import "tooltip"; 40 | @import "popover"; 41 | @import "carousel"; 42 | @import "spinners"; 43 | @import "offcanvas"; 44 | @import "placeholders"; 45 | 46 | // Helpers 47 | @import "helpers"; 48 | 49 | // Utilities 50 | @import "utilities/api"; 51 | // scss-docs-end import-stack 52 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_visually-hidden.scss: -------------------------------------------------------------------------------- 1 | // stylelint-disable declaration-no-important 2 | 3 | // Hide content visually while keeping it accessible to assistive technologies 4 | // 5 | // See: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ 6 | // See: https://kittygiraudel.com/2016/10/13/css-hide-and-seek/ 7 | 8 | @mixin visually-hidden() { 9 | position: absolute !important; 10 | width: 1px !important; 11 | height: 1px !important; 12 | padding: 0 !important; 13 | margin: -1px !important; // Fix for https://github.com/twbs/bootstrap/issues/25686 14 | overflow: hidden !important; 15 | clip: rect(0, 0, 0, 0) !important; 16 | white-space: nowrap !important; 17 | border: 0 !important; 18 | } 19 | 20 | // Use to only display content when it's focused, or one of its child elements is focused 21 | // (i.e. when focus is within the element/container that the class was applied to) 22 | // 23 | // Useful for "Skip to main content" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 24 | 25 | @mixin visually-hidden-focusable() { 26 | &:not(:focus):not(:focus-within) { 27 | @include visually-hidden(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/shared/directives/is-visible-for-auth-user.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, Input, OnDestroy, OnInit } from "@angular/core"; 2 | import { AuthService } from "@app/core"; 3 | import { Subscription } from "rxjs"; 4 | 5 | @Directive({ 6 | selector: "[isVisibleForAuthUser]" 7 | }) 8 | export class IsVisibleForAuthUserDirective implements OnInit, OnDestroy { 9 | 10 | private subscription: Subscription | null = null; 11 | 12 | @Input() isVisibleForRoles: string[] | null = null; 13 | 14 | constructor(private elem: ElementRef, private authService: AuthService) { } 15 | 16 | ngOnDestroy(): void { 17 | if (this.subscription) { 18 | this.subscription.unsubscribe(); 19 | } 20 | } 21 | 22 | ngOnInit(): void { 23 | this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status)); 24 | this.changeVisibility(this.authService.isAuthUserLoggedIn()); 25 | } 26 | 27 | private changeVisibility(status: boolean) { 28 | const isInRoles = !this.isVisibleForRoles ? true : this.authService.isAuthUserInRoles(this.isVisibleForRoles); 29 | this.elem.nativeElement.style.display = isInRoles && status ? "" : "none"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Postman/JWT.humao.rest-client.rest: -------------------------------------------------------------------------------- 1 | @baseUrl = https://localhost:5001/api 2 | 3 | # @name login 4 | POST {{baseUrl}}/account/login HTTP/1.1 5 | Content-Type: application/json 6 | 7 | { 8 | "username": "Vahid", 9 | "password": "1234" 10 | } 11 | 12 | ### 13 | 14 | @accessToken = {{login.response.body.access_token}} 15 | @refreshToken = {{login.response.body.refresh_token}} 16 | 17 | 18 | # @name callMyProtectedApi 19 | GET {{baseUrl}}/MyProtectedApi HTTP/1.1 20 | Authorization: Bearer {{accessToken}} 21 | 22 | ### 23 | 24 | # @name callMyProtectedAdminApi 25 | 26 | GET {{baseUrl}}/MyProtectedAdminApi HTTP/1.1 27 | Authorization: Bearer {{accessToken}} 28 | 29 | ### 30 | 31 | # @name callRefreshToken 32 | POST {{baseUrl}}/account/RefreshToken HTTP/1.1 33 | Content-Type: application/json 34 | 35 | { 36 | "refreshToken": "{{refreshToken}}" 37 | } 38 | 39 | ### 40 | //TODO: 41 | //@accessToken = {{callRefreshToken.response.body.access_token}} 42 | //@refreshToken = {{callRefreshToken.response.body.refresh_token}} 43 | 44 | 45 | # @name logout 46 | GET {{baseUrl}}/account/logout?refreshToken={{refreshToken}} HTTP/1.1 47 | Authorization: Bearer {{accessToken}} 48 | 49 | 50 | ### 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jwt-decode/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Auth0, Inc. (http://auth0.com) 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 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/component/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from "@angular/core"; 2 | import { Subscription } from "rxjs"; 3 | 4 | import { AuthService } from "../../services/auth.service"; 5 | 6 | @Component({ 7 | selector: "app-header", 8 | templateUrl: "./header.component.html", 9 | styleUrls: ["./header.component.css"] 10 | }) 11 | export class HeaderComponent implements OnInit, OnDestroy { 12 | 13 | title = "Angular.Jwt.Core"; 14 | 15 | isLoggedIn = false; 16 | subscription: Subscription | null = null; 17 | displayName = ""; 18 | 19 | constructor(private authService: AuthService) { } 20 | 21 | ngOnInit() { 22 | this.subscription = this.authService.authStatus$.subscribe(status => { 23 | this.isLoggedIn = status; 24 | if (status) { 25 | const authUser = this.authService.getAuthUser(); 26 | this.displayName = authUser ? authUser.displayName : ""; 27 | } 28 | }); 29 | } 30 | 31 | ngOnDestroy() { 32 | // prevent memory leak when component is destroyed 33 | if (this.subscription) { 34 | this.subscription.unsubscribe(); 35 | } 36 | } 37 | 38 | logout() { 39 | this.authService.logout(true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2022 Twitter, Inc. 4 | Copyright (c) 2011-2022 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_table-variants.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start table-variant 2 | @mixin table-variant($state, $background) { 3 | .table-#{$state} { 4 | $color: color-contrast(opaque($body-bg, $background)); 5 | $hover-bg: mix($color, $background, percentage($table-hover-bg-factor)); 6 | $striped-bg: mix($color, $background, percentage($table-striped-bg-factor)); 7 | $active-bg: mix($color, $background, percentage($table-active-bg-factor)); 8 | $table-border-color: mix($color, $background, percentage($table-border-factor)); 9 | 10 | --#{$prefix}table-color: #{$color}; 11 | --#{$prefix}table-bg: #{$background}; 12 | --#{$prefix}table-border-color: #{$table-border-color}; 13 | --#{$prefix}table-striped-bg: #{$striped-bg}; 14 | --#{$prefix}table-striped-color: #{color-contrast($striped-bg)}; 15 | --#{$prefix}table-active-bg: #{$active-bg}; 16 | --#{$prefix}table-active-color: #{color-contrast($active-bg)}; 17 | --#{$prefix}table-hover-bg: #{$hover-bg}; 18 | --#{$prefix}table-hover-color: #{color-contrast($hover-bg)}; 19 | 20 | color: var(--#{$prefix}table-color); 21 | border-color: var(--#{$prefix}table-border-color); 22 | } 23 | } 24 | // scss-docs-end table-variant 25 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/js/src/util/component-functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------------------------------------------- 3 | * Bootstrap (v5.2.3): util/component-functions.js 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | * -------------------------------------------------------------------------- 6 | */ 7 | 8 | import EventHandler from '../dom/event-handler' 9 | import { getElementFromSelector, isDisabled } from './index' 10 | 11 | const enableDismissTrigger = (component, method = 'hide') => { 12 | const clickEvent = `click.dismiss${component.EVENT_KEY}` 13 | const name = component.NAME 14 | 15 | EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { 16 | if (['A', 'AREA'].includes(this.tagName)) { 17 | event.preventDefault() 18 | } 19 | 20 | if (isDisabled(this)) { 21 | return 22 | } 23 | 24 | const target = getElementFromSelector(this) || this.closest(`.${name}`) 25 | const instance = component.getOrCreateInstance(target) 26 | 27 | // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method 28 | instance[method]() 29 | }) 30 | } 31 | 32 | export { 33 | enableDismissTrigger 34 | } 35 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/component/header/header.component.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_badge.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | // 3 | // Requires one of the contextual, color modifier classes for `color` and 4 | // `background-color`. 5 | 6 | .badge { 7 | // scss-docs-start badge-css-vars 8 | --#{$prefix}badge-padding-x: #{$badge-padding-x}; 9 | --#{$prefix}badge-padding-y: #{$badge-padding-y}; 10 | @include rfs($badge-font-size, --#{$prefix}badge-font-size); 11 | --#{$prefix}badge-font-weight: #{$badge-font-weight}; 12 | --#{$prefix}badge-color: #{$badge-color}; 13 | --#{$prefix}badge-border-radius: #{$badge-border-radius}; 14 | // scss-docs-end badge-css-vars 15 | 16 | display: inline-block; 17 | padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x); 18 | @include font-size(var(--#{$prefix}badge-font-size)); 19 | font-weight: var(--#{$prefix}badge-font-weight); 20 | line-height: 1; 21 | color: var(--#{$prefix}badge-color); 22 | text-align: center; 23 | white-space: nowrap; 24 | vertical-align: baseline; 25 | @include border-radius(var(--#{$prefix}badge-border-radius)); 26 | @include gradient-bg(); 27 | 28 | // Empty badges collapse automatically 29 | &:empty { 30 | display: none; 31 | } 32 | } 33 | 34 | // Quick fix for badges in buttons 35 | .btn .badge { 36 | position: relative; 37 | top: -1px; 38 | } 39 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_close.scss: -------------------------------------------------------------------------------- 1 | // Transparent background and border properties included for button version. 2 | // iOS requires the button element instead of an anchor tag. 3 | // If you want the anchor version, it requires `href="#"`. 4 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 5 | 6 | .btn-close { 7 | box-sizing: content-box; 8 | width: $btn-close-width; 9 | height: $btn-close-height; 10 | padding: $btn-close-padding-y $btn-close-padding-x; 11 | color: $btn-close-color; 12 | background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements 13 | border: 0; // for button elements 14 | @include border-radius(); 15 | opacity: $btn-close-opacity; 16 | 17 | // Override 's hover style 18 | &:hover { 19 | color: $btn-close-color; 20 | text-decoration: none; 21 | opacity: $btn-close-hover-opacity; 22 | } 23 | 24 | &:focus { 25 | outline: 0; 26 | box-shadow: $btn-close-focus-shadow; 27 | opacity: $btn-close-focus-opacity; 28 | } 29 | 30 | &:disabled, 31 | &.disabled { 32 | pointer-events: none; 33 | user-select: none; 34 | opacity: $btn-close-disabled-opacity; 35 | } 36 | } 37 | 38 | .btn-close-white { 39 | filter: $btn-close-white-filter; 40 | } 41 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/forms/_labels.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // 4 | 5 | .form-label { 6 | margin-bottom: $form-label-margin-bottom; 7 | @include font-size($form-label-font-size); 8 | font-style: $form-label-font-style; 9 | font-weight: $form-label-font-weight; 10 | color: $form-label-color; 11 | } 12 | 13 | // For use with horizontal and inline forms, when you need the label (or legend) 14 | // text to align with the form controls. 15 | .col-form-label { 16 | padding-top: add($input-padding-y, $input-border-width); 17 | padding-bottom: add($input-padding-y, $input-border-width); 18 | margin-bottom: 0; // Override the `` default 19 | @include font-size(inherit); // Override the `` default 20 | font-style: $form-label-font-style; 21 | font-weight: $form-label-font-weight; 22 | line-height: $input-line-height; 23 | color: $form-label-color; 24 | } 25 | 26 | .col-form-label-lg { 27 | padding-top: add($input-padding-y-lg, $input-border-width); 28 | padding-bottom: add($input-padding-y-lg, $input-border-width); 29 | @include font-size($input-font-size-lg); 30 | } 31 | 32 | .col-form-label-sm { 33 | padding-top: add($input-padding-y-sm, $input-border-width); 34 | padding-bottom: add($input-padding-y-sm, $input-border-width); 35 | @include font-size($input-font-size-sm); 36 | } 37 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/ASPNETCore2JwtAuthentication.WebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/services/xsrf.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpXsrfTokenExtractor } from "@angular/common/http"; 2 | import { Injectable } from "@angular/core"; 3 | import { Observable } from "rxjs"; 4 | 5 | @Injectable() 6 | export class XsrfInterceptor implements HttpInterceptor { // Handles absolute URLs 7 | 8 | constructor(private tokenExtractor: HttpXsrfTokenExtractor) { } 9 | 10 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 11 | /* 12 | const lcUrl = request.url.toLowerCase(); 13 | if (request.method === "GET" || request.method === "HEAD" || 14 | lcUrl.startsWith("http://") || lcUrl.startsWith("https://")) { 15 | console.log("Original HttpXsrfInterceptor skips both non-mutating requests and absolute URLs."); 16 | console.log("Skipped request", { lcUrl: lcUrl, method: request.method }); 17 | } 18 | */ 19 | if (request.method === "POST") { 20 | const headerName = "X-XSRF-TOKEN"; 21 | const token = this.tokenExtractor.getToken(); 22 | if (token && !request.headers.has(headerName)) { 23 | request = request.clone({ 24 | headers: request.headers.set(headerName, token) 25 | }); 26 | } 27 | } 28 | return next.handle(request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/welcome/welcome.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Welcome! 4 |

5 | 7 |
8 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_images.scss: -------------------------------------------------------------------------------- 1 | // Responsive images (ensure images don't scale beyond their parents) 2 | // 3 | // This is purposefully opt-in via an explicit class rather than being the default for all ``s. 4 | // We previously tried the "images are responsive by default" approach in Bootstrap v2, 5 | // and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps) 6 | // which weren't expecting the images within themselves to be involuntarily resized. 7 | // See also https://github.com/twbs/bootstrap/issues/18178 8 | .img-fluid { 9 | @include img-fluid(); 10 | } 11 | 12 | 13 | // Image thumbnails 14 | .img-thumbnail { 15 | padding: $thumbnail-padding; 16 | background-color: $thumbnail-bg; 17 | border: $thumbnail-border-width solid $thumbnail-border-color; 18 | @include border-radius($thumbnail-border-radius); 19 | @include box-shadow($thumbnail-box-shadow); 20 | 21 | // Keep them at most 100% wide 22 | @include img-fluid(); 23 | } 24 | 25 | // 26 | // Figures 27 | // 28 | 29 | .figure { 30 | // Ensures the caption's text aligns with the image. 31 | display: inline-block; 32 | } 33 | 34 | .figure-img { 35 | margin-bottom: $spacer * .5; 36 | line-height: 1; 37 | } 38 | 39 | .figure-caption { 40 | @include font-size($figure-caption-font-size); 41 | color: $figure-caption-color; 42 | } 43 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/DOMEval.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/document" 3 | ], function( document ) { 4 | "use strict"; 5 | 6 | var preservedScriptAttributes = { 7 | type: true, 8 | src: true, 9 | nonce: true, 10 | noModule: true 11 | }; 12 | 13 | function DOMEval( code, node, doc ) { 14 | doc = doc || document; 15 | 16 | var i, val, 17 | script = doc.createElement( "script" ); 18 | 19 | script.text = code; 20 | if ( node ) { 21 | for ( i in preservedScriptAttributes ) { 22 | 23 | // Support: Firefox 64+, Edge 18+ 24 | // Some browsers don't support the "nonce" property on scripts. 25 | // On the other hand, just using `getAttribute` is not enough as 26 | // the `nonce` attribute is reset to an empty string whenever it 27 | // becomes browsing-context connected. 28 | // See https://github.com/whatwg/html/issues/2369 29 | // See https://html.spec.whatwg.org/#nonce-attributes 30 | // The `node.getAttribute` check was added for the sake of 31 | // `jQuery.globalEval` so that it can fake a nonce-containing node 32 | // via an object. 33 | val = node[ i ] || node.getAttribute && node.getAttribute( i ); 34 | if ( val ) { 35 | script.setAttribute( i, val ); 36 | } 37 | } 38 | } 39 | doc.head.appendChild( script ).parentNode.removeChild( script ); 40 | } 41 | 42 | return DOMEval; 43 | } ); 44 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/deprecated/event.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | 4 | "../event", 5 | "../event/trigger" 6 | ], function( jQuery ) { 7 | 8 | "use strict"; 9 | 10 | jQuery.fn.extend( { 11 | 12 | bind: function( types, data, fn ) { 13 | return this.on( types, null, data, fn ); 14 | }, 15 | unbind: function( types, fn ) { 16 | return this.off( types, null, fn ); 17 | }, 18 | 19 | delegate: function( selector, types, data, fn ) { 20 | return this.on( types, selector, data, fn ); 21 | }, 22 | undelegate: function( selector, types, fn ) { 23 | 24 | // ( namespace ) or ( selector, types [, fn] ) 25 | return arguments.length === 1 ? 26 | this.off( selector, "**" ) : 27 | this.off( types, selector || "**", fn ); 28 | }, 29 | 30 | hover: function( fnOver, fnOut ) { 31 | return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); 32 | } 33 | } ); 34 | 35 | jQuery.each( 36 | ( "blur focus focusin focusout resize scroll click dblclick " + 37 | "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 38 | "change select submit keydown keypress keyup contextmenu" ).split( " " ), 39 | function( _i, name ) { 40 | 41 | // Handle event binding 42 | jQuery.fn[ name ] = function( data, fn ) { 43 | return arguments.length > 0 ? 44 | this.on( name, null, data, fn ) : 45 | this.trigger( name ); 46 | }; 47 | } 48 | ); 49 | 50 | } ); 51 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_containers.scss: -------------------------------------------------------------------------------- 1 | // Container widths 2 | // 3 | // Set the container width, and override it for fixed navbars in media queries. 4 | 5 | @if $enable-container-classes { 6 | // Single container class with breakpoint max-widths 7 | .container, 8 | // 100% wide container at all breakpoints 9 | .container-fluid { 10 | @include make-container(); 11 | } 12 | 13 | // Responsive containers that are 100% wide until a breakpoint 14 | @each $breakpoint, $container-max-width in $container-max-widths { 15 | .container-#{$breakpoint} { 16 | @extend .container-fluid; 17 | } 18 | 19 | @include media-breakpoint-up($breakpoint, $grid-breakpoints) { 20 | %responsive-container-#{$breakpoint} { 21 | max-width: $container-max-width; 22 | } 23 | 24 | // Extend each breakpoint which is smaller or equal to the current breakpoint 25 | $extend-breakpoint: true; 26 | 27 | @each $name, $width in $grid-breakpoints { 28 | @if ($extend-breakpoint) { 29 | .container#{breakpoint-infix($name, $grid-breakpoints)} { 30 | @extend %responsive-container-#{$breakpoint}; 31 | } 32 | 33 | // Once the current breakpoint is reached, stop extending 34 | @if ($breakpoint == $name) { 35 | $extend-breakpoint: false; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | "noImplicitReturns": true, 12 | "allowUnreachableCode": false, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "suppressExcessPropertyErrors": false, 16 | "noImplicitThis": true, 17 | "noFallthroughCasesInSwitch": true, 18 | //"strictFunctionTypes": true, 19 | "suppressImplicitAnyIndexErrors": false, 20 | "alwaysStrict": true, 21 | "noImplicitAny": true, 22 | "strictNullChecks": true, 23 | "target": "es5", 24 | "typeRoots": [ 25 | "node_modules/@types" 26 | ], 27 | "lib": [ 28 | "es2017", 29 | "dom" 30 | ], 31 | "baseUrl": "src", 32 | "paths": { 33 | "@app/*": [ 34 | "app/*" 35 | ], 36 | "@app/core/*": [ 37 | "app/core/*" 38 | ], 39 | "@app/shared/*": [ 40 | "app/shared/*" 41 | ], 42 | "@env/*": [ 43 | "environments/*" 44 | ] 45 | } 46 | }, 47 | "angularCompilerOptions": { 48 | "fullTemplateTypeCheck": true, 49 | "preserveWhiteSpace": false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/change-password/change-password.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpErrorResponse } from "@angular/common/http"; 2 | import { Component, OnInit } from "@angular/core"; 3 | import { NgForm } from "@angular/forms"; 4 | import { Router } from "@angular/router"; 5 | 6 | import { ChangePassword } from "./models/change-password"; 7 | import { ChangePasswordService } from "./services/change-password.service"; 8 | 9 | @Component({ 10 | selector: "app-change-password", 11 | templateUrl: "./change-password.component.html", 12 | styleUrls: ["./change-password.component.css"] 13 | }) 14 | export class ChangePasswordComponent implements OnInit { 15 | 16 | error = ""; 17 | model: ChangePassword = { 18 | oldPassword: "", 19 | newPassword: "", 20 | confirmPassword: "" 21 | }; 22 | 23 | constructor(private router: Router, private changePasswordService: ChangePasswordService) { } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | submitForm(form: NgForm) { 29 | console.log(this.model); 30 | console.log(form.value); 31 | this.error = ""; 32 | this.changePasswordService.changePassword(this.model) 33 | .subscribe(() => { 34 | this.router.navigate(["/welcome"]); 35 | }, (error: HttpErrorResponse) => { 36 | console.error("ChangePassword error", error); 37 | this.error = `${error.error} -> ${error.statusText}: ${error.message}`; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/manipulation/support.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../var/document", 3 | "../var/support" 4 | ], function( document, support ) { 5 | 6 | "use strict"; 7 | 8 | ( function() { 9 | var fragment = document.createDocumentFragment(), 10 | div = fragment.appendChild( document.createElement( "div" ) ), 11 | input = document.createElement( "input" ); 12 | 13 | // Support: Android 4.0 - 4.3 only 14 | // Check state lost if the name is set (trac-11217) 15 | // Support: Windows Web Apps (WWA) 16 | // `name` and `type` must use .setAttribute for WWA (trac-14901) 17 | input.setAttribute( "type", "radio" ); 18 | input.setAttribute( "checked", "checked" ); 19 | input.setAttribute( "name", "t" ); 20 | 21 | div.appendChild( input ); 22 | 23 | // Support: Android <=4.1 only 24 | // Older WebKit doesn't clone checked state correctly in fragments 25 | support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; 26 | 27 | // Support: IE <=11 only 28 | // Make sure textarea (and checkbox) defaultValue is properly cloned 29 | div.innerHTML = ""; 30 | support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; 31 | 32 | // Support: IE <=9 only 33 | // IE <=9 replaces "; 36 | support.option = !!div.lastChild; 37 | } )(); 38 | 39 | return support; 40 | 41 | } ); 42 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/css/var/isHiddenWithinTree.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../../core", 3 | "../../core/isAttached" 4 | 5 | // css is assumed 6 | ], function( jQuery, isAttached ) { 7 | "use strict"; 8 | 9 | // isHiddenWithinTree reports if an element has a non-"none" display style (inline and/or 10 | // through the CSS cascade), which is useful in deciding whether or not to make it visible. 11 | // It differs from the :hidden selector (jQuery.expr.pseudos.hidden) in two important ways: 12 | // * A hidden ancestor does not force an element to be classified as hidden. 13 | // * Being disconnected from the document does not force an element to be classified as hidden. 14 | // These differences improve the behavior of .toggle() et al. when applied to elements that are 15 | // detached or contained within hidden ancestors (gh-2404, gh-2863). 16 | return function( elem, el ) { 17 | 18 | // isHiddenWithinTree might be called from jQuery#filter function; 19 | // in that case, element will be second argument 20 | elem = el || elem; 21 | 22 | // Inline style trumps all 23 | return elem.style.display === "none" || 24 | elem.style.display === "" && 25 | 26 | // Otherwise, check computed style 27 | // Support: Firefox <=43 - 45 28 | // Disconnected elements can have computed display: none, so first confirm that elem is 29 | // in the document. 30 | isAttached( elem ) && 31 | 32 | jQuery.css( elem, "display" ) === "none"; 33 | }; 34 | } ); 35 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/ASPNETCore2JwtAuthentication.DataLayer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Common/ServerPath.cs: -------------------------------------------------------------------------------- 1 | namespace ASPNETCore2JwtAuthentication.Common; 2 | 3 | public static class ServerPath 4 | { 5 | public static string GetProjectPath(Assembly startupAssembly) 6 | { 7 | ArgumentNullException.ThrowIfNull(startupAssembly); 8 | 9 | var projectName = startupAssembly.GetName().Name; 10 | 11 | if (string.IsNullOrWhiteSpace(projectName)) 12 | { 13 | throw new InvalidOperationException(message: "Couldn't find the assembly name."); 14 | } 15 | 16 | var applicationBasePath = AppContext.BaseDirectory; 17 | var directoryInfo = new DirectoryInfo(applicationBasePath); 18 | 19 | do 20 | { 21 | directoryInfo = directoryInfo.Parent; 22 | 23 | if (directoryInfo is null) 24 | { 25 | break; 26 | } 27 | 28 | var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName)); 29 | 30 | if (projectDirectoryInfo.Exists && 31 | new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj")).Exists) 32 | { 33 | return Path.Combine(projectDirectoryInfo.FullName, projectName); 34 | } 35 | } 36 | while (directoryInfo.Parent != null); 37 | 38 | throw new InvalidOperationException( 39 | $"Project root could not be located using the application root {applicationBasePath}."); 40 | } 41 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/Base/TestsHttpClient.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.Models; 2 | using Microsoft.AspNetCore.Routing; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace ASPNETCore2JwtAuthentication.IntegrationTests.Base; 7 | 8 | internal static class TestsHttpClient 9 | { 10 | private static readonly CustomWebApplicationFactory Factory = new(); 11 | 12 | private static readonly Lazy ServiceProviderBuilder = 13 | new(GetClientModel, LazyThreadSafetyMode.ExecutionAndPublication); 14 | 15 | /// 16 | /// A lazy loaded thread-safe singleton 17 | /// 18 | public static TestsHttpClientModel Instance { get; } = ServiceProviderBuilder.Value; 19 | 20 | private static TestsHttpClientModel GetClientModel() 21 | { 22 | var httpClient = Factory.CreateClient(); 23 | var services = Factory.Services; 24 | var settings = services.GetRequiredService>().Value; 25 | var generator = services.GetRequiredService(); 26 | 27 | return new TestsHttpClientModel 28 | { 29 | HttpClient = httpClient, 30 | ServiceProvider = services, 31 | AdminUserSeed = settings, 32 | LinkGenerator = generator, 33 | }; //NOTE: This action is very time consuming, so it should be defined as a singleton. 34 | } 35 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Controllers/ChangePasswordController.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.Models; 2 | using ASPNETCore2JwtAuthentication.Services; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Cors; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace ASPNETCore2JwtAuthentication.WebApp.Controllers; 8 | 9 | [Authorize] 10 | [Route(template: "api/[controller]")] 11 | [EnableCors(policyName: "CorsPolicy")] 12 | public class ChangePasswordController(IUsersService usersService) : Controller 13 | { 14 | private readonly IUsersService 15 | _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); 16 | 17 | [HttpPost] 18 | [ValidateAntiForgeryToken] 19 | public async Task Post([FromBody] ChangePasswordViewModel model) 20 | { 21 | if (model == null) 22 | { 23 | return BadRequest(); 24 | } 25 | 26 | if (!ModelState.IsValid) 27 | { 28 | return BadRequest(ModelState); 29 | } 30 | 31 | var user = await _usersService.GetCurrentUserAsync(); 32 | 33 | if (user == null) 34 | { 35 | return BadRequest(error: "NotFound"); 36 | } 37 | 38 | var (succeeded, error) = await _usersService.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); 39 | 40 | if (succeeded) 41 | { 42 | return Ok(); 43 | } 44 | 45 | return BadRequest(error); 46 | } 47 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/bootstrap-grid.scss: -------------------------------------------------------------------------------- 1 | @import "mixins/banner"; 2 | @include bsBanner(Grid); 3 | 4 | $include-column-box-sizing: true !default; 5 | 6 | @import "functions"; 7 | @import "variables"; 8 | @import "maps"; 9 | 10 | @import "mixins/lists"; 11 | @import "mixins/breakpoints"; 12 | @import "mixins/container"; 13 | @import "mixins/grid"; 14 | @import "mixins/utilities"; 15 | 16 | @import "vendor/rfs"; 17 | 18 | @import "root"; 19 | 20 | @import "containers"; 21 | @import "grid"; 22 | 23 | @import "utilities"; 24 | // Only use the utilities we need 25 | // stylelint-disable-next-line scss/dollar-variable-default 26 | $utilities: map-get-multiple( 27 | $utilities, 28 | ( 29 | "display", 30 | "order", 31 | "flex", 32 | "flex-direction", 33 | "flex-grow", 34 | "flex-shrink", 35 | "flex-wrap", 36 | "justify-content", 37 | "align-items", 38 | "align-content", 39 | "align-self", 40 | "margin", 41 | "margin-x", 42 | "margin-y", 43 | "margin-top", 44 | "margin-end", 45 | "margin-bottom", 46 | "margin-start", 47 | "negative-margin", 48 | "negative-margin-x", 49 | "negative-margin-y", 50 | "negative-margin-top", 51 | "negative-margin-end", 52 | "negative-margin-bottom", 53 | "negative-margin-start", 54 | "padding", 55 | "padding-x", 56 | "padding-y", 57 | "padding-top", 58 | "padding-end", 59 | "padding-bottom", 60 | "padding-start", 61 | ) 62 | ); 63 | 64 | @import "utilities/api"; 65 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/services/api-config.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from "@angular/common/http"; 2 | import { Inject, Injectable, Injector } from "@angular/core"; 3 | 4 | import { APP_CONFIG, IAppConfig } from "./app.config"; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class ApiConfigService { 10 | 11 | private config: IApiConfig | null = null; 12 | 13 | constructor( 14 | private injector: Injector, 15 | @Inject(APP_CONFIG) private appConfig: IAppConfig) { } 16 | 17 | loadApiConfig(): Promise { 18 | const http = this.injector.get(HttpClient); 19 | const url = `${this.appConfig.apiEndpoint}/${this.appConfig.apiSettingsPath}`; 20 | return http.get(url) 21 | .toPromise() 22 | .then(config => { 23 | this.config = config; 24 | console.log("ApiConfig", this.config); 25 | }) 26 | .catch(err => { 27 | console.error(`Failed to loadApiConfig(). Make sure ${url} is accessible.`, this.config); 28 | return Promise.reject(err); 29 | }); 30 | } 31 | 32 | get configuration(): IApiConfig { 33 | if (!this.config) { 34 | throw new Error("Attempted to access configuration property before configuration data was loaded."); 35 | } 36 | return this.config; 37 | } 38 | } 39 | 40 | export interface IApiConfig { 41 | loginPath: string; 42 | logoutPath: string; 43 | refreshTokenPath: string; 44 | accessTokenObjectKey: string; 45 | refreshTokenObjectKey: string; 46 | adminRoleName: string; 47 | } 48 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.DataLayer/Context/ApplicationDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Design; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace ASPNETCore2JwtAuthentication.DataLayer.Context; 6 | 7 | /// 8 | /// Only used by EF Tooling 9 | /// 10 | public class ApplicationDbContextFactory : IDesignTimeDbContextFactory 11 | { 12 | public ApplicationDbContext CreateDbContext(string[] args) 13 | { 14 | var basePath = Directory.GetCurrentDirectory(); 15 | WriteLine($"Using `{basePath}` as the BasePath"); 16 | var configuration = new ConfigurationBuilder() 17 | .SetBasePath(basePath) 18 | .AddJsonFile("appsettings.json") 19 | .Build(); 20 | var builder = new DbContextOptionsBuilder(); 21 | var defaultConnection = configuration.GetConnectionString("DefaultConnection"); 22 | if (string.IsNullOrWhiteSpace(defaultConnection)) 23 | { 24 | throw new InvalidOperationException("defaultConnection is null"); 25 | } 26 | 27 | var connectionString = defaultConnection.Replace("|DataDirectory|", 28 | Path.Combine(basePath, "wwwroot", "app_data"), 29 | StringComparison.OrdinalIgnoreCase); 30 | builder.UseSqlServer(connectionString); 31 | return new ApplicationDbContext(builder.Options); 32 | } 33 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jwt-decode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-decode", 3 | "version": "3.1.2", 4 | "description": "Decode JWT tokens, mostly useful for browser applications.", 5 | "main": "build/jwt-decode.cjs.js", 6 | "module": "build/jwt-decode.esm.js", 7 | "types": "index.d.ts", 8 | "keywords": [ 9 | "jwt", 10 | "browser" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/auth0/jwt-decode" 15 | }, 16 | "url": "https://github.com/auth0/jwt-decode/issues", 17 | "homepage": "https://github.com/auth0/jwt-decode#readme", 18 | "scripts": { 19 | "dev": "rollup -m -c", 20 | "build": "rimraf build && rollup -m -c --environment NODE_ENV:production", 21 | "test": "mocha" 22 | }, 23 | "author": "Jose F. Romaniello (http://joseoncode.com)", 24 | "contributors": [ 25 | "Sam Bellen " 26 | ], 27 | "license": "MIT", 28 | "dependencies": {}, 29 | "devDependencies": { 30 | "@rollup/plugin-commonjs": "^15.0.0", 31 | "@rollup/plugin-node-resolve": "^9.0.0", 32 | "expect.js": "~0.2.0", 33 | "mocha": "^8.1.3", 34 | "rimraf": "^2.2.8", 35 | "rollup": "^2.26.11", 36 | "rollup-plugin-livereload": "^2.0.0", 37 | "rollup-plugin-serve": "^1.0.4", 38 | "rollup-plugin-sourcemaps": "^0.6.2", 39 | "rollup-plugin-terser": "^7.0.2", 40 | "uglify-js": "^2.8.29" 41 | }, 42 | "files": [ 43 | "build/", 44 | "index.d.ts" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/core/services/browser-storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class BrowserStorageService { 7 | 8 | getSession(key: string): any { 9 | const data = window.sessionStorage.getItem(key); 10 | if (data) { 11 | return JSON.parse(data); 12 | } else { 13 | return null; 14 | } 15 | } 16 | 17 | setSession(key: string, value: any): void { 18 | const data = value === undefined ? "" : JSON.stringify(value); 19 | window.sessionStorage.setItem(key, data); 20 | } 21 | 22 | removeSession(key: string): void { 23 | window.sessionStorage.removeItem(key); 24 | } 25 | 26 | removeAllSessions(): void { 27 | for (const key in window.sessionStorage) { 28 | if (window.sessionStorage.hasOwnProperty(key)) { 29 | this.removeSession(key); 30 | } 31 | } 32 | } 33 | 34 | getLocal(key: string): any { 35 | const data = window.localStorage.getItem(key); 36 | if (data) { 37 | return JSON.parse(data); 38 | } else { 39 | return null; 40 | } 41 | } 42 | 43 | setLocal(key: string, value: any): void { 44 | const data = value === undefined ? "" : JSON.stringify(value); 45 | window.localStorage.setItem(key, data); 46 | } 47 | 48 | removeLocal(key: string): void { 49 | window.localStorage.removeItem(key); 50 | } 51 | 52 | removeAllLocals(): void { 53 | for (const key in window.localStorage) { 54 | if (window.localStorage.hasOwnProperty(key)) { 55 | this.removeLocal(key); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/Controllers/MyProtectedAdminApiController.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using ASPNETCore2JwtAuthentication.Services; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Cors; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace ASPNETCore2JwtAuthentication.WebApp.Controllers; 8 | 9 | [Route(template: "api/[controller]")] 10 | [EnableCors(policyName: "CorsPolicy")] 11 | [Authorize(Policy = CustomRoles.Admin)] 12 | public class MyProtectedAdminApiController(IUsersService usersService) : Controller 13 | { 14 | private readonly IUsersService 15 | _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); 16 | 17 | [HttpGet] 18 | public async Task Get() 19 | { 20 | var claimsIdentity = User.Identity as ClaimsIdentity; 21 | var userDataClaim = claimsIdentity?.FindFirst(ClaimTypes.UserData); 22 | var userId = userDataClaim?.Value; 23 | 24 | return Ok(new 25 | { 26 | Id = 1, 27 | Title = "Hello from My Protected Admin Api Controller! [Authorize(Policy = CustomRoles.Admin)]", 28 | Username = User.Identity?.Name, 29 | UserData = userId, 30 | TokenSerialNumber = 31 | await _usersService.GetSerialNumberAsync(int.Parse(userId ?? "0", NumberStyles.Number, 32 | CultureInfo.InvariantCulture)), 33 | Roles = claimsIdentity?.Claims.Where(x => string.Equals(x.Type, ClaimTypes.Role, StringComparison.Ordinal)) 34 | .Select(x => x.Value) 35 | .ToList() 36 | }); 37 | } 38 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/access.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../core/toType", 4 | "../var/isFunction" 5 | ], function( jQuery, toType, isFunction ) { 6 | 7 | "use strict"; 8 | 9 | // Multifunctional method to get and set values of a collection 10 | // The value/s can optionally be executed if it's a function 11 | var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { 12 | var i = 0, 13 | len = elems.length, 14 | bulk = key == null; 15 | 16 | // Sets many values 17 | if ( toType( key ) === "object" ) { 18 | chainable = true; 19 | for ( i in key ) { 20 | access( elems, fn, i, key[ i ], true, emptyGet, raw ); 21 | } 22 | 23 | // Sets one value 24 | } else if ( value !== undefined ) { 25 | chainable = true; 26 | 27 | if ( !isFunction( value ) ) { 28 | raw = true; 29 | } 30 | 31 | if ( bulk ) { 32 | 33 | // Bulk operations run against the entire set 34 | if ( raw ) { 35 | fn.call( elems, value ); 36 | fn = null; 37 | 38 | // ...except when executing function values 39 | } else { 40 | bulk = fn; 41 | fn = function( elem, _key, value ) { 42 | return bulk.call( jQuery( elem ), value ); 43 | }; 44 | } 45 | } 46 | 47 | if ( fn ) { 48 | for ( ; i < len; i++ ) { 49 | fn( 50 | elems[ i ], key, raw ? 51 | value : 52 | value.call( elems[ i ], i, fn( elems[ i ], key ) ) 53 | ); 54 | } 55 | } 56 | } 57 | 58 | if ( chainable ) { 59 | return elems; 60 | } 61 | 62 | // Gets 63 | if ( bulk ) { 64 | return fn.call( elems ); 65 | } 66 | 67 | return len ? fn( elems[ 0 ], key ) : emptyGet; 68 | }; 69 | 70 | return access; 71 | 72 | } ); 73 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/js/src/dom/data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------------------------------------------- 3 | * Bootstrap (v5.2.3): dom/data.js 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | * -------------------------------------------------------------------------- 6 | */ 7 | 8 | /** 9 | * Constants 10 | */ 11 | 12 | const elementMap = new Map() 13 | 14 | export default { 15 | set(element, key, instance) { 16 | if (!elementMap.has(element)) { 17 | elementMap.set(element, new Map()) 18 | } 19 | 20 | const instanceMap = elementMap.get(element) 21 | 22 | // make it clear we only want one instance per element 23 | // can be removed later when multiple key/instances are fine to be used 24 | if (!instanceMap.has(key) && instanceMap.size !== 0) { 25 | // eslint-disable-next-line no-console 26 | console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`) 27 | return 28 | } 29 | 30 | instanceMap.set(key, instance) 31 | }, 32 | 33 | get(element, key) { 34 | if (elementMap.has(element)) { 35 | return elementMap.get(element).get(key) || null 36 | } 37 | 38 | return null 39 | }, 40 | 41 | remove(element, key) { 42 | if (!elementMap.has(element)) { 43 | return 44 | } 45 | 46 | const instanceMap = elementMap.get(element) 47 | 48 | instanceMap.delete(key) 49 | 50 | // free up element references if there are no instances left for an element 51 | if (instanceMap.size === 0) { 52 | elementMap.delete(element) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aspnetcore2-jwt-authentication.angular-client", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "7.2.4", 16 | "@angular/common": "7.2.4", 17 | "@angular/compiler": "7.2.4", 18 | "@angular/core": "7.2.4", 19 | "@angular/forms": "7.2.4", 20 | "@angular/http": "7.2.4", 21 | "@angular/platform-browser": "7.2.4", 22 | "@angular/platform-browser-dynamic": "7.2.4", 23 | "@angular/router": "7.2.4", 24 | "bootstrap": "4.2.1", 25 | "core-js": "2.6.3", 26 | "font-awesome": "^4.7.0", 27 | "jwt-decode": "^2.2.0", 28 | "rxjs": "6.4.0", 29 | "zone.js": "^0.8.29" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "0.13.0", 33 | "@angular/cli": "7.3.0", 34 | "@angular/compiler-cli": "7.2.4", 35 | "@angular/language-service": "7.2.4", 36 | "@types/jasmine": "3.3.8", 37 | "@types/jasminewd2": "2.0.6", 38 | "@types/jwt-decode": "^2.2.1", 39 | "@types/node": "10.12.21", 40 | "codelyzer": "4.5.0", 41 | "jasmine-core": "3.3.0", 42 | "jasmine-spec-reporter": "~4.2.1", 43 | "karma": "4.0.0", 44 | "karma-chrome-launcher": "~2.2.0", 45 | "karma-cli": "~2.0.0", 46 | "karma-coverage-istanbul-reporter": "2.0.4", 47 | "karma-jasmine": "2.0.1", 48 | "karma-jasmine-html-reporter": "1.4.0", 49 | "protractor": "5.4.2", 50 | "ts-node": "8.0.2", 51 | "tslint": "5.12.1", 52 | "typescript": "^3.2.4" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.Services/RolesService.cs: -------------------------------------------------------------------------------- 1 | using ASPNETCore2JwtAuthentication.DataLayer.Context; 2 | using ASPNETCore2JwtAuthentication.DomainClasses; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace ASPNETCore2JwtAuthentication.Services; 6 | 7 | public class RolesService : IRolesService 8 | { 9 | private readonly DbSet _roles; 10 | private readonly DbSet _users; 11 | 12 | public RolesService(IUnitOfWork uow) 13 | { 14 | ArgumentNullException.ThrowIfNull(uow); 15 | 16 | _roles = uow.Set(); 17 | _users = uow.Set(); 18 | } 19 | 20 | public Task> FindUserRolesAsync(int userId) 21 | { 22 | var userRolesQuery = from role in _roles 23 | from userRoles in role.UserRoles 24 | where userRoles.UserId == userId 25 | select role; 26 | 27 | return userRolesQuery.OrderBy(x => x.Name).ToListAsync(); 28 | } 29 | 30 | public async Task IsUserInRoleAsync(int userId, string roleName) 31 | { 32 | var userRolesQuery = from role in _roles 33 | where role.Name == roleName 34 | from user in role.UserRoles 35 | where user.UserId == userId 36 | select role; 37 | var userRole = await userRolesQuery.FirstOrDefaultAsync(); 38 | return userRole != null; 39 | } 40 | 41 | public Task> FindUsersInRoleAsync(string roleName) 42 | { 43 | var roleUserIdsQuery = from role in _roles 44 | where role.Name == roleName 45 | from user in role.UserRoles 46 | select user.UserId; 47 | return _users.Where(user => roleUserIdsQuery.Contains(user.Id)) 48 | .ToListAsync(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/external/sizzle/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/sizzle 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.IntegrationTests/Base/CustomWebApplicationFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Mvc.Testing; 3 | using Microsoft.AspNetCore.TestHost; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection.Extensions; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace ASPNETCore2JwtAuthentication.IntegrationTests.Base; 9 | 10 | internal class CustomWebApplicationFactory : WebApplicationFactory 11 | { 12 | protected override IHost CreateHost(IHostBuilder builder) 13 | { 14 | // How to override settings ... 15 | builder.ConfigureHostConfiguration(config => 16 | { 17 | config.AddInMemoryCollection(new Dictionary 18 | { 19 | { 20 | "ConnectionStrings:SqlServer:ApplicationDbContextConnection", "newConnectionString ...." 21 | } 22 | }); 23 | 24 | // config.AddJsonFile("integrationsettings.json").Build(); 25 | }); 26 | 27 | var host = base.CreateHost(builder); 28 | 29 | // How to run a command during startup 30 | // host.InitializeDb() 31 | 32 | return host; 33 | } 34 | 35 | protected override IWebHostBuilder? CreateWebHostBuilder() 36 | { 37 | var builder = base.CreateWebHostBuilder(); 38 | 39 | builder?.ConfigureLogging(logging => 40 | { 41 | //TODO: ... 42 | }); 43 | 44 | return builder; 45 | } 46 | 47 | protected override void ConfigureWebHost(IWebHostBuilder builder) 48 | => builder.ConfigureTestServices(services => 49 | { 50 | // How to remove or add services 51 | services.RemoveAll(); 52 | }); 53 | } -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/mixins/_caret.scss: -------------------------------------------------------------------------------- 1 | // scss-docs-start caret-mixins 2 | @mixin caret-down { 3 | border-top: $caret-width solid; 4 | border-right: $caret-width solid transparent; 5 | border-bottom: 0; 6 | border-left: $caret-width solid transparent; 7 | } 8 | 9 | @mixin caret-up { 10 | border-top: 0; 11 | border-right: $caret-width solid transparent; 12 | border-bottom: $caret-width solid; 13 | border-left: $caret-width solid transparent; 14 | } 15 | 16 | @mixin caret-end { 17 | border-top: $caret-width solid transparent; 18 | border-right: 0; 19 | border-bottom: $caret-width solid transparent; 20 | border-left: $caret-width solid; 21 | } 22 | 23 | @mixin caret-start { 24 | border-top: $caret-width solid transparent; 25 | border-right: $caret-width solid; 26 | border-bottom: $caret-width solid transparent; 27 | } 28 | 29 | @mixin caret($direction: down) { 30 | @if $enable-caret { 31 | &::after { 32 | display: inline-block; 33 | margin-left: $caret-spacing; 34 | vertical-align: $caret-vertical-align; 35 | content: ""; 36 | @if $direction == down { 37 | @include caret-down(); 38 | } @else if $direction == up { 39 | @include caret-up(); 40 | } @else if $direction == end { 41 | @include caret-end(); 42 | } 43 | } 44 | 45 | @if $direction == start { 46 | &::after { 47 | display: none; 48 | } 49 | 50 | &::before { 51 | display: inline-block; 52 | margin-right: $caret-spacing; 53 | vertical-align: $caret-vertical-align; 54 | content: ""; 55 | @include caret-start(); 56 | } 57 | } 58 | 59 | &:empty::after { 60 | margin-left: 0; 61 | } 62 | } 63 | } 64 | // scss-docs-end caret-mixins 65 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/shared/directives/has-auth-user-view-permission.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; 2 | import { AuthService } from "@app/core"; 3 | import { Subscription } from "rxjs"; 4 | 5 | @Directive({ 6 | selector: "[hasAuthUserViewPermission]" 7 | }) 8 | export class HasAuthUserViewPermissionDirective implements OnInit, OnDestroy { 9 | private isVisible = false; 10 | private requiredRoles: string[] | null = null; 11 | private subscription: Subscription | null = null; 12 | 13 | @Input() 14 | set hasAuthUserViewPermission(roles: string[] | null) { 15 | this.requiredRoles = roles; 16 | } 17 | 18 | // Note, if you don't place the * in front, you won't be able to inject the TemplateRef or ViewContainerRef into your directive. 19 | constructor( 20 | private templateRef: TemplateRef, 21 | private viewContainer: ViewContainerRef, 22 | private authService: AuthService 23 | ) { } 24 | 25 | ngOnInit() { 26 | this.subscription = this.authService.authStatus$.subscribe(status => this.changeVisibility(status)); 27 | this.changeVisibility(this.authService.isAuthUserLoggedIn()); 28 | } 29 | 30 | ngOnDestroy(): void { 31 | if (this.subscription) { 32 | this.subscription.unsubscribe(); 33 | } 34 | } 35 | 36 | private changeVisibility(status: boolean) { 37 | const isInRoles = !this.requiredRoles ? true : this.authService.isAuthUserInRoles(this.requiredRoles); 38 | if (isInRoles && status) { 39 | if (!this.isVisible) { 40 | this.viewContainer.createEmbeddedView(this.templateRef); 41 | this.isVisible = true; 42 | } 43 | } else { 44 | this.isVisible = false; 45 | this.viewContainer.clear(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpErrorResponse } from "@angular/common/http"; 2 | import { Component, OnInit } from "@angular/core"; 3 | import { NgForm } from "@angular/forms"; 4 | import { ActivatedRoute, Router } from "@angular/router"; 5 | import { AuthService, Credentials } from "@app/core"; 6 | 7 | @Component({ 8 | selector: "app-login", 9 | templateUrl: "./login.component.html", 10 | styleUrls: ["./login.component.css"] 11 | }) 12 | export class LoginComponent implements OnInit { 13 | 14 | model: Credentials = { username: "", password: "", rememberMe: false }; 15 | error = ""; 16 | returnUrl: string | null = null; 17 | 18 | constructor( 19 | private authService: AuthService, 20 | private router: Router, 21 | private route: ActivatedRoute) { } 22 | 23 | ngOnInit() { 24 | // reset the login status 25 | this.authService.logout(false); 26 | 27 | // get the return url from route parameters 28 | this.returnUrl = this.route.snapshot.queryParams["returnUrl"]; 29 | } 30 | 31 | submitForm(form: NgForm) { 32 | console.log(form); 33 | 34 | this.error = ""; 35 | this.authService.login(this.model) 36 | .subscribe(isLoggedIn => { 37 | if (isLoggedIn) { 38 | if (this.returnUrl) { 39 | this.router.navigate([this.returnUrl]); 40 | } else { 41 | this.router.navigate(["/protectedPage"]); 42 | } 43 | } 44 | }, 45 | (error: HttpErrorResponse) => { 46 | console.error("Login error", error); 47 | if (error.status === 401) { 48 | this.error = "Invalid User name or Password. Please try again."; 49 | } else { 50 | this.error = `${error.statusText}: ${error.message}`; 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/wrap.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "./core", 3 | "./var/isFunction", 4 | "./core/init", 5 | "./manipulation", // clone 6 | "./traversing" // parent, contents 7 | ], function( jQuery, isFunction ) { 8 | 9 | "use strict"; 10 | 11 | jQuery.fn.extend( { 12 | wrapAll: function( html ) { 13 | var wrap; 14 | 15 | if ( this[ 0 ] ) { 16 | if ( isFunction( html ) ) { 17 | html = html.call( this[ 0 ] ); 18 | } 19 | 20 | // The elements to wrap the target around 21 | wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); 22 | 23 | if ( this[ 0 ].parentNode ) { 24 | wrap.insertBefore( this[ 0 ] ); 25 | } 26 | 27 | wrap.map( function() { 28 | var elem = this; 29 | 30 | while ( elem.firstElementChild ) { 31 | elem = elem.firstElementChild; 32 | } 33 | 34 | return elem; 35 | } ).append( this ); 36 | } 37 | 38 | return this; 39 | }, 40 | 41 | wrapInner: function( html ) { 42 | if ( isFunction( html ) ) { 43 | return this.each( function( i ) { 44 | jQuery( this ).wrapInner( html.call( this, i ) ); 45 | } ); 46 | } 47 | 48 | return this.each( function() { 49 | var self = jQuery( this ), 50 | contents = self.contents(); 51 | 52 | if ( contents.length ) { 53 | contents.wrapAll( html ); 54 | 55 | } else { 56 | self.append( html ); 57 | } 58 | } ); 59 | }, 60 | 61 | wrap: function( html ) { 62 | var htmlIsFunction = isFunction( html ); 63 | 64 | return this.each( function( i ) { 65 | jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); 66 | } ); 67 | }, 68 | 69 | unwrap: function( selector ) { 70 | this.parent( selector ).not( "body" ).each( function() { 71 | jQuery( this ).replaceWith( this.childNodes ); 72 | } ); 73 | return this; 74 | } 75 | } ); 76 | 77 | return jQuery; 78 | } ); 79 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from "@angular/common"; 2 | import { HttpClientModule } from "@angular/common/http"; 3 | import { ModuleWithProviders, NgModule } from "@angular/core"; 4 | import { FormsModule } from "@angular/forms"; 5 | 6 | import { EqualValidatorDirective } from "./directives/equal-validator.directive"; 7 | import { HasAuthUserViewPermissionDirective } from "./directives/has-auth-user-view-permission.directive"; 8 | import { IsVisibleForAuthUserDirective } from "./directives/is-visible-for-auth-user.directive"; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CommonModule, 13 | FormsModule, 14 | HttpClientModule 15 | ], 16 | entryComponents: [ 17 | // All components about to be loaded "dynamically" need to be declared in the entryComponents section. 18 | ], 19 | declarations: [ 20 | // common and shared components/directives/pipes between more than one module and components will be listed here. 21 | IsVisibleForAuthUserDirective, 22 | HasAuthUserViewPermissionDirective, 23 | EqualValidatorDirective 24 | ], 25 | exports: [ 26 | // common and shared components/directives/pipes between more than one module and components will be listed here. 27 | CommonModule, 28 | FormsModule, 29 | HttpClientModule, 30 | IsVisibleForAuthUserDirective, 31 | HasAuthUserViewPermissionDirective, 32 | EqualValidatorDirective 33 | ] 34 | /* No providers here! Since they’ll be already provided in AppModule. */ 35 | }) 36 | export class SharedModule { 37 | static forRoot(): ModuleWithProviders { 38 | // Forcing the whole app to use the returned providers from the AppModule only. 39 | return { 40 | ngModule: SharedModule, 41 | providers: [ /* All of your services here. It will hold the services needed by `itself`. */] 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_maps.scss: -------------------------------------------------------------------------------- 1 | // Re-assigned maps 2 | // 3 | // Placed here so that others can override the default Sass maps and see automatic updates to utilities and more. 4 | 5 | // scss-docs-start theme-colors-rgb 6 | $theme-colors-rgb: map-loop($theme-colors, to-rgb, "$value") !default; 7 | // scss-docs-end theme-colors-rgb 8 | 9 | // Utilities maps 10 | // 11 | // Extends the default `$theme-colors` maps to help create our utilities. 12 | 13 | // Come v6, we'll de-dupe these variables. Until then, for backward compatibility, we keep them to reassign. 14 | // scss-docs-start utilities-colors 15 | $utilities-colors: $theme-colors-rgb !default; 16 | // scss-docs-end utilities-colors 17 | 18 | // scss-docs-start utilities-text-colors 19 | $utilities-text: map-merge( 20 | $utilities-colors, 21 | ( 22 | "black": to-rgb($black), 23 | "white": to-rgb($white), 24 | "body": to-rgb($body-color) 25 | ) 26 | ) !default; 27 | $utilities-text-colors: map-loop($utilities-text, rgba-css-var, "$key", "text") !default; 28 | // scss-docs-end utilities-text-colors 29 | 30 | // scss-docs-start utilities-bg-colors 31 | $utilities-bg: map-merge( 32 | $utilities-colors, 33 | ( 34 | "black": to-rgb($black), 35 | "white": to-rgb($white), 36 | "body": to-rgb($body-bg) 37 | ) 38 | ) !default; 39 | $utilities-bg-colors: map-loop($utilities-bg, rgba-css-var, "$key", "bg") !default; 40 | // scss-docs-end utilities-bg-colors 41 | 42 | // scss-docs-start utilities-border-colors 43 | $utilities-border: map-merge( 44 | $utilities-colors, 45 | ( 46 | "white": to-rgb($white) 47 | ) 48 | ) !default; 49 | $utilities-border-colors: map-loop($utilities-border, rgba-css-var, "$key", "border") !default; 50 | // scss-docs-end utilities-border-colors 51 | 52 | $negative-spacers: if($enable-negative-margins, negativify-map($spacers), null) !default; 53 | 54 | $gutters: $spacers !default; 55 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/core/parseHTML.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../var/document", 4 | "./var/rsingleTag", 5 | "../manipulation/buildFragment", 6 | 7 | // This is the only module that needs core/support 8 | "./support" 9 | ], function( jQuery, document, rsingleTag, buildFragment, support ) { 10 | 11 | "use strict"; 12 | 13 | // Argument "data" should be string of html 14 | // context (optional): If specified, the fragment will be created in this context, 15 | // defaults to document 16 | // keepScripts (optional): If true, will include scripts passed in the html string 17 | jQuery.parseHTML = function( data, context, keepScripts ) { 18 | if ( typeof data !== "string" ) { 19 | return []; 20 | } 21 | if ( typeof context === "boolean" ) { 22 | keepScripts = context; 23 | context = false; 24 | } 25 | 26 | var base, parsed, scripts; 27 | 28 | if ( !context ) { 29 | 30 | // Stop scripts or inline event handlers from being executed immediately 31 | // by using document.implementation 32 | if ( support.createHTMLDocument ) { 33 | context = document.implementation.createHTMLDocument( "" ); 34 | 35 | // Set the base href for the created document 36 | // so any parsed elements with URLs 37 | // are based on the document's URL (gh-2965) 38 | base = context.createElement( "base" ); 39 | base.href = document.location.href; 40 | context.head.appendChild( base ); 41 | } else { 42 | context = document; 43 | } 44 | } 45 | 46 | parsed = rsingleTag.exec( data ); 47 | scripts = !keepScripts && []; 48 | 49 | // Single tag 50 | if ( parsed ) { 51 | return [ context.createElement( parsed[ 1 ] ) ]; 52 | } 53 | 54 | parsed = buildFragment( [ data ], context, scripts ); 55 | 56 | if ( scripts && scripts.length ) { 57 | jQuery( scripts ).remove(); 58 | } 59 | 60 | return jQuery.merge( [], parsed.childNodes ); 61 | }; 62 | 63 | return jQuery.parseHTML; 64 | 65 | } ); 66 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/authentication/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Login

4 |
5 |
6 |
7 |
8 | 9 | 12 |
13 |
14 | Name is required. 15 |
16 |
17 |
18 | 19 |
20 | 21 | 24 |
25 |
26 | Password is required. 27 |
28 |
29 |
30 | 31 |
32 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | 44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | // scss-docs-start breadcrumb-css-vars 3 | --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x}; 4 | --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y}; 5 | --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; 6 | @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size); 7 | --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg}; 8 | --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius}; 9 | --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color}; 10 | --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x}; 11 | --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color}; 12 | // scss-docs-end breadcrumb-css-vars 13 | 14 | display: flex; 15 | flex-wrap: wrap; 16 | padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x); 17 | margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom); 18 | @include font-size(var(--#{$prefix}breadcrumb-font-size)); 19 | list-style: none; 20 | background-color: var(--#{$prefix}breadcrumb-bg); 21 | @include border-radius(var(--#{$prefix}breadcrumb-border-radius)); 22 | } 23 | 24 | .breadcrumb-item { 25 | // The separator between breadcrumbs (by default, a forward-slash: "/") 26 | + .breadcrumb-item { 27 | padding-left: var(--#{$prefix}breadcrumb-item-padding-x); 28 | 29 | &::before { 30 | float: left; // Suppress inline spacings and underlining of the separator 31 | padding-right: var(--#{$prefix}breadcrumb-item-padding-x); 32 | color: var(--#{$prefix}breadcrumb-divider-color); 33 | content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; 34 | } 35 | } 36 | 37 | &.active { 38 | color: var(--#{$prefix}breadcrumb-item-active-color); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/event/focusin.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../data/var/dataPriv", 4 | "./support", 5 | 6 | "../event", 7 | "./trigger" 8 | ], function( jQuery, dataPriv, support ) { 9 | 10 | "use strict"; 11 | 12 | // Support: Firefox <=44 13 | // Firefox doesn't have focus(in | out) events 14 | // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 15 | // 16 | // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 17 | // focus(in | out) events fire after focus & blur events, 18 | // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order 19 | // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 20 | if ( !support.focusin ) { 21 | jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { 22 | 23 | // Attach a single capturing handler on the document while someone wants focusin/focusout 24 | var handler = function( event ) { 25 | jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); 26 | }; 27 | 28 | jQuery.event.special[ fix ] = { 29 | setup: function() { 30 | 31 | // Handle: regular nodes (via `this.ownerDocument`), window 32 | // (via `this.document`) & document (via `this`). 33 | var doc = this.ownerDocument || this.document || this, 34 | attaches = dataPriv.access( doc, fix ); 35 | 36 | if ( !attaches ) { 37 | doc.addEventListener( orig, handler, true ); 38 | } 39 | dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); 40 | }, 41 | teardown: function() { 42 | var doc = this.ownerDocument || this.document || this, 43 | attaches = dataPriv.access( doc, fix ) - 1; 44 | 45 | if ( !attaches ) { 46 | doc.removeEventListener( orig, handler, true ); 47 | dataPriv.remove( doc, fix ); 48 | 49 | } else { 50 | dataPriv.access( doc, fix, attaches ); 51 | } 52 | } 53 | }; 54 | } ); 55 | } 56 | 57 | return jQuery; 58 | } ); 59 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/scss/utilities/_api.scss: -------------------------------------------------------------------------------- 1 | // Loop over each breakpoint 2 | @each $breakpoint in map-keys($grid-breakpoints) { 3 | 4 | // Generate media query if needed 5 | @include media-breakpoint-up($breakpoint) { 6 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 7 | 8 | // Loop over each utility property 9 | @each $key, $utility in $utilities { 10 | // The utility can be disabled with `false`, thus check if the utility is a map first 11 | // Only proceed if responsive media queries are enabled or if it's the base media query 12 | @if type-of($utility) == "map" and (map-get($utility, responsive) or $infix == "") { 13 | @include generate-utility($utility, $infix); 14 | } 15 | } 16 | } 17 | } 18 | 19 | // RFS rescaling 20 | @media (min-width: $rfs-mq-value) { 21 | @each $breakpoint in map-keys($grid-breakpoints) { 22 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 23 | 24 | @if (map-get($grid-breakpoints, $breakpoint) < $rfs-breakpoint) { 25 | // Loop over each utility property 26 | @each $key, $utility in $utilities { 27 | // The utility can be disabled with `false`, thus check if the utility is a map first 28 | // Only proceed if responsive media queries are enabled or if it's the base media query 29 | @if type-of($utility) == "map" and map-get($utility, rfs) and (map-get($utility, responsive) or $infix == "") { 30 | @include generate-utility($utility, $infix, true); 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | 38 | // Print utilities 39 | @media print { 40 | @each $key, $utility in $utilities { 41 | // The utility can be disabled with `false`, thus check if the utility is a map first 42 | // Then check if the utility needs print styles 43 | @if type-of($utility) == "map" and map-get($utility, print) == true { 44 | @include generate-utility($utility, "-print"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/bootstrap/js/src/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * -------------------------------------------------------------------------- 3 | * Bootstrap (v5.2.3): button.js 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | * -------------------------------------------------------------------------- 6 | */ 7 | 8 | import { defineJQueryPlugin } from './util/index' 9 | import EventHandler from './dom/event-handler' 10 | import BaseComponent from './base-component' 11 | 12 | /** 13 | * Constants 14 | */ 15 | 16 | const NAME = 'button' 17 | const DATA_KEY = 'bs.button' 18 | const EVENT_KEY = `.${DATA_KEY}` 19 | const DATA_API_KEY = '.data-api' 20 | 21 | const CLASS_NAME_ACTIVE = 'active' 22 | const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="button"]' 23 | const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` 24 | 25 | /** 26 | * Class definition 27 | */ 28 | 29 | class Button extends BaseComponent { 30 | // Getters 31 | static get NAME() { 32 | return NAME 33 | } 34 | 35 | // Public 36 | toggle() { 37 | // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method 38 | this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE)) 39 | } 40 | 41 | // Static 42 | static jQueryInterface(config) { 43 | return this.each(function () { 44 | const data = Button.getOrCreateInstance(this) 45 | 46 | if (config === 'toggle') { 47 | data[config]() 48 | } 49 | }) 50 | } 51 | } 52 | 53 | /** 54 | * Data API implementation 55 | */ 56 | 57 | EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, event => { 58 | event.preventDefault() 59 | 60 | const button = event.target.closest(SELECTOR_DATA_TOGGLE) 61 | const data = Button.getOrCreateInstance(button) 62 | 63 | data.toggle() 64 | }) 65 | 66 | /** 67 | * jQuery 68 | */ 69 | 70 | defineJQueryPlugin(Button) 71 | 72 | export default Button 73 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.AngularClient/src/app/dashboard/call-protected-api/call-protected-api.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Call protected API

4 |
5 |
6 | 9 | 10 | 13 | 14 | 17 | 18 |
19 |
{{result | json}}
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |

Show/Hide elements using isVisibleForAuthUser directive

28 |
29 |
30 |
31 | Is-Visible-For-AuthUser 32 |
33 |
34 | Is-Visible-For-Roles = ['Admin','User'] 35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 |

Show/Hide elements using *hasAuthUserViewPermission directive

44 |
45 |
46 |
47 | *hasAuthUserViewPermission="" 48 |
49 |
50 | *hasAuthUserViewPermission="['Admin','User']" 51 |
52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /src/ASPNETCore2JwtAuthentication.WebApp/wwwroot/lib/jquery/src/ajax/script.js: -------------------------------------------------------------------------------- 1 | define( [ 2 | "../core", 3 | "../var/document", 4 | "../ajax" 5 | ], function( jQuery, document ) { 6 | 7 | "use strict"; 8 | 9 | // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) 10 | jQuery.ajaxPrefilter( function( s ) { 11 | if ( s.crossDomain ) { 12 | s.contents.script = false; 13 | } 14 | } ); 15 | 16 | // Install script dataType 17 | jQuery.ajaxSetup( { 18 | accepts: { 19 | script: "text/javascript, application/javascript, " + 20 | "application/ecmascript, application/x-ecmascript" 21 | }, 22 | contents: { 23 | script: /\b(?:java|ecma)script\b/ 24 | }, 25 | converters: { 26 | "text script": function( text ) { 27 | jQuery.globalEval( text ); 28 | return text; 29 | } 30 | } 31 | } ); 32 | 33 | // Handle cache's special case and crossDomain 34 | jQuery.ajaxPrefilter( "script", function( s ) { 35 | if ( s.cache === undefined ) { 36 | s.cache = false; 37 | } 38 | if ( s.crossDomain ) { 39 | s.type = "GET"; 40 | } 41 | } ); 42 | 43 | // Bind script tag hack transport 44 | jQuery.ajaxTransport( "script", function( s ) { 45 | 46 | // This transport only deals with cross domain or forced-by-attrs requests 47 | if ( s.crossDomain || s.scriptAttrs ) { 48 | var script, callback; 49 | return { 50 | send: function( _, complete ) { 51 | script = jQuery( "