├── .gitattributes ├── .gitignore ├── AspNetCoreIdentity.sln ├── AspNetCoreIdentity ├── .gitignore ├── .vscode │ ├── launch.json │ └── tasks.json ├── AspNetCoreIdentity.csproj ├── ClientApp │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── angular.json │ ├── browserslist │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.e2e.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── admin │ │ │ │ ├── admin.component.css │ │ │ │ ├── admin.component.html │ │ │ │ └── admin.component.ts │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── app.server.module.ts │ │ │ ├── claims │ │ │ │ ├── access-forbidden │ │ │ │ │ ├── access-forbidden.component.css │ │ │ │ │ ├── access-forbidden.component.html │ │ │ │ │ └── access-forbidden.component.ts │ │ │ │ ├── claims.component.css │ │ │ │ ├── claims.component.html │ │ │ │ └── claims.component.ts │ │ │ ├── core │ │ │ │ ├── domain.ts │ │ │ │ ├── http-interceptors │ │ │ │ │ ├── auth-interceptor.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── openid-connect.service.ts │ │ │ │ └── state.service.ts │ │ │ ├── counter │ │ │ │ ├── counter.component.html │ │ │ │ ├── counter.component.spec.ts │ │ │ │ └── counter.component.ts │ │ │ ├── fetch-data │ │ │ │ ├── fetch-data.component.html │ │ │ │ └── fetch-data.component.ts │ │ │ ├── home │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ └── login.component.ts │ │ │ ├── manage-account │ │ │ │ ├── account-profile.component.css │ │ │ │ ├── account-profile.component.html │ │ │ │ ├── account-profile.component.ts │ │ │ │ ├── manage-account.component.html │ │ │ │ ├── manage-account.component.ts │ │ │ │ ├── reset-authenticator.component.css │ │ │ │ ├── reset-authenticator.component.html │ │ │ │ ├── reset-authenticator.component.ts │ │ │ │ ├── setup-authenticator.component.css │ │ │ │ ├── setup-authenticator.component.html │ │ │ │ └── setup-authenticator.component.ts │ │ │ ├── navmenu │ │ │ │ ├── navmenu.component.css │ │ │ │ ├── navmenu.component.html │ │ │ │ └── navmenu.component.ts │ │ │ ├── password │ │ │ │ ├── password.component.css │ │ │ │ ├── password.component.html │ │ │ │ └── password.component.ts │ │ │ ├── register │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.html │ │ │ │ └── register.component.ts │ │ │ ├── socialapi │ │ │ │ ├── share.component.css │ │ │ │ ├── share.component.html │ │ │ │ └── share.component.ts │ │ │ └── streaming │ │ │ │ ├── add │ │ │ │ ├── add-video.component.css │ │ │ │ ├── add-video.component.html │ │ │ │ └── add-video.component.ts │ │ │ │ ├── register │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.html │ │ │ │ └── register.component.ts │ │ │ │ ├── streaming.component.css │ │ │ │ ├── streaming.component.html │ │ │ │ └── streaming.component.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── index.html │ │ ├── karma.conf.js │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ ├── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.server.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── tsconfig.json │ └── tslint.json ├── Controllers │ ├── AccountController.cs │ ├── ClientAppSettings.cs │ ├── HomeController.cs │ ├── ManageController.cs │ ├── SocialAccountController.cs │ ├── StreamingController.cs │ ├── TwoFactorAuthenticationController.cs │ └── WeatherForecastController.cs ├── Infrastructure │ ├── AppUserManager.cs │ ├── AppUserStore.cs │ ├── AuthMessageSenderOptions.cs │ ├── DbInitializer.cs │ ├── EmailSender.cs │ ├── ExternalProvidersRegistrations.cs │ ├── Identity.Internals │ │ ├── Base32.cs │ │ └── Rfc6238AuthenticationService.cs │ ├── StreaminCategoryAuthorizeAttribute.cs │ ├── StreamingCategoryAuthorizationHandler.cs │ ├── StreamingCategoryPolicyProvider.cs │ ├── StreamingCategoryRequirement.cs │ ├── UserCategoryAuthorizationHandler.cs │ ├── UserRepository.cs │ └── VideoRepository.cs ├── Models │ └── AppUser.cs ├── Pages │ └── _ViewImports.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── ViewModels │ ├── AccountDetailsVM.cs │ ├── AssociateViewModel.cs │ ├── AuthenticatorDetailsVM.cs │ ├── ClaimVM.cs │ ├── LoginVM.cs │ ├── RegisterVM.cs │ ├── ResultVM.cs │ ├── TwoFactorLoginVM.cs │ ├── TwoFactorRecoveryCodeLoginVM.cs │ ├── UpdatePasswordVM.cs │ ├── UserStateVM.cs │ ├── VefiryAuthenticatorVM.cs │ └── VideoVM.cs ├── WeatherForecast.cs ├── appsettings.Development.json ├── appsettings.json ├── libman.json └── wwwroot │ ├── content │ ├── ng2-toastr.min.css │ └── ng2-toastr.min.js │ ├── css │ └── styles.css │ ├── favicon.ico │ ├── images │ ├── 403-forbidden.png │ ├── dropbox.png │ ├── facebook.png │ ├── github.png │ ├── google.png │ ├── linkedin.png │ ├── microsoft.png │ └── twitter.png │ └── js │ ├── oidc-client.js │ └── qrcode.min.js ├── IdentityServer ├── Config.cs ├── Controllers │ ├── AccountController.cs │ ├── ConsentController.cs │ ├── GrantsController.cs │ └── HomeController.cs ├── Data │ ├── ApplicationDbContext.cs │ └── instructions.md ├── DatabaseInitializer.cs ├── Extensions │ └── Extensions.cs ├── Filters │ └── SecurityHeadersAttribute.cs ├── IdentityServer.csproj ├── Models │ ├── Account │ │ ├── AccountOptions.cs │ │ ├── ExternalProvider.cs │ │ ├── LoggedOutViewModel.cs │ │ ├── LoginInputModel.cs │ │ ├── LoginViewModel.cs │ │ ├── LogoutInputModel.cs │ │ ├── LogoutViewModel.cs │ │ ├── RedirectViewModel.cs │ │ └── RegisterVM.cs │ ├── Consent │ │ ├── ConsentInputModel.cs │ │ ├── ConsentOptions.cs │ │ ├── ConsentViewModel.cs │ │ ├── ProcessConsentResult.cs │ │ └── ScopeViewModel.cs │ ├── ErrorViewModel.cs │ └── Grants │ │ ├── GrantViewModel.cs │ │ └── GrantsViewModel.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── Views │ ├── Account │ │ ├── LoggedOut.cshtml │ │ ├── Login.cshtml │ │ ├── Logout.cshtml │ │ └── Register.cshtml │ ├── Consent │ │ └── Index.cshtml │ ├── Grants │ │ └── Index.cshtml │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── Redirect.cshtml │ │ ├── _Layout.cshtml │ │ ├── _ScopeListItem.cshtml │ │ └── _ValidationSummary.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json ├── tempkey.rsa └── wwwroot │ ├── css │ ├── site.css │ ├── site.less │ └── site.min.css │ ├── favicon.ico │ ├── icon.jpg │ ├── icon.png │ ├── js │ ├── signin-redirect.js │ └── signout-redirect.js │ └── lib │ ├── bootstrap │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js │ └── jquery │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── LICENSE ├── README.md ├── SocialNetwork.API ├── Controllers │ └── ContactsController.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── SocialNetwork.API.csproj ├── Startup.cs ├── appsettings.Development.json └── appsettings.json └── appveyor.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /AspNetCoreIdentity.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.2015 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNetCoreIdentity", "AspNetCoreIdentity\AspNetCoreIdentity.csproj", "{1F8BFE1B-F23D-4E93-B12C-393976AF5371}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{5178703E-2DE1-4543-B220-CEFECB113CBE}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocialNetwork.API", "SocialNetwork.API\SocialNetwork.API.csproj", "{BF77D4DE-700A-4CFD-B902-C0B4DA2C83B5}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1F8BFE1B-F23D-4E93-B12C-393976AF5371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {1F8BFE1B-F23D-4E93-B12C-393976AF5371}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {1F8BFE1B-F23D-4E93-B12C-393976AF5371}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {1F8BFE1B-F23D-4E93-B12C-393976AF5371}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {5178703E-2DE1-4543-B220-CEFECB113CBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {5178703E-2DE1-4543-B220-CEFECB113CBE}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {5178703E-2DE1-4543-B220-CEFECB113CBE}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {5178703E-2DE1-4543-B220-CEFECB113CBE}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {BF77D4DE-700A-4CFD-B902-C0B4DA2C83B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BF77D4DE-700A-4CFD-B902-C0B4DA2C83B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BF77D4DE-700A-4CFD-B902-C0B4DA2C83B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BF77D4DE-700A-4CFD-B902-C0B4DA2C83B5}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {8F217B3F-B5C6-4409-867D-31CE31CEDEBF} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/AspNetCoreIdentity.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/AspNetCoreIdentity.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # AspNetCoreIdentity 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.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 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/e2e/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: ["./src/**/*.e2e-spec.ts"], 9 | capabilities: { 10 | browserName: "chrome" 11 | }, 12 | directConnect: true, 13 | baseUrl: "http://localhost:4200/", 14 | framework: "jasmine", 15 | jasmineNodeOpts: { 16 | showColors: true, 17 | defaultTimeoutInterval: 30000, 18 | print: function() {} 19 | }, 20 | onPrepare() { 21 | require("ts-node").register({ 22 | project: require("path").join(__dirname, "./tsconfig.e2e.json") 23 | }); 24 | jasmine 25 | .getEnv() 26 | .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('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.getMainHeading()).toEqual('Hello, world!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getMainHeading() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AspNetCoreIdentity", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "build:ssr": "ng run AspNetCoreIdentity:server:dev", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "8.0.0", 16 | "@angular/common": "8.0.0", 17 | "@angular/compiler": "8.0.0", 18 | "@angular/core": "8.0.0", 19 | "@angular/forms": "8.0.0", 20 | "@angular/platform-browser": "8.0.0", 21 | "@angular/platform-browser-dynamic": "8.0.0", 22 | "@angular/platform-server": "8.0.0", 23 | "@angular/router": "8.0.0", 24 | "@nguniversal/module-map-ngfactory-loader": "8.0.0-rc.1", 25 | "aspnet-prerendering": "^3.0.1", 26 | "bootstrap": "3.4.1", 27 | "core-js": "^2.6.5", 28 | "jquery": "3.4.1", 29 | "oidc-client": "^1.9.0", 30 | "popper.js": "^1.14.3", 31 | "rxjs": "^6.4.0", 32 | "zone.js": "~0.9.1" 33 | }, 34 | "devDependencies": { 35 | "@angular-devkit/build-angular": "^0.800.6", 36 | "@angular/cli": "8.0.6", 37 | "@angular/compiler-cli": "8.0.0", 38 | "@angular/language-service": "8.0.0", 39 | "@types/jasmine": "~3.3.9", 40 | "@types/jasminewd2": "~2.0.6", 41 | "@types/node": "~11.10.5", 42 | "codelyzer": "^5.0.1", 43 | "jasmine-core": "~3.3.0", 44 | "jasmine-spec-reporter": "~4.2.1", 45 | "karma": "^4.0.0", 46 | "karma-chrome-launcher": "~2.2.0", 47 | "karma-coverage-istanbul-reporter": "~2.0.5", 48 | "karma-jasmine": "~2.0.1", 49 | "karma-jasmine-html-reporter": "^1.4.0", 50 | "typescript": "3.4.5" 51 | }, 52 | "optionalDependencies": { 53 | "node-sass": "^4.9.3", 54 | "protractor": "~5.4.0", 55 | "ts-node": "~5.0.1", 56 | "tslint": "~5.9.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/admin/admin.component.css: -------------------------------------------------------------------------------- 1 | .manage-users { 2 | padding: 50px 20px; 3 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/admin/admin.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Manage View 4 | 5 | 6 | {{users.length}} total users 7 | 8 | 9 |

10 |
11 |

Only administrators can access this view

12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
UsernameEmailEmail confirmed2 FA enabledLockout Enabled
{{user.userName}}{{user.email}}{{user.emailConfirmed}}{{user.twoFactorEnabled}}{{user.lockoutEnabled}}
41 |
42 |
43 |
-------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { UserVM } from '../core/domain'; 4 | 5 | @Component({ 6 | selector: 'admin', 7 | templateUrl: './admin.component.html', 8 | styleUrls: ['./admin.component.css'] 9 | }) 10 | export class AdminComponent { 11 | public users: UserVM[] = []; 12 | 13 | constructor(public http: HttpClient, @Inject('BASE_URL') public baseUrl: string) { 14 | this.http.get(this.baseUrl + 'api/manage/users').subscribe(result => { 15 | this.users = result; 16 | console.log(this.users); 17 | }, error => console.error(error)); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ 3 | .body-content { 4 | padding-top: 50px; 5 | } 6 | } 7 | 8 | .navbar-default { 9 | box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1); 10 | border-bottom: 1px solid #d4dadf; 11 | background-color: #FFFFFF; 12 | } 13 | 14 | .navbar-default .navbar-nav > li > a { 15 | color: #3884FE !important; 16 | font-size: 16px; 17 | font-family: Content-font, Roboto, sans-serif; 18 | font-weight: 600; 19 | } 20 | 21 | .container-fluid.content { 22 | margin-top: 50px; 23 | } 24 | 25 | @media (max-width: 768px) { 26 | .navbar-default { 27 | display: none; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { StateService } from './core/state.service'; 5 | import { Notification } from './core/domain'; 6 | import { OpenIdConnectService } from './core/openid-connect.service'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'] 12 | }) 13 | export class AppComponent { 14 | 15 | private notification: Notification = { message: '', type: '' }; 16 | 17 | constructor(public http: HttpClient, stateService: StateService, router: Router, 18 | @Inject('BASE_URL') public baseUrl: string, openConnectIdService: OpenIdConnectService) { 19 | this.http.get(this.baseUrl + 'api/account/authenticated').subscribe(result => { 20 | var state = result; 21 | console.log(state); 22 | stateService.setAuthentication(state); 23 | stateService.setDisplayPassword(state.displaySetPassword); 24 | }, error => console.error(error)); 25 | 26 | let code = this.getUrlParameter("code"); 27 | if (code) { 28 | openConnectIdService.signinRedirectCallback().then(() => { 29 | router.navigate(['/share']); 30 | }).catch((error: any) => { 31 | console.error(error); 32 | }); 33 | } 34 | 35 | let message = this.getUrlParameter("message"); 36 | if (message) { 37 | let type = this.getUrlParameter("type"); 38 | if (!type) { 39 | type = "success"; 40 | } 41 | 42 | this.notification.message = message; 43 | this.notification.type = type; 44 | stateService.displayNotification(this.notification); 45 | console.log(message, type); 46 | if (type === "success") { 47 | router.navigate((['/'])); 48 | } 49 | } 50 | } 51 | 52 | private getUrlParameter(name: string) { 53 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 54 | const regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); 55 | const results = regex.exec(location.search); 56 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { NavMenuComponent } from './navmenu/navmenu.component'; 9 | import { HomeComponent } from './home/home.component'; 10 | import { ClaimsComponent } from './claims/claims.component'; 11 | import { LoginComponent } from './login/login.component'; 12 | import { RegisterComponent } from './register/register.component'; 13 | import { ManageAccountComponent } from './manage-account/manage-account.component'; 14 | import { AccessForbiddenComponent } from './claims/access-forbidden/access-forbidden.component'; 15 | import { AdminComponent } from './admin/admin.component'; 16 | import { AccountProfileComponent } from './manage-account/account-profile.component'; 17 | import { SetupAuthenticatorComponent } from './manage-account/setup-authenticator.component'; 18 | import { ResetAuthenticatorComponent } from './manage-account/reset-authenticator.component'; 19 | import { StreamingComponent } from './streaming/streaming.component'; 20 | import { StreamingRegisterComponent } from './streaming/register/register.component'; 21 | import { AddVideoComponent } from './streaming/add/add-video.component'; 22 | import { CounterComponent } from './counter/counter.component'; 23 | import { FetchDataComponent } from './fetch-data/fetch-data.component'; 24 | 25 | import { httpInterceptorProviders } from './core/http-interceptors/index'; 26 | import { StateService } from './core/state.service'; 27 | import { OpenIdConnectService } from './core/openid-connect.service'; 28 | import { PasswordComponent } from './password/password.component'; 29 | import { SocialApiShareComponent } from './socialapi/share.component'; 30 | 31 | 32 | @NgModule({ 33 | declarations: [ 34 | AppComponent, 35 | ClaimsComponent, 36 | LoginComponent, 37 | RegisterComponent, 38 | AccessForbiddenComponent, 39 | AdminComponent, 40 | ManageAccountComponent, 41 | AccountProfileComponent, 42 | SetupAuthenticatorComponent, 43 | ResetAuthenticatorComponent, 44 | NavMenuComponent, 45 | HomeComponent, 46 | StreamingComponent, 47 | StreamingRegisterComponent, 48 | AddVideoComponent, 49 | SocialApiShareComponent, 50 | PasswordComponent, 51 | CounterComponent, 52 | FetchDataComponent 53 | ], 54 | imports: [ 55 | BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), 56 | HttpClientModule, 57 | FormsModule, 58 | RouterModule.forRoot([ 59 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 60 | { path: 'home', component: HomeComponent }, 61 | { path: 'login', component: LoginComponent }, 62 | { path: 'register', component: RegisterComponent }, 63 | { path: 'access-forbidden', component: AccessForbiddenComponent }, 64 | { path: 'manage/users', component: AdminComponent }, 65 | { path: 'password', component: PasswordComponent }, 66 | { path: 'share', component: SocialApiShareComponent }, 67 | { path: 'claims', component: ClaimsComponent }, 68 | { path: 'videos/:id', component: StreamingComponent }, 69 | { path: 'videos', component: StreamingComponent }, 70 | { path: 'streaming/register', component: StreamingRegisterComponent }, 71 | { path: 'streaming/videos/add', component: AddVideoComponent }, 72 | { path: 'manage/account', component: ManageAccountComponent }, 73 | { path: 'counter', component: CounterComponent }, 74 | { path: 'fetch-data', component: FetchDataComponent }, 75 | { path: '**', redirectTo: 'home' } 76 | ]) 77 | ], 78 | providers: [ 79 | httpInterceptorProviders, 80 | StateService, 81 | OpenIdConnectService 82 | ], 83 | bootstrap: [AppComponent] 84 | }) 85 | export class AppModule { } 86 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/app.server.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; 4 | import { AppComponent } from './app.component'; 5 | import { AppModule } from './app.module'; 6 | 7 | @NgModule({ 8 | imports: [AppModule, ServerModule, ModuleMapLoaderModule], 9 | bootstrap: [AppComponent] 10 | }) 11 | export class AppServerModule { } 12 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/access-forbidden/access-forbidden.component.css: -------------------------------------------------------------------------------- 1 | .center {text-align: center; margin-left: auto; margin-right: auto; margin-bottom: auto; margin-top: auto;} 2 | 3 | .forbidden { 4 | padding-bottom: 70px; 5 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/access-forbidden/access-forbidden.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Access Forbidden 5 | 6 | Error 403 7 | 8 |

9 |
10 |

You are not authorized to access this resource

11 |
12 | Access Forbidden 13 |
14 | 15 |
16 |
-------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/access-forbidden/access-forbidden.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'access-forbidden', 5 | templateUrl: './access-forbidden.component.html', 6 | styleUrls: ['./access-forbidden.component.css'] 7 | }) 8 | export class AccessForbiddenComponent { 9 | } 10 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/claims.component.css: -------------------------------------------------------------------------------- 1 | .claims { 2 | word-wrap: break-word; 3 | } 4 | 5 | .claimType { 6 | color: rgb(56, 132, 254); 7 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/claims.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

User account claims

5 |
6 |
7 |
8 |
9 |
10 | {{userName}} 11 |
12 |
13 |
14 |
15 |
16 |
17 | Type: 18 | {{claim.type}} 19 |
Value: 20 | {{claim.value}} 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
-------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/claims/claims.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { ClaimsVM, UserClaims } from '../core/domain'; 4 | 5 | @Component({ 6 | selector: 'claims', 7 | templateUrl: './claims.component.html', 8 | styleUrls: ['./claims.component.css'] 9 | }) 10 | export class ClaimsComponent { 11 | public claims: ClaimsVM[] = []; 12 | public userName: string = ''; 13 | 14 | constructor(public http: HttpClient, 15 | @Inject('BASE_URL') public baseUrl: string) { 16 | this.http.get(this.baseUrl + 'api/account/claims').subscribe(result => { 17 | var claimsResult = result; 18 | this.claims = claimsResult.claims; 19 | this.userName = claimsResult.userName; 20 | }, error => console.error(error)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/core/domain.ts: -------------------------------------------------------------------------------- 1 | export interface UserVM { 2 | id: string; 3 | email: string; 4 | emailConfirmed: boolean; 5 | userName: string; 6 | lockoutEnabled: boolean; 7 | twoFactorEnabled: boolean; 8 | } 9 | 10 | export interface ClaimsVM { 11 | type: string; 12 | value: string; 13 | } 14 | 15 | export interface UserClaims { 16 | claims: ClaimsVM[]; 17 | userName: string; 18 | } 19 | 20 | export interface LoginVM { 21 | username: string; 22 | password: string; 23 | } 24 | 25 | export interface RegisterVM { 26 | userName: string; 27 | email: string; 28 | password: string; 29 | confirmPassword: string; 30 | startFreeTrial: boolean; 31 | isAdmin: boolean; 32 | } 33 | 34 | export interface IContact { 35 | id: string; 36 | name: string; 37 | username: string; 38 | email: string; 39 | } 40 | 41 | export interface StreamingCategoryVM { 42 | category: string; 43 | value: number; 44 | registered: boolean; 45 | } 46 | 47 | export interface VideoVM { 48 | url: string; 49 | title: string; 50 | description: string; 51 | category: string; 52 | } 53 | 54 | export interface UserState { 55 | username: string; 56 | isAuthenticated: boolean; 57 | authenticationMethod: string; 58 | } 59 | 60 | export interface Notification { 61 | message: string; 62 | type: string; 63 | } 64 | 65 | export interface AccountDetailsVM { 66 | username: string; 67 | email: string; 68 | emailConfirmed: boolean; 69 | phoneNumber: string; 70 | externalLogins: string[]; 71 | twoFactorEnabled: boolean; 72 | hasAuthenticator: boolean; 73 | twoFactorClientRemembered: boolean; 74 | recoveryCodesLeft: number[]; 75 | } 76 | 77 | export interface AuthenticatorDetailsVM { 78 | sharedKey: string; 79 | authenticatorUri: string; 80 | } 81 | 82 | export interface ResultVM { 83 | status: StatusEnum; 84 | message: string; 85 | data: any; 86 | } 87 | 88 | export enum StatusEnum { 89 | Success = 1, 90 | Error = 2 91 | } 92 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/core/http-interceptors/auth-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | import { Router } from '@angular/router'; 6 | import { StateService } from '../state.service'; 7 | 8 | @Injectable() 9 | export class AuthInterceptor implements HttpInterceptor { 10 | constructor(public stateService: StateService, public router: Router) { } 11 | 12 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 13 | return next.handle(request).pipe(catchError(err => { 14 | console.log(this.router.url); 15 | if (this.router.url == "/share") 16 | return Observable.throw(err); 17 | 18 | if ((err.status === 401) && (window.location.href.match(/\?/g) || []).length < 2) { 19 | this.stateService.setAuthentication({ username: '', isAuthenticated: false, authenticationMethod: '' }); 20 | this.router.navigate(['/login']); 21 | } 22 | else if ((err.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) { 23 | this.router.navigate(['/access-forbidden']); 24 | } 25 | 26 | const error = err.error.message || err.statusText; 27 | return throwError(error); 28 | })) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/core/http-interceptors/index.ts: -------------------------------------------------------------------------------- 1 | /* "Barrel" of Http Interceptors */ 2 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 3 | 4 | import { AuthInterceptor } from './auth-interceptor'; 5 | 6 | 7 | /** Http interceptor providers in outside-in order */ 8 | export const httpInterceptorProviders = [ 9 | { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } 10 | ]; 11 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/core/openid-connect.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | declare var Oidc : any; 4 | 5 | @Injectable() 6 | export class OpenIdConnectService { 7 | 8 | config = { 9 | authority: "http://localhost:5005", 10 | client_id: "AspNetCoreIdentity", 11 | redirect_uri: "http://localhost:5000", 12 | response_type: "code", 13 | scope: "openid profile SocialAPI", 14 | post_logout_redirect_uri: "http://localhost:5000", 15 | }; 16 | userManager : any; 17 | 18 | constructor() { 19 | this.userManager = new Oidc.UserManager(this.config); 20 | } 21 | 22 | public getUser() { 23 | return this.userManager.getUser(); 24 | } 25 | 26 | public login() { 27 | return this.userManager.signinRedirect();; 28 | } 29 | 30 | public signinRedirectCallback() { 31 | return new Oidc.UserManager({ response_mode: "query" }).signinRedirectCallback(); 32 | } 33 | 34 | public logout() { 35 | this.userManager.signoutRedirect(); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/core/state.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { UserState, Notification } from './domain'; 3 | 4 | @Injectable() 5 | export class StateService { 6 | 7 | public userState: UserState = { username: '', isAuthenticated: false, authenticationMethod: '' }; 8 | public notification: Notification = { message: '', type: '' }; 9 | public displaySetPassword: boolean = false; 10 | 11 | /** 12 | * setAuthentication 13 | */ 14 | public setAuthentication(state: UserState) { 15 | this.userState = state; 16 | } 17 | 18 | public displayNotification(notify: Notification) { 19 | this.notification.message = notify.message; 20 | this.notification.type = notify.type; 21 | 22 | setTimeout(() => { 23 | this.notification.message = ''; 24 | this.notification.type = ''; 25 | }, 8000); 26 | } 27 | 28 | public setDisplayPassword(display: boolean) { 29 | this.displaySetPassword = display; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/counter/counter.component.html: -------------------------------------------------------------------------------- 1 |

Counter

2 | 3 |

This is a simple example of an Angular component.

4 | 5 |

Current count: {{ currentCount }}

6 | 7 | 8 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/counter/counter.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CounterComponent } from './counter.component'; 4 | 5 | describe('CounterComponent', () => { 6 | let component: CounterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CounterComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CounterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should display a title', async(() => { 23 | const titleText = fixture.nativeElement.querySelector('h1').textContent; 24 | expect(titleText).toEqual('Counter'); 25 | })); 26 | 27 | it('should start with count 0, then increments by 1 when clicked', async(() => { 28 | const countElement = fixture.nativeElement.querySelector('strong'); 29 | expect(countElement.textContent).toEqual('0'); 30 | 31 | const incrementButton = fixture.nativeElement.querySelector('button'); 32 | incrementButton.click(); 33 | fixture.detectChanges(); 34 | expect(countElement.textContent).toEqual('1'); 35 | })); 36 | }); 37 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/counter/counter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-counter-component', 5 | templateUrl: './counter.component.html' 6 | }) 7 | export class CounterComponent { 8 | public currentCount = 0; 9 | 10 | public incrementCounter() { 11 | this.currentCount++; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/fetch-data/fetch-data.component.html: -------------------------------------------------------------------------------- 1 |

Weather forecast

2 | 3 |

This component demonstrates fetching data from the server.

4 | 5 |

Loading...

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
DateTemp. (C)Temp. (F)Summary
{{ forecast.date }}{{ forecast.temperatureC }}{{ forecast.temperatureF }}{{ forecast.summary }}
25 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/fetch-data/fetch-data.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | @Component({ 5 | selector: 'app-fetch-data', 6 | templateUrl: './fetch-data.component.html' 7 | }) 8 | export class FetchDataComponent { 9 | public forecasts: WeatherForecast[]; 10 | 11 | constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) { 12 | http.get(baseUrl + 'weatherforecast').subscribe(result => { 13 | this.forecasts = result; 14 | }, error => console.error(error)); 15 | } 16 | } 17 | 18 | interface WeatherForecast { 19 | date: string; 20 | temperatureC: number; 21 | temperatureF: number; 22 | summary: string; 23 | } 24 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | }) 7 | export class HomeComponent { 8 | } 9 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .login-form { 2 | padding-top: 90px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 59 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Router } from '@angular/router'; 4 | import { StateService } from '../core/state.service'; 5 | import { LoginVM, ResultVM, StatusEnum } from '../core/domain'; 6 | 7 | @Component({ 8 | selector: 'login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.css'] 11 | }) 12 | export class LoginComponent implements OnInit { 13 | public user: LoginVM = { username: '', password: '' } 14 | public errors: string = ''; 15 | public socialProviders: string[] = []; 16 | public requires2FA: boolean = false; 17 | public twoFaCode: string = ''; 18 | public useRecoveryCode: boolean = false; 19 | public rememberMachine: boolean = false; 20 | 21 | constructor(public http: HttpClient, 22 | @Inject('BASE_URL') public baseUrl: string, 23 | public router: Router, public stateService: StateService) { 24 | } 25 | 26 | ngOnInit() { 27 | this.http.get(this.baseUrl + 'socialaccount/providers') 28 | .subscribe(result => { 29 | this.socialProviders = result; 30 | console.log(this.socialProviders); 31 | }); 32 | } 33 | 34 | onKeydown(event: any) { 35 | if (event.key === "Enter") { 36 | this.login(); 37 | } 38 | } 39 | 40 | twoFaPlaceholder() { 41 | return this.useRecoveryCode ? 'Enter recovery code' : '6-digit code'; 42 | } 43 | 44 | login() { 45 | this.errors = ''; 46 | console.log(this.user); 47 | 48 | if (this.requires2FA) { 49 | 50 | let data = {}; 51 | let uri = ''; 52 | 53 | if (this.useRecoveryCode) { 54 | data = { 55 | recoveryCode: this.twoFaCode 56 | }; 57 | uri = 'api/twoFactorAuthentication/loginWithRecovery'; 58 | } else { 59 | data = { 60 | twoFactorCode: this.twoFaCode, 61 | rememberMachine: this.rememberMachine 62 | }; 63 | uri = 'api/twoFactorAuthentication/login'; 64 | } 65 | 66 | this.http.post(this.baseUrl + uri, data).subscribe(result => { 67 | let loginResult = result; 68 | if (loginResult.status === StatusEnum.Success) { 69 | this.stateService.setAuthentication({ 70 | isAuthenticated: true, 71 | username: this.user.username, 72 | authenticationMethod: '' 73 | }); 74 | this.router.navigate(['/home']); 75 | } else if (loginResult.status === StatusEnum.Error) { 76 | this.errors = loginResult.data.toString(); 77 | } 78 | 79 | }, 80 | error => console.error(error)); 81 | } else { 82 | this.http.post(this.baseUrl + 'api/account/login', this.user).subscribe(result => { 83 | let loginResult = result; 84 | if (loginResult.status === StatusEnum.Success) { 85 | if (loginResult.data.requires2FA) { 86 | this.requires2FA = true; 87 | this.stateService.displayNotification({ message: loginResult.message, type: "success" }); 88 | return; 89 | } 90 | this.stateService.setAuthentication({ 91 | isAuthenticated: true, 92 | username: this.user.username, 93 | authenticationMethod: '' 94 | }); 95 | this.router.navigate(['/home']); 96 | } else if (loginResult.status === StatusEnum.Error) { 97 | this.errors = loginResult.data.toString(); 98 | } 99 | 100 | }, 101 | error => console.error(error)); 102 | } 103 | } 104 | } 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/account-profile.component.css: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/account-profile.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
Basic info
UsernameEmail2FA EnabledHas Authenticator2FA Remember
{{accountDetails.username}}{{accountDetails.email}}{{accountDetails.twoFactorEnabled}}{{accountDetails.hasAuthenticator}}{{accountDetails.twoFactorClientRemembered}}
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 |
External providers
#Provider
{{i+1}}{{provider}} 40 | 41 |
45 |
-------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/account-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { AccountDetailsVM } from '../core/domain'; 3 | 4 | @Component({ 5 | selector: 'account-profile', 6 | templateUrl: './account-profile.component.html', 7 | styleUrls: ['./account-profile.component.css'] 8 | }) 9 | export class AccountProfileComponent { 10 | 11 | @Input() accountDetails: AccountDetailsVM; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/manage-account.component.html: -------------------------------------------------------------------------------- 1 | 
2 |
3 | 6 |
7 |
8 | 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/manage-account.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { StateService } from '../core/state.service'; 4 | import { AccountDetailsVM } from '../core/domain'; 5 | 6 | @Component({ 7 | selector: 'manage-account', 8 | templateUrl: './manage-account.component.html', 9 | }) 10 | export class ManageAccountComponent { 11 | 12 | public accountDetails: AccountDetailsVM = {}; 13 | 14 | constructor(public http: HttpClient, @Inject('BASE_URL') public baseUrl: string, 15 | public stateService: StateService) { 16 | this.http.get(this.baseUrl + 'api/twoFactorAuthentication/details').subscribe(result => { 17 | this.accountDetails = result; 18 | console.log(this.accountDetails); 19 | }, error => console.error(error)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/reset-authenticator.component.css: -------------------------------------------------------------------------------- 1 | #genQrCode { 2 | margin: 20px auto; 3 | padding: 20px; 4 | text-align: center; 5 | background: #ffffff; 6 | } 7 | 8 | .validVerificationCode { 9 | margin-right: 5px; 10 | color: #3884FE; 11 | cursor: pointer; 12 | font-weight: 600; 13 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/reset-authenticator.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 12 | 13 |
14 |
15 | 25 |
-------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/reset-authenticator.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, Input } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { StateService } from '../core/state.service'; 4 | import { ResultVM, StatusEnum, AccountDetailsVM } from '../core/domain'; 5 | 6 | @Component({ 7 | selector: 'reset-authenticator', 8 | templateUrl: './reset-authenticator.component.html', 9 | styleUrls: ['./reset-authenticator.component.css'] 10 | }) 11 | export class ResetAuthenticatorComponent { 12 | 13 | @Input() accountDetails: AccountDetailsVM; 14 | public authenticatorNeedsSetup: boolean = false; 15 | 16 | constructor(public http: HttpClient, @Inject('BASE_URL') public baseUrl: string, 17 | public stateService: StateService) { 18 | } 19 | 20 | resetAuthenticator() { 21 | this.http.post(this.baseUrl + 'api/twoFactorAuthentication/resetAuthenticator', {}).subscribe(result => { 22 | 23 | let resetAuthenticatorResult = result; 24 | 25 | if (resetAuthenticatorResult.status === StatusEnum.Success) { 26 | this.stateService.displayNotification({ message: resetAuthenticatorResult.message, type: "success" }); 27 | this.accountDetails.twoFactorEnabled = false; 28 | this.authenticatorNeedsSetup = true; 29 | } 30 | }, 31 | error => console.error(error)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/setup-authenticator.component.css: -------------------------------------------------------------------------------- 1 | #genQrCode { 2 | margin: 20px auto; 3 | padding: 20px; 4 | text-align: center; 5 | background: #ffffff; 6 | } 7 | 8 | .authenticatorKey { 9 | text-align: center; 10 | font-size: 20px; 11 | color: #8a6d3b; 12 | } 13 | 14 | .validVerificationCode { 15 | margin-right: 5px; 16 | color: #3884FE; 17 | cursor: pointer; 18 | font-weight: 600; 19 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/manage-account/setup-authenticator.component.html: -------------------------------------------------------------------------------- 1 | 
2 | 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 |

Scan the QR Code or enter the following key into your two factor authenticator app. Spaces and casing do not matter

14 |
{{authenticatorDetails.sharedKey}}
15 |
Generating...
16 |
17 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you with a unique code. Enter the code in the confirmation box below 18 | 19 | 20 |
21 |
22 |
    23 |
    24 |
    25 | 28 |
    29 |
    30 |
    Valid 6-digit verification codes
    31 | {{validVerificationCode}} 32 |
    33 |
    34 |
    35 |
    Copy and save your recovery codes
    36 |
      37 |
    • {{recoveryCode}}
    • 38 |
    39 |
    -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/navmenu/navmenu.component.css: -------------------------------------------------------------------------------- 1 | li .glyphicon { 2 | margin-right: 10px; 3 | } 4 | 5 | .navbar-inverse .navbar-nav > li > a { 6 | color: #5C6975; 7 | } 8 | 9 | /* Highlighting rules for nav menu items */ 10 | .navbar-inverse .navbar-nav > li > a:hover, 11 | li.link-active a, 12 | li.link-active a:hover, 13 | li.link-active a:focus { 14 | background: rgb(230, 236, 241); 15 | } 16 | 17 | /* Keep the nav menu independent of scrolling and on top of other items */ 18 | .main-nav { 19 | position: fixed; 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | z-index: 1; 24 | } 25 | 26 | .logout { 27 | cursor: pointer; 28 | } 29 | 30 | .navbar-inverse { 31 | background: #F5F7F9; 32 | border-right: 1px solid #E6ECF1; 33 | } 34 | 35 | @media (min-width: 768px) { 36 | /* On small screens, convert the nav menu to a vertical sidebar */ 37 | .main-nav { 38 | height: 100%; 39 | width: calc(25% - 20px); 40 | } 41 | 42 | .navbar { 43 | border-radius: 0px; 44 | border-width: 0px; 45 | height: 100%; 46 | } 47 | 48 | .navbar-header { 49 | float: none; 50 | } 51 | 52 | .navbar-collapse { 53 | border-top: 1px solid #444; 54 | padding: 0px; 55 | } 56 | 57 | .navbar ul { 58 | float: none; 59 | } 60 | 61 | .navbar li { 62 | float: none; 63 | font-size: 15px; 64 | margin: 6px; 65 | } 66 | 67 | .navbar li a { 68 | padding: 10px 16px; 69 | border-radius: 4px; 70 | } 71 | 72 | .navbar a { 73 | /* If a menu item's text is too long, truncate it */ 74 | width: 100%; 75 | white-space: nowrap; 76 | overflow: hidden; 77 | text-overflow: ellipsis; 78 | } 79 | } 80 | 81 | @media (max-width: 768px) { 82 | /* On small screens, convert the nav menu to a vertical sidebar */ 83 | .main-nav { 84 | z-index: 5; 85 | } 86 | 87 | .navbar-inverse { 88 | box-shadow: 0 3px 8px 0 rgba(116, 129, 141, 0.1); 89 | border-bottom: 1px solid #d4dadf; 90 | background-color: #FFFFFF; 91 | } 92 | 93 | .navbar-inverse .navbar-toggle { 94 | background-color: #3884FE; 95 | } 96 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/navmenu/navmenu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { StateService } from '../core/state.service'; 3 | import { Router } from '@angular/router'; 4 | import { HttpClient } from '@angular/common/http'; 5 | 6 | @Component({ 7 | selector: 'nav-menu', 8 | templateUrl: './navmenu.component.html', 9 | styleUrls: ['./navmenu.component.css'] 10 | }) 11 | export class NavMenuComponent { 12 | constructor(public stateService: StateService, public router: Router, 13 | public http: HttpClient, 14 | @Inject('BASE_URL') public baseUrl: string) { } 15 | 16 | logout() { 17 | this.http.post(this.baseUrl + 'api/account/signout', {}).subscribe(result => { 18 | this.stateService.setAuthentication({ username: '', isAuthenticated: false, authenticationMethod: '' }); 19 | this.router.navigate(['/home']); 20 | }, error => console.error(error)); 21 | 22 | } 23 | 24 | closeMenu() { 25 | if (window.innerWidth < 768) { 26 | let navButton = document.getElementById('navButton'); 27 | if (navButton) { 28 | navButton.click(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/password/password.component.css: -------------------------------------------------------------------------------- 1 | .password { 2 | padding-top: 90px; 3 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/password/password.component.html: -------------------------------------------------------------------------------- 1 | 
    2 |
    3 |
    4 |
    5 |
    6 | Set password 7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 |
    17 |
    18 | 19 |
    20 |
    21 |
      22 |
      23 |
      24 |
      25 |
      26 |
      27 | 28 |
      29 |
      30 |
      31 |
      32 |
      33 |
      34 |
      35 |
      36 |
      37 |
      -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/password/password.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Router } from '@angular/router'; 4 | import { ResultVM, StatusEnum } from '../core/domain'; 5 | import { StateService } from '../core/state.service'; 6 | 7 | @Component({ 8 | selector: 'password', 9 | templateUrl: './password.component.html', 10 | styleUrls: ['./password.component.css'] 11 | }) 12 | export class PasswordComponent implements OnInit { 13 | 14 | public errors: string = ''; 15 | public password: string = ''; 16 | public confirmPassword: string = ''; 17 | 18 | constructor(public http: HttpClient, 19 | @Inject('BASE_URL') public baseUrl: string, 20 | public router: Router, public stateService: StateService) { 21 | } 22 | 23 | ngOnInit() { 24 | 25 | } 26 | 27 | onKeydown(event: any) { 28 | if (event.key === "Enter") { 29 | this.setPassword(); 30 | } 31 | } 32 | 33 | setPassword() { 34 | this.errors = ''; 35 | 36 | let updatePassword = { 37 | password: this.password, 38 | confirmPassword: this.confirmPassword 39 | }; 40 | 41 | 42 | this.http.post(this.baseUrl + 'api/account/managePassword', updatePassword).subscribe(result => { 43 | let setPasswordResult = result; 44 | 45 | if (setPasswordResult.status === StatusEnum.Success) { 46 | this.stateService.displayNotification({ message: setPasswordResult.message, type: "success" }); 47 | this.stateService.setDisplayPassword(false); 48 | this.router.navigate(['/']); 49 | } else if (setPasswordResult.status === StatusEnum.Error) { 50 | this.errors = setPasswordResult.data.toString(); 51 | } 52 | }, 53 | error => console.error(error)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/register/register.component.css: -------------------------------------------------------------------------------- 1 | .registration-form { 2 | padding-top: 90px; 3 | } 4 | 5 | label.trial { 6 | color: #5C6975; 7 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/socialapi/share.component.css: -------------------------------------------------------------------------------- 1 | .share { 2 | padding-top: 90px; 3 | } 4 | 5 | .message { 6 | padding: 14px; 7 | } 8 | 9 | .contact { 10 | color: #4189C7; 11 | font-size: large; 12 | } 13 | 14 | .grant-error { 15 | color: #d9534f; 16 | } 17 | 18 | .grant-success { 19 | text-align: center; 20 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/socialapi/share.component.html: -------------------------------------------------------------------------------- 1 |  61 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/socialapi/share.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { Router } from '@angular/router'; 4 | import { IContact } from '../core/domain'; 5 | import { StateService } from '../core/state.service'; 6 | import { OpenIdConnectService } from '../core/openid-connect.service'; 7 | 8 | @Component({ 9 | selector: 'social-api-share', 10 | templateUrl: './share.component.html', 11 | styleUrls: ['./share.component.css'] 12 | }) 13 | export class SocialApiShareComponent { 14 | 15 | public socialLoggedIn: any; 16 | public contacts: IContact[] = []; 17 | public socialApiAccessDenied : boolean = false; 18 | 19 | constructor(public http: HttpClient, 20 | public openConnectIdService: OpenIdConnectService, 21 | public router: Router, public stateService: StateService) { 22 | openConnectIdService.getUser().then((user: any) => { 23 | if (user) { 24 | console.log("User logged in", user.profile); 25 | console.log(user); 26 | this.socialLoggedIn = true; 27 | const headers = new HttpHeaders({'Authorization':`Bearer ${user.access_token}`}); 28 | const socialApiContactsURI = "http://localhost:5010/api/contacts"; 29 | 30 | this.http.get(socialApiContactsURI, {headers: headers}).subscribe(result => { 31 | this.contacts = result; 32 | 33 | }, error => { 34 | if (error.status === 401) { 35 | this.socialApiAccessDenied = true; 36 | } 37 | }); 38 | } 39 | 40 | }); 41 | } 42 | 43 | login() { 44 | this.openConnectIdService.login(); 45 | } 46 | 47 | logout() { 48 | this.openConnectIdService.logout(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/add/add-video.component.css: -------------------------------------------------------------------------------- 1 | label.trial { 2 | color: whitesmoke; 3 | } 4 | 5 | select.selectpicker { 6 | width: 100%; 7 | padding-right: 25px; 8 | z-index: 1; 9 | color: #333; 10 | background-color: #fff; 11 | border-color: #ccc; 12 | height: 45px; 13 | border: 1px solid #ddd; 14 | font-size: 16px; 15 | -webkit-transition: all 0.1s linear; 16 | -moz-transition: all 0.1s linear; 17 | transition: all 0.1s linear; 18 | border-color: #ccc; 19 | border-radius: 4px; 20 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/add/add-video.component.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |

      Add YouTube video to registered category

      4 |
      5 |
      6 |
      7 |
      8 |
      9 | Add New Video 10 |
      11 |
      12 |
      13 |
      14 |
      15 |
      16 |
      17 | 19 |
      20 |
      21 | 23 |
      24 |
      25 | 27 |
      28 |
      29 | 32 |
      33 |
      34 |
      35 |
      36 | 38 |
      39 |
      40 |
      41 |
      42 |
      43 |
      44 |
      45 |
      46 |
      47 |
      48 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/add/add-video.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { VideoVM, StreamingCategoryVM } from '../../core/domain'; 4 | import { Router } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'add-video', 8 | templateUrl: './add-video.component.html', 9 | styleUrls: ['./add-video.component.css'] 10 | }) 11 | export class AddVideoComponent { 12 | public categories: StreamingCategoryVM[] = []; 13 | public newVideo: VideoVM = { title: '', category: '', description: '', url: '' }; 14 | 15 | constructor(public http: HttpClient, @Inject('BASE_URL') public baseUrl: string, 16 | private router: Router) { 17 | this.http.get(this.baseUrl + 'api/streaming/videos/register').subscribe(result => { 18 | this.categories = result; 19 | this.newVideo.category = this.categories[0].category; 20 | }, error => console.error(error)); 21 | } 22 | 23 | addVideo() { 24 | let categoryId = this.categories.find(cat => cat.category === this.newVideo.category).value; 25 | console.log(this.newVideo); 26 | var postData = { 27 | title: this.newVideo.title, 28 | category: categoryId, 29 | description: this.newVideo.description, 30 | url: this.newVideo.url 31 | }; 32 | this.http.post(this.baseUrl + 'api/streaming/videos/add', postData).subscribe(result => { 33 | this.router.navigate(['videos', this.newVideo.category]); 34 | }, error => console.error(error)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/register/register.component.css: -------------------------------------------------------------------------------- 1 | 2 | .list-group-item.active { 3 | background-color: #3884FE !important; 4 | border-color: #3884FE !important; 5 | } 6 | 7 | .registered, .list-group-item .registered { 8 | background-color: #f5f5f5; 9 | color: green; 10 | } 11 | 12 | button.save { 13 | text-align: center; 14 | } 15 | 16 | button.add { 17 | text-align: center; 18 | background: #eee !important; 19 | color: #232222; 20 | } 21 | 22 | .streaming-category { 23 | cursor: pointer; 24 | } 25 | 26 | .category-button { 27 | padding: 0px 10px !important; 28 | } 29 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/register/register.component.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |

      Streaming Video Categories

      4 |
      5 | 6 |
      7 | 19 |
      20 |
      -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, ViewContainerRef } from '@angular/core'; 2 | import { HttpClient, HttpRequest, HttpHeaders } from '@angular/common/http'; 3 | import { Router } from '@angular/router'; 4 | import { StreamingCategoryVM } from '../../core/domain'; 5 | import { StateService } from 'src/app/core/state.service'; 6 | 7 | @Component({ 8 | selector: 'streaming-register', 9 | templateUrl: './register.component.html', 10 | styleUrls: ['./register.component.css'] 11 | }) 12 | export class StreamingRegisterComponent { 13 | public categories: StreamingCategoryVM[] = []; 14 | public checkedAll: boolean = false; 15 | public displayVideoForm: boolean = false; 16 | 17 | constructor(public http: HttpClient, @Inject('BASE_URL') public baseUrl: string, 18 | private router: Router, public vcr: ViewContainerRef, 19 | public stateService: StateService) { 20 | this.http.get(this.baseUrl + 'api/streaming/videos/register').subscribe(result => { 21 | this.categories = result; 22 | console.log(this.categories); 23 | }, error => console.error(error)); 24 | 25 | } 26 | 27 | toggleCategories($event: any) { 28 | var check = $event.target.checked; 29 | 30 | this.categories.forEach(c => c.registered = check); 31 | 32 | this.update(); 33 | } 34 | 35 | toggleCategory(category: StreamingCategoryVM) { 36 | category.registered = !category.registered; 37 | 38 | if(!category.registered) { 39 | this.checkedAll = false; 40 | } 41 | 42 | this.update(); 43 | } 44 | 45 | update() { 46 | var categories = this.categories.filter(c => c.registered === true).map(c => c.category); 47 | const headers = new HttpHeaders({'Content-Type':'application/json; charset=utf-8'}); 48 | 49 | this.http.post(this.baseUrl + 'api/streaming/videos/register', 50 | JSON.stringify(categories), { headers: headers }).subscribe(result => { 51 | this.stateService.displayNotification({ message: 'Categories updated', type: "success" }); 52 | }, error => console.error(error)); 53 | } 54 | 55 | viewCategory(event: any, category: string) { 56 | event.stopPropagation(); 57 | this.router.navigate(['/videos', category]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/streaming.component.css: -------------------------------------------------------------------------------- 1 | .video { 2 | padding-bottom: 20px; 3 | padding-top: 10px; 4 | } 5 | 6 | .video-title { 7 | color:whitesmoke; 8 | } 9 | 10 | .video-description { 11 | color: #3884FE; 12 | } 13 | 14 | .video-category { 15 | font-style: italic; 16 | color: brown; 17 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/streaming.component.html: -------------------------------------------------------------------------------- 1 |
      2 |

      Sample videos available in our Streaming Service

      3 | 4 |
      5 |

      Want the full experience? Click 6 | here and register for specific Streaming Categories!

      7 |
      8 |
      9 |

      You are subscribed to the {{category}} of our Streaming Service

      10 | 11 |
      12 |

      {{videos.length}} total videos

      13 |
      14 |
      15 |
      16 |

      {{video.title}}

      17 |
      18 | 19 |
      20 |
      21 |
      -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/app/streaming/streaming.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { DomSanitizer } from '@angular/platform-browser'; 4 | import { ActivatedRoute } from '@angular/router'; 5 | import { VideoVM } from '../core/domain'; 6 | 7 | @Component({ 8 | selector: 'streaming', 9 | templateUrl: './streaming.component.html', 10 | styleUrls: ['./streaming.component.css'] 11 | }) 12 | export class StreamingComponent { 13 | public videos: VideoVM[] = []; 14 | public category: string = ''; 15 | private sub: any; 16 | 17 | constructor(public http: HttpClient, public sanitizer: DomSanitizer, 18 | @Inject('BASE_URL') public baseUrl: string, private route: ActivatedRoute) { } 19 | 20 | ngOnInit() { 21 | this.sub = this.route.params.subscribe(params => { 22 | this.category = params['id'] || ''; 23 | var route = this.category.length === 0 ? 'videos' : this.category 24 | this.http.get(this.baseUrl + `api/streaming/${route}`).subscribe(result => { 25 | this.videos = result; 26 | console.log(this.videos); 27 | }, error => console.error(error)); 28 | }); 29 | } 30 | 31 | ngOnDestroy() { 32 | this.sub.unsubscribe(); 33 | } 34 | 35 | sanitizeUrl(url: string) { 36 | return this.sanitizer.bypassSecurityTrustResourceUrl(url); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/ClientApp/src/assets/.gitkeep -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | chsakell's blog - Identity Series 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Loading... 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/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'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/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 | export function getBaseUrl() { 8 | return document.getElementsByTagName('base')[0].href; 9 | } 10 | 11 | const providers = [ 12 | { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] } 13 | ]; 14 | 15 | if (environment.production) { 16 | enableProdMode(); 17 | } 18 | 19 | platformBrowserDynamic(providers).bootstrapModule(AppModule) 20 | .catch(err => console.log(err)); 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | /* Provide sufficient contrast against white background */ 4 | a { 5 | color: #0366d6; 6 | } 7 | 8 | code { 9 | color: #e01a76; 10 | } 11 | 12 | .btn-primary { 13 | color: #fff; 14 | background-color: #1b6ec2; 15 | border-color: #1861ac; 16 | } 17 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/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/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "src/test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "angularCompilerOptions": { 7 | "entryModule": "app/app.server.module#AppServerModule" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "module": "esnext", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es2015", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2017", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "no-inputs-metadata-property": true, 121 | "no-outputs-metadata-property": true, 122 | "no-host-metadata-property": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-lifecycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Controllers/ClientAppSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace AspNetCoreIdentity.Controllers 4 | { 5 | public class ClientAppSettings : Controller 6 | { 7 | public IActionResult Index() 8 | { 9 | var settings = new 10 | { 11 | stsServer = "http://localhost:5005", 12 | redirect_url = "http://localhost:5000", 13 | client_id = "angularclient", 14 | response_type = "code", 15 | scope = "openid profile SocialAPI", 16 | post_logout_redirect_uri = "http://localhost:5000", 17 | start_checksession = true, 18 | silent_renew = true, 19 | post_login_route = "/home", 20 | forbidden_route = "/forbidden", 21 | unauthorized_route = "/unauthorized", 22 | log_console_warning_active = true, 23 | log_console_debug_active = true, 24 | max_id_token_iat_offset_allowed_in_seconds = 10, 25 | apiServer = "http://localhost:5010" 26 | }; 27 | 28 | return Ok(settings); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using AspNetCoreIdentity.Infrastructure; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | 6 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Controllers/ManageController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 5 | using Microsoft.AspNetCore.Mvc; 6 | 7 | namespace AspNetCoreIdentity.Controllers { 8 | [Route ("api/[controller]/[action]")] 9 | [Authorize(Policy = "AdminOnly")] 10 | public class ManageController : Controller { 11 | private readonly UserManager _userManager; 12 | private readonly RoleManager _roleManager; 13 | private readonly IdentityDbContext _context; 14 | 15 | public ManageController (UserManager userManager, 16 | RoleManager roleManager, IdentityDbContext context) { 17 | _userManager = userManager; 18 | _roleManager = roleManager; 19 | _context = context; 20 | } 21 | 22 | [HttpGet] 23 | [Authorize(Policy = "AdminOnly")] 24 | public async Task Users () { 25 | return Ok(_context.Users); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace AspNetCoreIdentity.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/AuthMessageSenderOptions.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreIdentity.Infrastructure 2 | { 3 | public class AuthMessageSenderOptions 4 | { 5 | public string SendGridUser { get; set; } 6 | public string SendGridKey { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 6 | 7 | namespace AspNetCoreIdentity.Infrastructure { 8 | public class DbInitializer : IDbInitializer { 9 | private readonly IdentityDbContext _context; 10 | private readonly RoleManager _roleManager; 11 | 12 | public DbInitializer ( 13 | IdentityDbContext context, 14 | RoleManager roleManager) { 15 | _context = context; 16 | _roleManager = roleManager; 17 | } 18 | 19 | //This example just creates an Administrator role and one Admin users 20 | public async Task Initialize () { 21 | //create database schema if none exists 22 | _context.Database.EnsureCreated (); 23 | 24 | //If there is already an Administrator role, abort 25 | var adminRoleExists = await _roleManager.RoleExistsAsync("Admin"); 26 | 27 | if (!adminRoleExists) { 28 | //Create the Admin Role 29 | var adminRole = new IdentityRole ("Admin"); 30 | var result = await _roleManager.CreateAsync (adminRole); 31 | 32 | if (result.Succeeded) { 33 | // Add the Trial claim 34 | var foreverTrialClaim = new Claim ("Trial", DateTime.Now.AddYears(1).ToString()); 35 | await _roleManager.AddClaimAsync (adminRole, foreverTrialClaim); 36 | } 37 | } 38 | } 39 | 40 | } 41 | 42 | public interface IDbInitializer { 43 | Task Initialize (); 44 | } 45 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Extensions.Options; 3 | using SendGrid; 4 | using SendGrid.Helpers.Mail; 5 | 6 | namespace AspNetCoreIdentity.Infrastructure 7 | { 8 | public class EmailSender : IEmailSender 9 | { 10 | public EmailSender(IOptions optionsAccessor) 11 | { 12 | Options = optionsAccessor.Value; 13 | } 14 | 15 | public AuthMessageSenderOptions Options { get; } //set only via Secret Manager 16 | 17 | public Task SendEmailAsync(string email, string subject, string message) 18 | { 19 | return Execute(Options.SendGridKey, subject, message, email); 20 | } 21 | 22 | public Task Execute(string apiKey, string subject, string message, string email) 23 | { 24 | var client = new SendGridClient(apiKey); 25 | var msg = new SendGridMessage() 26 | { 27 | From = new EmailAddress("aspnetidentity@chsakell.com", "ASP.NET Core Identity"), 28 | Subject = subject, 29 | PlainTextContent = message, 30 | HtmlContent = message 31 | }; 32 | msg.AddTo(new EmailAddress(email)); 33 | 34 | // Disable click tracking. 35 | // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html 36 | msg.SetClickTracking(false, false); 37 | 38 | return client.SendEmailAsync(msg); 39 | } 40 | } 41 | 42 | public interface IEmailSender 43 | { 44 | Task SendEmailAsync(string email, string subject, string message); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/Identity.Internals/Base32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreIdentity.Infrastructure.Identity.Internals 8 | { 9 | internal static class Base32 10 | { 11 | private static readonly string _base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 12 | 13 | public static string ToBase32(byte[] input) 14 | { 15 | if (input == null) 16 | { 17 | throw new ArgumentNullException(nameof(input)); 18 | } 19 | 20 | StringBuilder sb = new StringBuilder(); 21 | for (int offset = 0; offset < input.Length;) 22 | { 23 | byte a, b, c, d, e, f, g, h; 24 | int numCharsToOutput = GetNextGroup(input, ref offset, out a, out b, out c, out d, out e, out f, out g, out h); 25 | 26 | sb.Append((numCharsToOutput >= 1) ? _base32Chars[a] : '='); 27 | sb.Append((numCharsToOutput >= 2) ? _base32Chars[b] : '='); 28 | sb.Append((numCharsToOutput >= 3) ? _base32Chars[c] : '='); 29 | sb.Append((numCharsToOutput >= 4) ? _base32Chars[d] : '='); 30 | sb.Append((numCharsToOutput >= 5) ? _base32Chars[e] : '='); 31 | sb.Append((numCharsToOutput >= 6) ? _base32Chars[f] : '='); 32 | sb.Append((numCharsToOutput >= 7) ? _base32Chars[g] : '='); 33 | sb.Append((numCharsToOutput >= 8) ? _base32Chars[h] : '='); 34 | } 35 | 36 | return sb.ToString(); 37 | } 38 | 39 | public static byte[] FromBase32(string input) 40 | { 41 | if (input == null) 42 | { 43 | throw new ArgumentNullException(nameof(input)); 44 | } 45 | input = input.TrimEnd('=').ToUpperInvariant(); 46 | if (input.Length == 0) 47 | { 48 | return new byte[0]; 49 | } 50 | 51 | var output = new byte[input.Length * 5 / 8]; 52 | var bitIndex = 0; 53 | var inputIndex = 0; 54 | var outputBits = 0; 55 | var outputIndex = 0; 56 | while (outputIndex < output.Length) 57 | { 58 | var byteIndex = _base32Chars.IndexOf(input[inputIndex]); 59 | if (byteIndex < 0) 60 | { 61 | throw new FormatException(); 62 | } 63 | 64 | var bits = Math.Min(5 - bitIndex, 8 - outputBits); 65 | output[outputIndex] <<= bits; 66 | output[outputIndex] |= (byte)(byteIndex >> (5 - (bitIndex + bits))); 67 | 68 | bitIndex += bits; 69 | if (bitIndex >= 5) 70 | { 71 | inputIndex++; 72 | bitIndex = 0; 73 | } 74 | 75 | outputBits += bits; 76 | if (outputBits >= 8) 77 | { 78 | outputIndex++; 79 | outputBits = 0; 80 | } 81 | } 82 | return output; 83 | } 84 | 85 | // returns the number of bytes that were output 86 | private static int GetNextGroup(byte[] input, ref int offset, out byte a, out byte b, out byte c, out byte d, out byte e, out byte f, out byte g, out byte h) 87 | { 88 | uint b1, b2, b3, b4, b5; 89 | 90 | int retVal; 91 | switch (offset - input.Length) 92 | { 93 | case 1: retVal = 2; break; 94 | case 2: retVal = 4; break; 95 | case 3: retVal = 5; break; 96 | case 4: retVal = 7; break; 97 | default: retVal = 8; break; 98 | } 99 | 100 | b1 = (offset < input.Length) ? input[offset++] : 0U; 101 | b2 = (offset < input.Length) ? input[offset++] : 0U; 102 | b3 = (offset < input.Length) ? input[offset++] : 0U; 103 | b4 = (offset < input.Length) ? input[offset++] : 0U; 104 | b5 = (offset < input.Length) ? input[offset++] : 0U; 105 | 106 | a = (byte)(b1 >> 3); 107 | b = (byte)(((b1 & 0x07) << 2) | (b2 >> 6)); 108 | c = (byte)((b2 >> 1) & 0x1f); 109 | d = (byte)(((b2 & 0x01) << 4) | (b3 >> 4)); 110 | e = (byte)(((b3 & 0x0f) << 1) | (b4 >> 7)); 111 | f = (byte)((b4 >> 2) & 0x1f); 112 | g = (byte)(((b4 & 0x3) << 3) | (b5 >> 5)); 113 | h = (byte)(b5 & 0x1f); 114 | 115 | return retVal; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/Identity.Internals/Rfc6238AuthenticationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace AspNetCoreIdentity.Infrastructure.Identity.Internals 11 | { 12 | internal static class Rfc6238AuthenticationService 13 | { 14 | private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 15 | private static readonly TimeSpan _timestep = TimeSpan.FromMinutes(3); 16 | private static readonly Encoding _encoding = new UTF8Encoding(false, true); 17 | private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); 18 | 19 | // Generates a new 80-bit security token 20 | public static byte[] GenerateRandomKey() 21 | { 22 | byte[] bytes = new byte[20]; 23 | _rng.GetBytes(bytes); 24 | return bytes; 25 | } 26 | 27 | internal static int ComputeTotp(HashAlgorithm hashAlgorithm, ulong timestepNumber, string modifier) 28 | { 29 | // # of 0's = length of pin 30 | const int Mod = 1000000; 31 | 32 | // See https://tools.ietf.org/html/rfc4226 33 | // We can add an optional modifier 34 | var timestepAsBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)timestepNumber)); 35 | var hash = hashAlgorithm.ComputeHash(ApplyModifier(timestepAsBytes, modifier)); 36 | 37 | // Generate DT string 38 | var offset = hash[hash.Length - 1] & 0xf; 39 | Debug.Assert(offset + 4 < hash.Length); 40 | var binaryCode = (hash[offset] & 0x7f) << 24 41 | | (hash[offset + 1] & 0xff) << 16 42 | | (hash[offset + 2] & 0xff) << 8 43 | | (hash[offset + 3] & 0xff); 44 | 45 | return binaryCode % Mod; 46 | } 47 | 48 | private static byte[] ApplyModifier(byte[] input, string modifier) 49 | { 50 | if (String.IsNullOrEmpty(modifier)) 51 | { 52 | return input; 53 | } 54 | 55 | var modifierBytes = _encoding.GetBytes(modifier); 56 | var combined = new byte[checked(input.Length + modifierBytes.Length)]; 57 | Buffer.BlockCopy(input, 0, combined, 0, input.Length); 58 | Buffer.BlockCopy(modifierBytes, 0, combined, input.Length, modifierBytes.Length); 59 | return combined; 60 | } 61 | 62 | // More info: https://tools.ietf.org/html/rfc6238#section-4 63 | private static ulong GetCurrentTimeStepNumber() 64 | { 65 | var delta = DateTime.UtcNow - _unixEpoch; 66 | return (ulong)(delta.Ticks / _timestep.Ticks); 67 | } 68 | 69 | public static int GenerateCode(byte[] securityToken, string modifier = null) 70 | { 71 | if (securityToken == null) 72 | { 73 | throw new ArgumentNullException(nameof(securityToken)); 74 | } 75 | 76 | // Allow a variance of no greater than 9 minutes in either direction 77 | var currentTimeStep = GetCurrentTimeStepNumber(); 78 | using (var hashAlgorithm = new HMACSHA1(securityToken)) 79 | { 80 | return ComputeTotp(hashAlgorithm, currentTimeStep, modifier); 81 | } 82 | } 83 | 84 | public static bool ValidateCode(byte[] securityToken, int code, string modifier = null) 85 | { 86 | if (securityToken == null) 87 | { 88 | throw new ArgumentNullException(nameof(securityToken)); 89 | } 90 | 91 | // Allow a variance of no greater than 9 minutes in either direction 92 | var currentTimeStep = GetCurrentTimeStepNumber(); 93 | using (var hashAlgorithm = new HMACSHA1(securityToken)) 94 | { 95 | for (var i = -2; i <= 2; i++) 96 | { 97 | var computedTotp = ComputeTotp(hashAlgorithm, (ulong)((long)currentTimeStep + i), modifier); 98 | if (computedTotp == code) 99 | { 100 | return true; 101 | } 102 | } 103 | } 104 | 105 | // No match 106 | return false; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/StreaminCategoryAuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authorization; 3 | 4 | namespace AspNetCoreIdentity.Infrastructure { 5 | public class StreamingCategoryAuthorizeAttribute : AuthorizeAttribute { 6 | const string POLICY_PREFIX = "StreamingCategory_"; 7 | 8 | public StreamingCategoryAuthorizeAttribute (StreamingCategory category) => Category = category; 9 | 10 | // Get or set the Category property by manipulating the underlying Policy property 11 | public StreamingCategory Category { 12 | get { 13 | var category = (StreamingCategory) Enum.Parse (typeof (StreamingCategory), 14 | Policy.Substring (POLICY_PREFIX.Length)); 15 | 16 | return (StreamingCategory) category; 17 | } 18 | set { 19 | Policy = $"{POLICY_PREFIX}{value.ToString()}"; 20 | } 21 | } 22 | } 23 | 24 | public enum StreamingCategory { 25 | ACTION_AND_ADVENTURE = 1, 26 | ACTION_COMEDIES = 2, 27 | ACTION_THRILLERS = 3, 28 | SCI_FI = 4, 29 | ANIMATION = 5, 30 | MUSIC_VIDEOS = 6, 31 | BOXING_MOVIES = 7, 32 | FAMILY_MOVIES = 8 33 | } 34 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/StreamingCategoryAuthorizationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Identity; 5 | 6 | namespace AspNetCoreIdentity.Infrastructure { 7 | internal class StreamingCategoryAuthorizationHandler : AuthorizationHandler { 8 | private readonly UserManager _userManager; 9 | 10 | public StreamingCategoryAuthorizationHandler (UserManager userManager) { 11 | _userManager = userManager; 12 | } 13 | 14 | protected override Task HandleRequirementAsync (AuthorizationHandlerContext context, StreamingCategoryRequirement requirement) { 15 | 16 | var loggedInUserTask = _userManager.GetUserAsync (context.User); 17 | 18 | loggedInUserTask.Wait (); 19 | 20 | var userClaimsTask = _userManager.GetClaimsAsync (loggedInUserTask.Result); 21 | 22 | userClaimsTask.Wait (); 23 | 24 | var userClaims = userClaimsTask.Result; 25 | 26 | if (userClaims.Any (c => c.Type == requirement.Category)) { 27 | context.Succeed (requirement); 28 | } 29 | 30 | return Task.CompletedTask; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/StreamingCategoryPolicyProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace AspNetCoreIdentity.Infrastructure { 7 | public class StreamingCategoryPolicyProvider : IAuthorizationPolicyProvider { 8 | const string POLICY_PREFIX = "StreamingCategory_"; 9 | 10 | public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } 11 | 12 | public StreamingCategoryPolicyProvider (IOptions options) { 13 | // ASP.NET Core only uses one authorization policy provider, so if the custom implementation 14 | // doesn't handle all policies (including default policies, etc.) it should fall back to an 15 | // alternate provider. 16 | // 17 | // In this sample, a default authorization policy provider (constructed with options from the 18 | // dependency injection container) is used if this custom provider isn't able to handle a given 19 | // policy name. 20 | // 21 | // If a custom policy provider is able to handle all expected policy names then, of course, this 22 | // fallback pattern is unnecessary. 23 | 24 | // Claims based authorization 25 | options.Value.AddPolicy ("TrialOnly", policy => { 26 | policy.RequireClaim ("Trial"); 27 | }); 28 | 29 | // Role based authorization 30 | options.Value.AddPolicy ("AdminOnly", policy => { 31 | policy.RequireRole ("Admin"); 32 | }); 33 | 34 | options.Value.AddPolicy("AddVideoPolicy", policy => 35 | policy.Requirements.Add(new UserCategoryRequirement())); 36 | 37 | FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider (options); 38 | } 39 | 40 | public Task GetPolicyAsync (string policyName) { 41 | if (policyName.StartsWith (POLICY_PREFIX, StringComparison.OrdinalIgnoreCase)) { 42 | var category = (StreamingCategory) Enum.Parse (typeof (StreamingCategory), 43 | policyName.Substring (POLICY_PREFIX.Length)); 44 | 45 | var policy = new AuthorizationPolicyBuilder (); 46 | policy.AddRequirements(new StreamingCategoryRequirement(category.ToString ())); 47 | return Task.FromResult (policy.Build ()); 48 | } else { 49 | // If the policy name doesn't match the format expected by this policy provider, 50 | // try the fallback provider. If no fallback provider is used, this would return 51 | // Task.FromResult(null) instead. 52 | return FallbackPolicyProvider.GetPolicyAsync (policyName); 53 | } 54 | } 55 | 56 | public Task GetDefaultPolicyAsync () => FallbackPolicyProvider.GetDefaultPolicyAsync (); 57 | 58 | public Task GetFallbackPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/StreamingCategoryRequirement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | 3 | namespace AspNetCoreIdentity.Infrastructure 4 | { 5 | internal class StreamingCategoryRequirement: IAuthorizationRequirement 6 | { 7 | public string Category { get; private set; } 8 | 9 | public StreamingCategoryRequirement(string category) { Category = category; } 10 | } 11 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/UserCategoryAuthorizationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using AspNetCoreIdentity.ViewModels; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace AspNetCoreIdentity.Infrastructure 8 | { 9 | public class UserCategoryAuthorizationHandler : 10 | AuthorizationHandler 11 | { 12 | private readonly UserManager _userManager; 13 | 14 | public UserCategoryAuthorizationHandler (UserManager userManager) { 15 | _userManager = userManager; 16 | } 17 | 18 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 19 | UserCategoryRequirement requirement, 20 | VideoVM resource) 21 | { 22 | var loggedInUserTask = _userManager.GetUserAsync (context.User); 23 | 24 | loggedInUserTask.Wait (); 25 | 26 | var userClaimsTask = _userManager.GetClaimsAsync (loggedInUserTask.Result); 27 | 28 | userClaimsTask.Wait (); 29 | 30 | var userClaims = userClaimsTask.Result; 31 | 32 | if (userClaims.Any (c => c.Type == resource.Category.ToString())) { 33 | context.Succeed (requirement); 34 | } 35 | 36 | return Task.CompletedTask; 37 | } 38 | } 39 | 40 | public class UserCategoryRequirement : IAuthorizationRequirement { } 41 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/Infrastructure/UserRepository.cs: -------------------------------------------------------------------------------- 1 | using AspNetCoreIdentity.Models; 2 | using System.Collections.Generic; 3 | 4 | namespace AspNetCoreIdentity.Infrastructure 5 | { 6 | public static class UserRepository 7 | { 8 | public static List Users; 9 | 10 | static UserRepository() 11 | { 12 | Users = new List(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Models/AppUser.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreIdentity.Models 2 | { 3 | public class AppUser 4 | { 5 | public string Id { get; set; } 6 | public string UserName { get; set; } 7 | public string Email { get; set; } 8 | public string NormalizeUserName { get; set; } 9 | public string PasswordHash { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @namespace AspNetCoreIdentity.Pages 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using AspNetCoreIdentity.Infrastructure; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace AspNetCoreIdentity 13 | { 14 | public class Program 15 | { 16 | public static async Task Main(string[] args) 17 | { 18 | var host = CreateWebHostBuilder(args).Build(); 19 | 20 | using (var scope = host.Services.CreateScope()) 21 | { 22 | var services = scope.ServiceProvider; 23 | var config = services.GetService(); 24 | var dbInitializer = services.GetService(); 25 | var useInMemoryStores = config.GetValue("UseInMemoryStores"); 26 | await dbInitializer.Initialize(); 27 | } 28 | 29 | host.Run(); 30 | } 31 | 32 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 33 | WebHost.CreateDefaultBuilder(args) 34 | .UseStartup(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AspNetCoreIdentity": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:5000/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/AccountDetailsVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace AspNetCoreIdentity.ViewModels 7 | { 8 | public class AccountDetailsVM 9 | { 10 | public string Username { get; set; } 11 | public string Email { get; set; } 12 | public bool EmailConfirmed { get; set; } 13 | public string PhoneNumber { get; set; } 14 | public List ExternalLogins { get; set; } 15 | public bool TwoFactorEnabled { get; set; } 16 | public bool HasAuthenticator { get; set; } 17 | public bool TwoFactorClientRemembered { get; set; } 18 | public int RecoveryCodesLeft { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/AssociateViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreIdentity.ViewModels 2 | { 3 | public class AssociateViewModel 4 | { 5 | public string Username { get; set; } 6 | public string OriginalEmail { get; set; } 7 | public string AssociateEmail { get; set; } 8 | public bool associateExistingAccount { get; set; } 9 | public string LoginProvider { get; set; } 10 | public string ProviderDisplayName { get; set; } 11 | public string ProviderKey { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/AuthenticatorDetailsVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace AspNetCoreIdentity.ViewModels 7 | { 8 | public class AuthenticatorDetailsVM 9 | { 10 | public string SharedKey { get; set; } 11 | 12 | public string AuthenticatorUri { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/ClaimVM.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace AspNetCoreIdentity.ViewModels 4 | { 5 | public class ClaimVM 6 | { 7 | public string Type { get; set; } 8 | public string Value { get; set; } 9 | } 10 | 11 | public class UserClaims 12 | { 13 | public IEnumerable Claims { get; set; } 14 | public string UserName { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/LoginVM.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AspNetCoreIdentity.ViewModels 4 | { 5 | public class LoginVM 6 | { 7 | public string UserName { get; set; } 8 | 9 | [DataType(DataType.Password)] 10 | public string Password { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/RegisterVM.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AspNetCoreIdentity.ViewModels 4 | { 5 | public class RegisterVM 6 | { 7 | public string UserName { get; set; } 8 | 9 | [DataType(DataType.EmailAddress)] 10 | public string Email { get; set; } 11 | 12 | [DataType(DataType.Password)] 13 | public string Password { get; set; } 14 | 15 | [Compare("Password")] 16 | [DataType(DataType.Password)] 17 | public string ConfirmPassword { get; set; } 18 | 19 | public bool StartFreeTrial {get; set;} 20 | 21 | public bool IsAdmin {get; set;} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/ResultVM.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreIdentity.ViewModels 2 | { 3 | public class ResultVM 4 | { 5 | public Status Status { get; set; } 6 | public string Message { get; set; } 7 | public object Data { get; set; } 8 | } 9 | 10 | public enum Status 11 | { 12 | Success = 1, 13 | Error = 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/TwoFactorLoginVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreIdentity.ViewModels 8 | { 9 | public class TwoFactorLoginVM 10 | { 11 | [Required] 12 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Text)] 14 | [Display(Name = "Authenticator code")] 15 | public string TwoFactorCode { get; set; } 16 | 17 | public bool RememberMachine { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/TwoFactorRecoveryCodeLoginVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreIdentity.ViewModels 8 | { 9 | public class TwoFactorRecoveryCodeLoginVM 10 | { 11 | [Required] 12 | [DataType(DataType.Text)] 13 | [Display(Name = "Recovery Code")] 14 | public string RecoveryCode { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/UpdatePasswordVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreIdentity.ViewModels 8 | { 9 | public class UpdatePasswordVM 10 | { 11 | [Required(ErrorMessage = "Password is required")] 12 | [DataType(DataType.Password)] 13 | public string Password { get; set; } 14 | 15 | [Required(ErrorMessage = "Confirm Password is required")] 16 | [DataType(DataType.Password)] 17 | [Compare("Password")] 18 | public string ConfirmPassword { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/UserStateVM.cs: -------------------------------------------------------------------------------- 1 | namespace AspNetCoreIdentity.ViewModels 2 | { 3 | public class UserStateVM 4 | { 5 | public bool IsAuthenticated { get; set; } 6 | public string Username { get; set; } 7 | public string AuthenticationMethod { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/VefiryAuthenticatorVM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace AspNetCoreIdentity.ViewModels 8 | { 9 | public class VefiryAuthenticatorVM 10 | { 11 | [Required] 12 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Text)] 14 | [Display(Name = "Verification Code")] 15 | public string VerificationCode { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/ViewModels/VideoVM.cs: -------------------------------------------------------------------------------- 1 | using AspNetCoreIdentity.Infrastructure; 2 | 3 | namespace AspNetCoreIdentity.ViewModels { 4 | public class VideoVM { 5 | public string Url { get; set; } 6 | public string Title { get; set; } 7 | public string Description { get; set; } 8 | public StreamingCategory Category {get; set;} 9 | } 10 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AspNetCoreIdentity 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "AspNetCoreIdentityDb": "Data Source=.;Database=AspNetCoreIdentityDb;trusted_connection=yes;MultipleActiveResultSets=true;" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Warning" 8 | } 9 | }, 10 | "InMemoryProvider": true, 11 | "TwoFactorAuthentication:EncryptionEnabled": true 12 | } 13 | -------------------------------------------------------------------------------- /AspNetCoreIdentity/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [] 5 | } -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/favicon.ico -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/403-forbidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/403-forbidden.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/dropbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/dropbox.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/facebook.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/github.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/google.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/linkedin.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/microsoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/microsoft.png -------------------------------------------------------------------------------- /AspNetCoreIdentity/wwwroot/images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/AspNetCoreIdentity/wwwroot/images/twitter.png -------------------------------------------------------------------------------- /IdentityServer/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4; 3 | using IdentityServer4.Models; 4 | 5 | namespace IdentityServer 6 | { 7 | public static class Config 8 | { 9 | public static IEnumerable GetIdentityResources() 10 | { 11 | return new List 12 | { 13 | new IdentityResources.OpenId(), 14 | new IdentityResources.Profile(), 15 | }; 16 | } 17 | 18 | public static IEnumerable GetClients() 19 | { 20 | return new List 21 | { 22 | new Client 23 | { 24 | ClientId = "AspNetCoreIdentity", 25 | ClientName = "AspNetCoreIdentity Client", 26 | AllowedGrantTypes = GrantTypes.Code, 27 | RequirePkce = true, 28 | RequireClientSecret = false, 29 | 30 | RedirectUris = { "http://localhost:5000" }, 31 | PostLogoutRedirectUris = { "http://localhost:5000" }, 32 | AllowedCorsOrigins = { "http://localhost:5000" }, 33 | 34 | AllowedScopes = 35 | { 36 | IdentityServerConstants.StandardScopes.OpenId, 37 | IdentityServerConstants.StandardScopes.Profile, 38 | "SocialAPI" 39 | } 40 | } 41 | }; 42 | } 43 | 44 | public static IEnumerable GetApis() 45 | { 46 | return new List 47 | { 48 | new ApiResource("SocialAPI", "Social Network API") 49 | }; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /IdentityServer/Controllers/GrantsController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using IdentityServer.Filters; 5 | using IdentityServer.Models.Grants; 6 | using IdentityServer4.Events; 7 | using IdentityServer4.Extensions; 8 | using IdentityServer4.Services; 9 | using IdentityServer4.Stores; 10 | using Microsoft.AspNetCore.Authorization; 11 | using Microsoft.AspNetCore.Mvc; 12 | 13 | namespace IdentityServer.Controllers 14 | { 15 | /// 16 | /// This sample controller allows a user to revoke grants given to clients 17 | /// 18 | [SecurityHeaders] 19 | [Authorize] 20 | public class GrantsController : Controller 21 | { 22 | private readonly IIdentityServerInteractionService _interaction; 23 | private readonly IClientStore _clients; 24 | private readonly IResourceStore _resources; 25 | private readonly IEventService _events; 26 | 27 | public GrantsController(IIdentityServerInteractionService interaction, 28 | IClientStore clients, 29 | IResourceStore resources, 30 | IEventService events) 31 | { 32 | _interaction = interaction; 33 | _clients = clients; 34 | _resources = resources; 35 | _events = events; 36 | } 37 | 38 | /// 39 | /// Show list of grants 40 | /// 41 | [HttpGet] 42 | public async Task Index() 43 | { 44 | return View("Index", await BuildViewModelAsync()); 45 | } 46 | 47 | /// 48 | /// Handle postback to revoke a client 49 | /// 50 | [HttpPost] 51 | [ValidateAntiForgeryToken] 52 | public async Task Revoke(string clientId) 53 | { 54 | await _interaction.RevokeUserConsentAsync(clientId); 55 | await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); 56 | 57 | return RedirectToAction("Index"); 58 | } 59 | 60 | private async Task BuildViewModelAsync() 61 | { 62 | var grants = await _interaction.GetAllUserConsentsAsync(); 63 | 64 | var list = new List(); 65 | foreach (var grant in grants) 66 | { 67 | var client = await _clients.FindClientByIdAsync(grant.ClientId); 68 | if (client != null) 69 | { 70 | var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes); 71 | 72 | var item = new GrantViewModel() 73 | { 74 | ClientId = client.ClientId, 75 | ClientName = client.ClientName ?? client.ClientId, 76 | ClientLogoUrl = client.LogoUri, 77 | ClientUrl = client.ClientUri, 78 | Created = grant.CreationTime, 79 | Expires = grant.Expiration, 80 | IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), 81 | ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() 82 | }; 83 | 84 | list.Add(item); 85 | } 86 | } 87 | 88 | return new GrantsViewModel 89 | { 90 | Grants = list 91 | }; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /IdentityServer/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using IdentityServer.Filters; 3 | using IdentityServer.Models; 4 | using IdentityServer4.Services; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace IdentityServer.Controllers 11 | { 12 | [SecurityHeaders] 13 | [AllowAnonymous] 14 | public class HomeController : Controller 15 | { 16 | private readonly IIdentityServerInteractionService _interaction; 17 | private readonly IHostingEnvironment _environment; 18 | private readonly ILogger _logger; 19 | 20 | public HomeController(IIdentityServerInteractionService interaction, IHostingEnvironment environment, ILogger logger) 21 | { 22 | _interaction = interaction; 23 | _environment = environment; 24 | _logger = logger; 25 | } 26 | 27 | public IActionResult Index() 28 | { 29 | if (_environment.IsDevelopment()) 30 | { 31 | // only show in development 32 | return View(); 33 | } 34 | 35 | _logger.LogInformation("Homepage is disabled in production. Returning 404."); 36 | return NotFound(); 37 | } 38 | 39 | /// 40 | /// Shows the error page 41 | /// 42 | public async Task Error(string errorId) 43 | { 44 | var vm = new ErrorViewModel(); 45 | 46 | // retrieve error details from identityserver 47 | var message = await _interaction.GetErrorContextAsync(errorId); 48 | if (message != null) 49 | { 50 | vm.Error = message; 51 | 52 | if (!_environment.IsDevelopment()) 53 | { 54 | // only show in development 55 | message.ErrorDescription = null; 56 | } 57 | } 58 | 59 | return View("Error", vm); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /IdentityServer/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace IdentityServer.Data 6 | { 7 | public class ApplicationDbContext : IdentityDbContext 8 | { 9 | public ApplicationDbContext(DbContextOptions options) 10 | : base(options) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /IdentityServer/Data/instructions.md: -------------------------------------------------------------------------------- 1 | # Migration instructions for IdentityServer project 2 | 3 | * `cd path-to\IdentityServer` 4 | * change `UseInMemoryStores` to `false` in **appsettings.json** 5 | 6 | ## Migrations using terminal 7 | 8 | ### Create migrations 9 | * `dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb` 10 | * `dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb` 11 | * `dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ApplicationDbContext -o Data/Migrations` 12 | 13 | ### Update database 14 | * `dotnet ef database update --context ApplicationDbContext` 15 | * `dotnet ef database update --context PersistedGrantDbContext` 16 | * `dotnet ef database update --context ConfigurationDbContext` 17 | 18 | 19 | ## Migrations using VISUAL STUDIO Package Manager Console 20 | 21 | ### Create migrations 22 | * `Add-Migration InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -Project "IdentityServer" -StartUpProject "IdentityServer" -o Data/Migrations/IdentityServer/PersistedGrantDb` 23 | * `Add-Migration InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -Project "IdentityServer" -StartUpProject "IdentityServer" -o Data/Migrations/IdentityServer/ConfigurationDb` 24 | * `Add-Migration InitialIdentityServerConfigurationDbMigration -c ApplicationDbContext -Project "IdentityServer" -StartUpProject "IdentityServer" -o Data/Migrations` 25 | 26 | ### Update database 27 | * `Update-Database -Context ApplicationDbContext -Project "IdentityServer" -StartupProject "IdentityServer"` 28 | * `Update-Database -Context PersistedGrantDbContext -Project "IdentityServer" -StartupProject "IdentityServer"` 29 | * `Update-Database -Context ConfigurationDbContext -Project "IdentityServer" -StartupProject "IdentityServer"` 30 | -------------------------------------------------------------------------------- /IdentityServer/DatabaseInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using IdentityModel; 5 | using IdentityServer.Data; 6 | using IdentityServer4.EntityFramework.DbContexts; 7 | using IdentityServer4.EntityFramework.Mappers; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace IdentityServer 13 | { 14 | public class DatabaseInitializer 15 | { 16 | public static void Init(IServiceProvider provider, bool useInMemoryStores) 17 | { 18 | if (!useInMemoryStores) 19 | { 20 | provider.GetRequiredService().Database.Migrate(); 21 | provider.GetRequiredService().Database.Migrate(); 22 | provider.GetRequiredService().Database.Migrate(); 23 | } 24 | InitializeIdentityServer(provider); 25 | 26 | var userManager = provider.GetRequiredService>(); 27 | var chsakell = userManager.FindByNameAsync("chsakell").Result; 28 | if (chsakell == null) 29 | { 30 | chsakell = new IdentityUser 31 | { 32 | UserName = "chsakell" 33 | }; 34 | var result = userManager.CreateAsync(chsakell, "$AspNetIdentity10$").Result; 35 | if (!result.Succeeded) 36 | { 37 | throw new Exception(result.Errors.First().Description); 38 | } 39 | 40 | chsakell = userManager.FindByNameAsync("chsakell").Result; 41 | 42 | result = userManager.AddClaimsAsync(chsakell, new Claim[]{ 43 | new Claim(JwtClaimTypes.Name, "Chris Sakellarios"), 44 | new Claim(JwtClaimTypes.GivenName, "Christos"), 45 | new Claim(JwtClaimTypes.FamilyName, "Sakellarios"), 46 | new Claim(JwtClaimTypes.Email, "chsakellsblog@blog.com"), 47 | new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 48 | new Claim(JwtClaimTypes.WebSite, "https://chsakell.com"), 49 | new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'localhost 10', 'postal_code': 11146, 'country': 'Greece' }", 50 | IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) 51 | }).Result; 52 | 53 | if (!result.Succeeded) 54 | { 55 | throw new Exception(result.Errors.First().Description); 56 | } 57 | Console.WriteLine("chsakell created"); 58 | } 59 | else 60 | { 61 | Console.WriteLine("chsakell already exists"); 62 | } 63 | } 64 | 65 | private static void InitializeIdentityServer(IServiceProvider provider) 66 | { 67 | var context = provider.GetRequiredService(); 68 | if (!context.Clients.Any()) 69 | { 70 | foreach (var client in Config.GetClients()) 71 | { 72 | context.Clients.Add(client.ToEntity()); 73 | } 74 | context.SaveChanges(); 75 | } 76 | 77 | if (!context.IdentityResources.Any()) 78 | { 79 | foreach (var resource in Config.GetIdentityResources()) 80 | { 81 | context.IdentityResources.Add(resource.ToEntity()); 82 | } 83 | context.SaveChanges(); 84 | } 85 | 86 | if (!context.ApiResources.Any()) 87 | { 88 | foreach (var resource in Config.GetApis()) 89 | { 90 | context.ApiResources.Add(resource.ToEntity()); 91 | } 92 | context.SaveChanges(); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /IdentityServer/Extensions/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using IdentityServer4.Stores; 3 | 4 | namespace IdentityServer.Extensions 5 | { 6 | public static class Extensions 7 | { 8 | /// 9 | /// Determines whether the client is configured to use PKCE. 10 | /// 11 | /// The store. 12 | /// The client identifier. 13 | /// 14 | public static async Task IsPkceClientAsync(this IClientStore store, string client_id) 15 | { 16 | if (!string.IsNullOrWhiteSpace(client_id)) 17 | { 18 | var client = await store.FindEnabledClientByIdAsync(client_id); 19 | return client?.RequirePkce == true; 20 | } 21 | 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /IdentityServer/Filters/SecurityHeadersAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | 4 | namespace IdentityServer.Filters 5 | { 6 | public class SecurityHeadersAttribute : ActionFilterAttribute 7 | { 8 | public override void OnResultExecuting(ResultExecutingContext context) 9 | { 10 | var result = context.Result; 11 | if (result is ViewResult) 12 | { 13 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 14 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) 15 | { 16 | context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 17 | } 18 | 19 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 20 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) 21 | { 22 | context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); 23 | } 24 | 25 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 26 | var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; 27 | // also consider adding upgrade-insecure-requests once you have HTTPS in place for production 28 | //csp += "upgrade-insecure-requests;"; 29 | // also an example if you need client images to be displayed from twitter 30 | // csp += "img-src 'self' https://pbs.twimg.com;"; 31 | 32 | // once for standards compliant browsers 33 | if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) 34 | { 35 | context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); 36 | } 37 | // and once again for IE 38 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) 39 | { 40 | context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); 41 | } 42 | 43 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy 44 | var referrer_policy = "no-referrer"; 45 | if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) 46 | { 47 | context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /IdentityServer/IdentityServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/AccountOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IdentityServer.Models.Account 4 | { 5 | public class AccountOptions 6 | { 7 | public static bool AllowLocalLogin = true; 8 | public static bool AllowRememberLogin = true; 9 | public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); 10 | 11 | public static bool ShowLogoutPrompt = true; 12 | public static bool AutomaticRedirectAfterSignOut = false; 13 | 14 | // specify the Windows authentication scheme being used 15 | public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; 16 | // if user uses windows auth, should we load the groups from windows 17 | public static bool IncludeWindowsGroups = false; 18 | 19 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 20 | 21 | public static string UserAlreadyExistsErrorMessage = "User already exists"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/ExternalProvider.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Account 2 | { 3 | public class ExternalProvider 4 | { 5 | public string DisplayName { get; set; } 6 | public string AuthenticationScheme { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/LoggedOutViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Account 2 | { 3 | public class LoggedOutViewModel 4 | { 5 | public string PostLogoutRedirectUri { get; set; } 6 | public string ClientName { get; set; } 7 | public string SignOutIframeUrl { get; set; } 8 | 9 | public bool AutomaticRedirectAfterSignOut { get; set; } = false; 10 | 11 | public string LogoutId { get; set; } 12 | public bool TriggerExternalSignout => ExternalAuthenticationScheme != null; 13 | public string ExternalAuthenticationScheme { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/LoginInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace IdentityServer.Models.Account 4 | { 5 | public class LoginInputModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | 10 | [Required] 11 | public string Password { get; set; } 12 | 13 | public bool RememberLogin { get; set; } 14 | 15 | public string ReturnUrl { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace IdentityServer.Models.Account 6 | { 7 | public class LoginViewModel : LoginInputModel 8 | { 9 | public bool AllowRememberLogin { get; set; } = true; 10 | public bool EnableLocalLogin { get; set; } = true; 11 | 12 | public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); 13 | public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); 14 | 15 | public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; 16 | public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/LogoutInputModel.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Account 2 | { 3 | public class LogoutInputModel 4 | { 5 | public string LogoutId { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/LogoutViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Account 2 | { 3 | public class LogoutViewModel : LogoutInputModel 4 | { 5 | public bool ShowLogoutPrompt { get; set; } = true; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/RedirectViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models 2 | { 3 | public class RedirectViewModel 4 | { 5 | public string RedirectUrl { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /IdentityServer/Models/Account/RegisterVM.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace IdentityServer.Models.Account 4 | { 5 | public class RegisterVM 6 | { 7 | public string Username { get; set; } 8 | 9 | [DataType(DataType.EmailAddress)] 10 | public string Email { get; set; } 11 | 12 | [DataType(DataType.Password)] 13 | public string Password { get; set; } 14 | 15 | [Compare("Password")] 16 | [DataType(DataType.Password)] 17 | public string ConfirmPassword { get; set; } 18 | 19 | public string ReturnUrl { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /IdentityServer/Models/Consent/ConsentInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IdentityServer.Models.Consent 4 | { 5 | public class ConsentInputModel 6 | { 7 | public string Button { get; set; } 8 | public IEnumerable ScopesConsented { get; set; } 9 | public bool RememberConsent { get; set; } 10 | public string ReturnUrl { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /IdentityServer/Models/Consent/ConsentOptions.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Consent 2 | { 3 | public class ConsentOptions 4 | { 5 | public static bool EnableOfflineAccess = true; 6 | public static string OfflineAccessDisplayName = "Offline Access"; 7 | public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; 8 | 9 | public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; 10 | public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /IdentityServer/Models/Consent/ConsentViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IdentityServer.Models.Consent 4 | { 5 | public class ConsentViewModel : ConsentInputModel 6 | { 7 | public string ClientName { get; set; } 8 | public string ClientUrl { get; set; } 9 | public string ClientLogoUrl { get; set; } 10 | public bool AllowRememberConsent { get; set; } 11 | 12 | public IEnumerable IdentityScopes { get; set; } 13 | public IEnumerable ResourceScopes { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IdentityServer/Models/Consent/ProcessConsentResult.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Consent 2 | { 3 | public class ProcessConsentResult 4 | { 5 | public bool IsRedirect => RedirectUri != null; 6 | public string RedirectUri { get; set; } 7 | public string ClientId { get; set; } 8 | 9 | public bool ShowView => ViewModel != null; 10 | public ConsentViewModel ViewModel { get; set; } 11 | 12 | public bool HasValidationError => ValidationError != null; 13 | public string ValidationError { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /IdentityServer/Models/Consent/ScopeViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace IdentityServer.Models.Consent 2 | { 3 | public class ScopeViewModel 4 | { 5 | public string Name { get; set; } 6 | public string DisplayName { get; set; } 7 | public string Description { get; set; } 8 | public bool Emphasize { get; set; } 9 | public bool Required { get; set; } 10 | public bool Checked { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /IdentityServer/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4.Models; 2 | 3 | namespace IdentityServer.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public ErrorMessage Error { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /IdentityServer/Models/Grants/GrantViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace IdentityServer.Models.Grants 5 | { 6 | public class GrantViewModel 7 | { 8 | public string ClientId { get; set; } 9 | public string ClientName { get; set; } 10 | public string ClientUrl { get; set; } 11 | public string ClientLogoUrl { get; set; } 12 | public DateTime Created { get; set; } 13 | public DateTime? Expires { get; set; } 14 | public IEnumerable IdentityGrantNames { get; set; } 15 | public IEnumerable ApiGrantNames { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /IdentityServer/Models/Grants/GrantsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace IdentityServer.Models.Grants 4 | { 5 | public class GrantsViewModel 6 | { 7 | public IEnumerable Grants { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /IdentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace IdentityServer 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var host = CreateWebHostBuilder(args).Build(); 13 | 14 | using (var scope = host.Services.CreateScope()) 15 | { 16 | var services = scope.ServiceProvider; 17 | var config = services.GetService(); 18 | var useInMemoryStores = config.GetValue("UseInMemoryStores"); 19 | DatabaseInitializer.Init(services, useInMemoryStores); 20 | } 21 | 22 | host.Run(); 23 | } 24 | 25 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 26 | WebHost.CreateDefaultBuilder(args) 27 | .UseStartup(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IdentityServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5005", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "IdentityServer": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "http://localhost:5005" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /IdentityServer/Views/Account/LoggedOut.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Account.LoggedOutViewModel 2 | 3 | @{ 4 | // set this so the layout rendering sees an anonymous user 5 | ViewData["signed-out"] = true; 6 | } 7 | 8 | 27 | 28 | @section scripts 29 | { 30 | @if (Model.AutomaticRedirectAfterSignOut) 31 | { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /IdentityServer/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Account.LoginViewModel 2 | 3 | @{ 4 | var dictionary = new Dictionary { {"ReturnUrl", Model.ReturnUrl }}; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /IdentityServer/Views/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Account.LogoutViewModel 2 | 3 |
      4 | 7 | 8 |
      9 |
      10 |

      Would you like to logout of IdentityServer?

      11 |
      12 | 13 |
      14 |
      15 | 16 |
      17 |
      18 |
      19 |
      20 |
      21 |
      -------------------------------------------------------------------------------- /IdentityServer/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Account.RegisterVM 2 | 3 |
      4 | 7 | 8 | 9 | 10 |
      11 | 12 | 13 |
      14 |
      15 |
      16 |

      Local Login

      17 |
      18 |
      19 | 20 |
      21 | 22 | 23 |
      24 |
      25 | 26 | 27 |
      28 |
      29 | 30 | 31 |
      32 |
      33 | 34 | 35 |
      36 |
      37 | 38 | 39 |
      40 | 41 |
      42 | 43 | 44 |
      45 |
      46 |
      47 |
      48 |
      49 |
      50 |
      51 |
      -------------------------------------------------------------------------------- /IdentityServer/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Consent.ConsentViewModel 2 | 3 | -------------------------------------------------------------------------------- /IdentityServer/Views/Grants/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Grants.GrantsViewModel 2 | 3 |
      4 | 12 | 13 | @if (Model.Grants.Any() == false) 14 | { 15 |
      16 |
      17 |
      18 | You have not given access to any applications 19 |
      20 |
      21 |
      22 | } 23 | else 24 | { 25 | foreach (var grant in Model.Grants) 26 | { 27 |
      28 |
      29 | @if (grant.ClientLogoUrl != null) 30 | { 31 | 32 | } 33 |
      34 |
      35 |
      @grant.ClientName
      36 |
      37 | Created: @grant.Created.ToString("yyyy-MM-dd") 38 |
      39 | @if (grant.Expires.HasValue) 40 | { 41 |
      42 | Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") 43 |
      44 | } 45 | @if (grant.IdentityGrantNames.Any()) 46 | { 47 |
      48 |
      Identity Grants
      49 |
        50 | @foreach (var name in grant.IdentityGrantNames) 51 | { 52 |
      • @name
      • 53 | } 54 |
      55 |
      56 | } 57 | @if (grant.ApiGrantNames.Any()) 58 | { 59 |
      60 |
      API Grants
      61 |
        62 | @foreach (var name in grant.ApiGrantNames) 63 | { 64 |
      • @name
      • 65 | } 66 |
      67 |
      68 | } 69 |
      70 |
      71 |
      72 | 73 | 74 |
      75 |
      76 |
      77 | } 78 | } 79 |
      -------------------------------------------------------------------------------- /IdentityServer/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var version = typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.GetName().Version.ToString(); 3 | } 4 | 5 |
      6 | 15 | 16 |
      17 |
      18 |

      19 | IdentityServer publishes a 20 | discovery document 21 | where you can find metadata and links to all the endpoints, key material, etc. 22 |

      23 |
      24 |
      25 |

      26 | Click here to manage your stored grants. 27 |

      28 |
      29 |
      30 |
      31 |
      32 |

      33 | Here are links to the 34 | source code repository, 35 | and ready to use samples. 36 |

      37 |
      38 |
      39 |
      40 | -------------------------------------------------------------------------------- /IdentityServer/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.ErrorViewModel 2 | 3 | @{ 4 | var error = Model?.Error?.Error; 5 | var errorDescription = Model?.Error?.ErrorDescription; 6 | var request_id = Model?.Error?.RequestId; 7 | } 8 | 9 |
      10 | 13 | 14 |
      15 |
      16 |
      17 | Sorry, there was an error 18 | 19 | @if (error != null) 20 | { 21 | 22 | 23 | : @error 24 | 25 | 26 | 27 | if (errorDescription != null) 28 | { 29 |
      @errorDescription
      30 | } 31 | } 32 |
      33 | 34 | @if (request_id != null) 35 | { 36 |
      Request Id: @request_id
      37 | } 38 |
      39 |
      40 |
      41 | -------------------------------------------------------------------------------- /IdentityServer/Views/Shared/Redirect.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.RedirectViewModel 2 | 3 |

      You are now being returned to the application.

      4 |

      Once complete, you may close this tab

      5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IdentityServer/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer4.Extensions 2 | @{ 3 | string name = null; 4 | if (!true.Equals(ViewData["signed-out"])) 5 | { 6 | name = Context.User?.GetDisplayName(); 7 | } 8 | } 9 | 10 | 11 | 12 | 13 | 14 | 15 | IdentityServer4 16 | 17 | 18 | 19 | 20 | 21 | 22 | 52 | 53 |
      54 | @RenderBody() 55 |
      56 | 57 | 58 | 59 | @RenderSection("scripts", required: false) 60 | 61 | 62 | -------------------------------------------------------------------------------- /IdentityServer/Views/Shared/_ScopeListItem.cshtml: -------------------------------------------------------------------------------- 1 | @model IdentityServer.Models.Consent.ScopeViewModel 2 | 3 |
    • 4 | 24 | @if (Model.Required) 25 | { 26 | (required) 27 | } 28 | @if (Model.Description != null) 29 | { 30 | 33 | } 34 |
    • -------------------------------------------------------------------------------- /IdentityServer/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
      4 | Error 5 |
      6 |
      7 | } -------------------------------------------------------------------------------- /IdentityServer/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using IdentityServer 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /IdentityServer/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /IdentityServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /IdentityServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "ConnectionStrings": { 8 | "IdentityServerConnection": "Data Source=.;Database=IdentityServerDb;trusted_connection=yes;MultipleActiveResultSets=true;" 9 | }, 10 | "UseInMemoryStores": true 11 | } 12 | 13 | -------------------------------------------------------------------------------- /IdentityServer/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"cbd3483398a40cf777e490cd2244deb3","Parameters":{"D":"nRvkt+n9Jawzff57Gi98FkV8ph1fuL2YFM/HVoexu0GkVDUwL6RfakDWXzBH/PmtOOEpBp0oayZlmgmd4Hu9+SLBEhNVY58zoJR6c8MTj8nA52iJyB3zvTbkK/VNbRxTnylmV/co8do8rDiUC+3QjxcydQSAB34nZ52F4WTDw5z5PrdydhNomu/3kPeUOjhJUZ+jmRTB0xP+5ODYJOboKQBELWEqsXEq/wkUYws86qxL09JhDnIHLetr88LLMcoYSTkgn1Oz9I6/Rfe8+SizQgWpeFXuCn1KqsckaAPSZa45mw7cOYY5DSXQXZqjk9V8Zna83M1DbgPeSe3gARgoxQ==","DP":"sTGWLMK/0ioAXZR43jlDygtONUbM6Ckcvd8q3S7mpmiaNhVBo6KVBU2oFaQ2/t1D/cnBR4SnQNjMJlC6ctTLEBo9voCm2MNOXasfU8BjVWDfrKdePFjBmHpytA7V6PpuiGsZGfHkkCq++p8NQ0y8SwVBZecciAq3ec3DOy6sdfE=","DQ":"xTdgnNoSpXs7M++vPNXHZ3VUEs0okEI2ktuMzvdM8EgIlcvNP3DOF4+8RI0iPKMBJr/6xU6fs8RGmiCGOha48SCwTua7WnrkgsaEbnte+s+eaAGePRZMnOhN1j20Jz6RkcpAA87W9wZ8Gi9XrFnq1ImIfZhKhPkYguVUPckiv5c=","Exponent":"AQAB","InverseQ":"lFPtEbpQedFAC7OE5qqNX7gO1LIGdJXopa8KQqC1CdAmevEDeXBlZT//c/lj+zIe8oiinqPPGBw+08KCfhnA8NtxIQhfIptuEVA45O17U508adBjMwfFgXB54qHm9OVewYwMNY/keavm+Qj6PjdJEemk/5uk6Y6XtycVYeEpWjo=","Modulus":"uUBSCgtKENYM4QQb4L9D9XNXILc7FI2NULCgm9vfYq2mPV3dgXXYuU4rYN6W9ZDs3XZR/ja5BSFPBBwFl44ZMsFyxyt2nFX6s8S1YY4hS5ybqoC9eI7WU6CgqqVUiKfXjtQp2KBmVEGi6GjnrbqcIngoCQ9BQsW+hYdvE0T4JhK5GzPCXbR9yle+nVW/SVx2SKdlecuVTaU2Qa4ionF6RrwA2yJ/6TNikQgEQE2LEVMVMrvAWsbFxrsM8M57tbWe+8IWac2KkZlhQhXXQmoccRkDbKb7d+bA/kcAia8GU8U3twp4EP7TJHBi5/julRlW9Rq5Id926sKGT23PxMXjIQ==","P":"zATIVLzhnmTBm7Vn4ydDel1xKSXlqGEGZmGwnGoWVOkC6JMM4o0U8KYAX+gqeHs1n9m2dkIN6cOu9+PwVB5boL96Q7UvW0F68vtYU53zErFVcTCWIGnqtIgOUD44BUExs58A/oCWQ1ogenfZlWY34FgivDch/2KMRYmchMalUHs=","Q":"6HNsVuHFoKiMgyfTdBPtcS0MdGDLAlQhHg0jcvf733HLJP7XCLvx8zfjU8JRfa7WqLBl5zV7HCdd0abD/SGljDvN4y7uMUPA4G9EeMEszZMLtNfiySwxbPWhZesLSfe5wxGlgLE20eyvv+PxMSDM2wILWYCIRI/f7+5abdTvnhM="}} -------------------------------------------------------------------------------- /IdentityServer/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | .navbar-header { 5 | position: relative; 6 | top: -4px; 7 | } 8 | .navbar-brand > .icon-banner { 9 | position: relative; 10 | top: -2px; 11 | display: inline; 12 | } 13 | .icon { 14 | position: relative; 15 | top: -10px; 16 | } 17 | .logged-out iframe { 18 | display: none; 19 | width: 0; 20 | height: 0; 21 | } 22 | .page-consent .client-logo { 23 | float: left; 24 | } 25 | .page-consent .client-logo img { 26 | width: 80px; 27 | height: 80px; 28 | } 29 | .page-consent .consent-buttons { 30 | margin-top: 25px; 31 | } 32 | .page-consent .consent-form .consent-scopecheck { 33 | display: inline-block; 34 | margin-right: 5px; 35 | } 36 | .page-consent .consent-form .consent-description { 37 | margin-left: 25px; 38 | } 39 | .page-consent .consent-form .consent-description label { 40 | font-weight: normal; 41 | } 42 | .page-consent .consent-form .consent-remember { 43 | padding-left: 16px; 44 | } 45 | .grants .page-header { 46 | margin-bottom: 10px; 47 | } 48 | .grants .grant { 49 | margin-top: 20px; 50 | padding-bottom: 20px; 51 | border-bottom: 1px solid lightgray; 52 | } 53 | .grants .grant img { 54 | width: 100px; 55 | height: 100px; 56 | } 57 | .grants .grant .clientname { 58 | font-size: 140%; 59 | font-weight: bold; 60 | } 61 | .grants .grant .granttype { 62 | font-size: 120%; 63 | font-weight: bold; 64 | } 65 | .grants .grant .created { 66 | font-size: 120%; 67 | font-weight: bold; 68 | } 69 | .grants .grant .expires { 70 | font-size: 120%; 71 | font-weight: bold; 72 | } 73 | .grants .grant li { 74 | list-style-type: none; 75 | display: inline; 76 | } 77 | .grants .grant li:after { 78 | content: ', '; 79 | } 80 | .grants .grant li:last-child:after { 81 | content: ''; 82 | } -------------------------------------------------------------------------------- /IdentityServer/wwwroot/css/site.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 65px; 3 | } 4 | 5 | .navbar-header { 6 | position: relative; 7 | top: -4px; 8 | } 9 | 10 | .navbar-brand > .icon-banner { 11 | position: relative; 12 | top: -2px; 13 | display: inline; 14 | } 15 | 16 | .icon { 17 | position: relative; 18 | top: -10px; 19 | } 20 | 21 | .logged-out iframe { 22 | display: none; 23 | width: 0; 24 | height: 0; 25 | } 26 | 27 | .page-consent { 28 | .client-logo { 29 | float: left; 30 | 31 | img { 32 | width: 80px; 33 | height: 80px; 34 | } 35 | } 36 | 37 | .consent-buttons { 38 | margin-top: 25px; 39 | } 40 | 41 | .consent-form { 42 | .consent-scopecheck { 43 | display: inline-block; 44 | margin-right: 5px; 45 | } 46 | 47 | .consent-scopecheck[disabled] { 48 | //visibility:hidden; 49 | } 50 | 51 | .consent-description { 52 | margin-left: 25px; 53 | 54 | label { 55 | font-weight: normal; 56 | } 57 | } 58 | 59 | .consent-remember { 60 | padding-left: 16px; 61 | } 62 | } 63 | } 64 | 65 | .grants { 66 | .page-header { 67 | margin-bottom: 10px; 68 | } 69 | 70 | .grant { 71 | margin-top: 20px; 72 | padding-bottom: 20px; 73 | border-bottom: 1px solid lightgray; 74 | 75 | img { 76 | width: 100px; 77 | height: 100px; 78 | } 79 | 80 | .clientname { 81 | font-size: 140%; 82 | font-weight: bold; 83 | } 84 | 85 | .granttype { 86 | font-size: 120%; 87 | font-weight: bold; 88 | } 89 | 90 | .created { 91 | font-size: 120%; 92 | font-weight: bold; 93 | } 94 | 95 | .expires { 96 | font-size: 120%; 97 | font-weight: bold; 98 | } 99 | 100 | li { 101 | list-style-type: none; 102 | display: inline; 103 | 104 | &:after { 105 | content: ', '; 106 | } 107 | 108 | &:last-child:after { 109 | content: ''; 110 | } 111 | 112 | .displayname { 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /IdentityServer/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{margin-top:65px;}.navbar-header{position:relative;top:-4px;}.navbar-brand>.icon-banner{position:relative;top:-2px;display:inline;}.icon{position:relative;top:-10px;}.logged-out iframe{display:none;width:0;height:0;}.page-consent .client-logo{float:left;}.page-consent .client-logo img{width:80px;height:80px;}.page-consent .consent-buttons{margin-top:25px;}.page-consent .consent-form .consent-scopecheck{display:inline-block;margin-right:5px;}.page-consent .consent-form .consent-description{margin-left:25px;}.page-consent .consent-form .consent-description label{font-weight:normal;}.page-consent .consent-form .consent-remember{padding-left:16px;}.grants .page-header{margin-bottom:10px;}.grants .grant{margin-top:20px;padding-bottom:20px;border-bottom:1px solid #d3d3d3;}.grants .grant img{width:100px;height:100px;}.grants .grant .clientname{font-size:140%;font-weight:bold;}.grants .grant .granttype{font-size:120%;font-weight:bold;}.grants .grant .created{font-size:120%;font-weight:bold;}.grants .grant .expires{font-size:120%;font-weight:bold;}.grants .grant li{list-style-type:none;display:inline;}.grants .grant li:after{content:', ';}.grants .grant li:last-child:after{content:'';} -------------------------------------------------------------------------------- /IdentityServer/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/favicon.ico -------------------------------------------------------------------------------- /IdentityServer/wwwroot/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/icon.jpg -------------------------------------------------------------------------------- /IdentityServer/wwwroot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/icon.png -------------------------------------------------------------------------------- /IdentityServer/wwwroot/js/signin-redirect.js: -------------------------------------------------------------------------------- 1 | window.location.href = document.querySelector("meta[http-equiv=refresh]").getAttribute("data-url"); 2 | -------------------------------------------------------------------------------- /IdentityServer/wwwroot/js/signout-redirect.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", function () { 2 | var a = document.querySelector("a.PostLogoutRedirectUri"); 3 | if (a) { 4 | window.location = a.href; 5 | } 6 | }); 7 | -------------------------------------------------------------------------------- /IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/aspnet-core-identity/882cc2329f46086ed3d55e349d041e975b4d2e7c/IdentityServer/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christos Sakellarios 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 | -------------------------------------------------------------------------------- /SocialNetwork.API/Controllers/ContactsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace SocialNetwork.API.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class ContactsController : ControllerBase 11 | { 12 | // GET api/values 13 | [HttpGet] 14 | [Authorize] 15 | public ActionResult> Get() 16 | { 17 | return new List 18 | { 19 | new Contact 20 | { 21 | Name = "Francesca Fenton", 22 | Username = "Fenton25", 23 | Email = "francesca@example.com" 24 | }, 25 | new Contact { 26 | Name = "Pierce North", 27 | Username = "Pierce", 28 | Email = "pierce@example.com" 29 | }, 30 | new Contact { 31 | Name = "Marta Grimes", 32 | Username = "GrimesX", 33 | Email = "marta@example.com" 34 | }, 35 | new Contact{ 36 | Name = "Margie Kearney", 37 | Username = "Kearney20", 38 | Email = "margie@example.com" 39 | } 40 | }; 41 | } 42 | } 43 | 44 | public class Contact 45 | { 46 | public Guid Id { get; set; } 47 | public string Name { get; set; } 48 | public string Username { get; set; } 49 | public string Email { get; set; } 50 | 51 | public Contact() 52 | { 53 | Id = Guid.NewGuid(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SocialNetwork.API/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace SocialNetwork.API 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SocialNetwork.API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5010/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/contacts", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SocialNetwork.API": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/contacts", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "applicationUrl": "http://localhost:5010" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SocialNetwork.API/SocialNetwork.API.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SocialNetwork.API/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace SocialNetwork.API 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddMvc(options => options.EnableEndpointRouting = false); 23 | 24 | services.AddAuthorization(); 25 | 26 | services.AddAuthentication("Bearer") 27 | .AddJwtBearer("Bearer", options => 28 | { 29 | options.Authority = "http://localhost:5005"; 30 | options.RequireHttpsMetadata = false; 31 | 32 | options.Audience = "SocialAPI"; 33 | }); 34 | 35 | 36 | services.AddCors(options => 37 | { 38 | // this defines a CORS policy called "default" 39 | options.AddPolicy("default", policy => 40 | { 41 | policy.WithOrigins("http://localhost:5000") 42 | .AllowAnyHeader() 43 | .AllowAnyMethod(); 44 | }); 45 | }); 46 | } 47 | 48 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 49 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 50 | { 51 | if (env.IsDevelopment()) 52 | { 53 | app.UseDeveloperExceptionPage(); 54 | } 55 | 56 | app.UseCors("default"); 57 | app.UseAuthentication(); 58 | app.UseMvc(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SocialNetwork.API/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /SocialNetwork.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '2.0.{build}' 2 | image: Visual Studio 2019 3 | branches: 4 | only: 5 | - master 6 | clone_depth: 1 7 | init: 8 | # Good practise, because Windows 9 | - git config --global core.autocrlf true 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Get the latest stable version of Node.js or io.js 14 | - cd AspNetCoreIdentity/ClientApp 15 | # install npm modules 16 | - ps: Install-Product node $env:nodejs_version 17 | - npm install 18 | before_build: 19 | - cd.. 20 | # Display minimal restore text 21 | - cmd: dotnet restore --verbosity m 22 | build_script: 23 | # output will be in ./AspNetCoreIdentity/bin/Release/netcoreapp2.0/publish/ 24 | - cmd: dotnet publish -c Release 25 | --------------------------------------------------------------------------------