├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md └── src ├── .angular-cli.json ├── .editorconfig ├── .gitignore ├── AngularASPNETCore2WebApiAuth.csproj ├── AngularASPNETCore2WebApiAuth.sln ├── Auth ├── IJwtFactory.cs └── JwtFactory.cs ├── Controllers ├── AccountsController.cs ├── AuthController.cs ├── DashboardController.cs └── ExternalAuthController.cs ├── Data └── ApplicationDbContext.cs ├── Extensions └── ResponseExtensions.cs ├── Helpers ├── Constants.cs ├── Errors.cs └── Tokens.cs ├── Migrations ├── 20180104134211_initial.Designer.cs ├── 20180104134211_initial.cs └── ApplicationDbContextModelSnapshot.cs ├── Models ├── Entities │ ├── AppUser.cs │ └── Customer.cs ├── FacebookApiResponses.cs ├── FacebookAuthSettings.cs └── JwtIssuerOptions.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Startup.cs ├── ViewModels ├── CredentialsViewModel.cs ├── FacebookAuthViewModel.cs ├── Mappings │ └── ViewModelToEntityMappingProfile.cs ├── RegistrationViewModel.cs └── Validations │ ├── CredentialsViewModelValidator.cs │ └── RegistrationViewModelValidator.cs ├── appsettings.Development.json ├── appsettings.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── account │ │ ├── account.module.ts │ │ ├── account.routing.ts │ │ ├── facebook-login │ │ │ ├── facebook-login.component.html │ │ │ ├── facebook-login.component.scss │ │ │ ├── facebook-login.component.spec.ts │ │ │ └── facebook-login.component.ts │ │ ├── login-form │ │ │ ├── login-form.component.html │ │ │ ├── login-form.component.scss │ │ │ ├── login-form.component.spec.ts │ │ │ └── login-form.component.ts │ │ └── registration-form │ │ │ ├── registration-form.component.html │ │ │ ├── registration-form.component.scss │ │ │ ├── registration-form.component.spec.ts │ │ │ └── registration-form.component.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routing.ts │ ├── auth.guard.ts │ ├── authenticate-xhr.backend.ts │ ├── dashboard │ │ ├── dashboard.module.ts │ │ ├── dashboard.routing.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── models │ │ │ └── home.details.interface.ts │ │ ├── root │ │ │ ├── root.component.html │ │ │ ├── root.component.scss │ │ │ ├── root.component.spec.ts │ │ │ └── root.component.ts │ │ ├── services │ │ │ └── dashboard.service.ts │ │ └── settings │ │ │ ├── settings.component.html │ │ │ ├── settings.component.scss │ │ │ ├── settings.component.spec.ts │ │ │ └── settings.component.ts │ ├── directives │ │ ├── email.validator.directive.ts │ │ └── focus.directive.ts │ ├── header │ │ ├── header.component.html │ │ ├── header.component.scss │ │ ├── header.component.spec.ts │ │ └── header.component.ts │ ├── home │ │ ├── home.component.html │ │ ├── home.component.scss │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── rxjs-operators.js │ ├── shared │ │ ├── models │ │ │ ├── credentials.interface.ts │ │ │ └── user.registration.interface.ts │ │ ├── modules │ │ │ └── shared.module.ts │ │ ├── services │ │ │ ├── base.service.ts │ │ │ └── user.service.ts │ │ └── utils │ │ │ └── config.service.ts │ └── spinner │ │ ├── spinner.component.html │ │ ├── spinner.component.scss │ │ ├── spinner.component.spec.ts │ │ └── spinner.component.ts ├── assets │ ├── .gitkeep │ ├── facebook-login.png │ └── util.js ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── facebook-auth.html ├── favicon.ico ├── index.html ├── main.ts ├── npm-debug.log.141272917 ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json ├── tslint.json └── wwwroot ├── 3rdpartylicenses.txt ├── assets ├── facebook-login.png └── util.js ├── facebook-auth.html ├── favicon.ico ├── index.html ├── inline.b6708a750e679a974885.bundle.js ├── main.1aad2c23f74b94cefc12.bundle.js ├── polyfills.61df7d7ec492d95bb0b2.bundle.js └── styles.053af3a4d85fe061870e.bundle.css /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | # **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /.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}/src/bin/Debug/netcoreapp2.0/AngularASPNETCore2WebApiAuth.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/src", 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 | } -------------------------------------------------------------------------------- /.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}/src/AngularASPNETCore2WebApiAuth.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mark Macneil 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularASPNETCore2WebApiAuth 2 | Sample project based on the blog post demonstrating jwt-based authentication with an Angular (v5.2.1) frontend and ASP.NET Core 2 WebApi. Includes both local user registration with .NET Core Identity membership and facebook login scenarios. 3 | 4 | ### Facebook flow 5 | 6 | 7 | ### Email flow 8 | 9 | 10 | ## Development Environment 11 | - Sql Server Express 2017 & Sql Server Management Studio 2017 12 | - Runs in both Visual Studio 2017 & Visual Studio Code 13 | - Node 8.9.4 & NPM 5.6.0 14 | - .NET Core 2.0 sdk 15 | - Angular CLI -> `npm install -g @angular/cli` https://github.com/angular/angular-cli 16 | 17 | 18 | ## Setup 19 | To build and run the project using the command line: 20 | 1. Install npm packages with `src>npm install` in the `src` directory. 21 | 2. Restore nuget packages with `src>dotnet restore` in the `src` directory. 22 | 3. Create the database with `src>dotnet ef database update` in the `src` directory. 23 | 4. Run the project with `src>dotnet run` in the `src` directory. 24 | 5. Point your browser to **http://localhost:5000**. 25 | 26 | Of course, you can also run it from either Visual Studio 2017 or Visual Studio Code with the IDE handling most of the steps above. If you have issues, try running the above steps from the command line to ensure things are setup properly. 27 | 28 | ## Facebook App Setup 29 | You're free to use the demo facebook app _Fullstack Cafe_ that the project is already configured with. To setup and use your own application follow the steps detailed on the post. 30 | -------------------------------------------------------------------------------- /src/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "jwt-auth-demo" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "wwwroot", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico", 13 | "facebook-auth.html" 14 | ], 15 | "index": "index.html", 16 | "main": "main.ts", 17 | "polyfills": "polyfills.ts", 18 | "test": "test.ts", 19 | "tsconfig": "tsconfig.app.json", 20 | "testTsconfig": "tsconfig.spec.json", 21 | "prefix": "app", 22 | "styles": [ 23 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 24 | "styles.scss" 25 | ], 26 | "scripts": [], 27 | "environmentSource": "environments/environment.ts", 28 | "environments": { 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "e2e": { 35 | "protractor": { 36 | "config": "./protractor.conf.js" 37 | } 38 | }, 39 | "lint": [ 40 | { 41 | "project": "src/tsconfig.app.json", 42 | "exclude": "**/node_modules/**" 43 | }, 44 | { 45 | "project": "src/tsconfig.spec.json", 46 | "exclude": "**/node_modules/**" 47 | }, 48 | { 49 | "project": "e2e/tsconfig.e2e.json", 50 | "exclude": "**/node_modules/**" 51 | } 52 | ], 53 | "test": { 54 | "karma": { 55 | "config": "./karma.conf.js" 56 | } 57 | }, 58 | "defaults": { 59 | "styleExt": "scss", 60 | "component": {} 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/.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 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/AngularASPNETCore2WebApiAuth.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | 2.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/AngularASPNETCore2WebApiAuth.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AngularASPNETCore2WebApiAuth", "AngularASPNETCore2WebApiAuth.csproj", "{27E2D292-D66B-47A1-AFF0-5DBADD669799}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {27E2D292-D66B-47A1-AFF0-5DBADD669799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {27E2D292-D66B-47A1-AFF0-5DBADD669799}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {27E2D292-D66B-47A1-AFF0-5DBADD669799}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {27E2D292-D66B-47A1-AFF0-5DBADD669799}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {6A8165CF-7DCC-4821-9956-A3E79FF34691} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Auth/IJwtFactory.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | 5 | namespace AngularASPNETCore2WebApiAuth.Auth 6 | { 7 | public interface IJwtFactory 8 | { 9 | Task GenerateEncodedToken(string userName, ClaimsIdentity identity); 10 | ClaimsIdentity GenerateClaimsIdentity(string userName, string id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Auth/JwtFactory.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | using System.IdentityModel.Tokens.Jwt; 5 | using System.Security.Claims; 6 | using System.Security.Principal; 7 | using System.Threading.Tasks; 8 | using AngularASPNETCore2WebApiAuth.Models; 9 | using Microsoft.Extensions.Options; 10 | 11 | 12 | namespace AngularASPNETCore2WebApiAuth.Auth 13 | { 14 | public class JwtFactory : IJwtFactory 15 | { 16 | private readonly JwtIssuerOptions _jwtOptions; 17 | 18 | public JwtFactory(IOptions jwtOptions) 19 | { 20 | _jwtOptions = jwtOptions.Value; 21 | ThrowIfInvalidOptions(_jwtOptions); 22 | } 23 | 24 | public async Task GenerateEncodedToken(string userName, ClaimsIdentity identity) 25 | { 26 | var claims = new[] 27 | { 28 | new Claim(JwtRegisteredClaimNames.Sub, userName), 29 | new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()), 30 | new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64), 31 | identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol), 32 | identity.FindFirst(Helpers.Constants.Strings.JwtClaimIdentifiers.Id) 33 | }; 34 | 35 | // Create the JWT security token and encode it. 36 | var jwt = new JwtSecurityToken( 37 | issuer: _jwtOptions.Issuer, 38 | audience: _jwtOptions.Audience, 39 | claims: claims, 40 | notBefore: _jwtOptions.NotBefore, 41 | expires: _jwtOptions.Expiration, 42 | signingCredentials: _jwtOptions.SigningCredentials); 43 | 44 | var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); 45 | 46 | return encodedJwt; 47 | } 48 | 49 | public ClaimsIdentity GenerateClaimsIdentity(string userName, string id) 50 | { 51 | return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[] 52 | { 53 | new Claim(Helpers.Constants.Strings.JwtClaimIdentifiers.Id, id), 54 | new Claim(Helpers.Constants.Strings.JwtClaimIdentifiers.Rol, Helpers.Constants.Strings.JwtClaims.ApiAccess) 55 | }); 56 | } 57 | 58 | /// Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC). 59 | private static long ToUnixEpochDate(DateTime date) 60 | => (long)Math.Round((date.ToUniversalTime() - 61 | new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)) 62 | .TotalSeconds); 63 | 64 | private static void ThrowIfInvalidOptions(JwtIssuerOptions options) 65 | { 66 | if (options == null) throw new ArgumentNullException(nameof(options)); 67 | 68 | if (options.ValidFor <= TimeSpan.Zero) 69 | { 70 | throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor)); 71 | } 72 | 73 | if (options.SigningCredentials == null) 74 | { 75 | throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials)); 76 | } 77 | 78 | if (options.JtiGenerator == null) 79 | { 80 | throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator)); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Controllers/AccountsController.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System.Threading.Tasks; 4 | using AngularASPNETCore2WebApiAuth.Data; 5 | using AngularASPNETCore2WebApiAuth.Helpers; 6 | using AngularASPNETCore2WebApiAuth.Models.Entities; 7 | using AngularASPNETCore2WebApiAuth.ViewModels; 8 | using AutoMapper; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.AspNetCore.Mvc; 11 | 12 | 13 | namespace AngularASPNETCore2WebApiAuth.Controllers 14 | { 15 | [Route("api/[controller]")] 16 | public class AccountsController : Controller 17 | { 18 | private readonly ApplicationDbContext _appDbContext; 19 | private readonly UserManager _userManager; 20 | private readonly IMapper _mapper; 21 | 22 | public AccountsController(UserManager userManager, IMapper mapper, ApplicationDbContext appDbContext) 23 | { 24 | _userManager = userManager; 25 | _mapper = mapper; 26 | _appDbContext = appDbContext; 27 | } 28 | 29 | // POST api/accounts 30 | [HttpPost] 31 | public async Task Post([FromBody]RegistrationViewModel model) 32 | { 33 | if (!ModelState.IsValid) 34 | { 35 | return BadRequest(ModelState); 36 | } 37 | 38 | var userIdentity = _mapper.Map(model); 39 | 40 | var result = await _userManager.CreateAsync(userIdentity, model.Password); 41 | 42 | if (!result.Succeeded) return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState)); 43 | 44 | await _appDbContext.Customers.AddAsync(new Customer { IdentityId = userIdentity.Id, Location = model.Location }); 45 | await _appDbContext.SaveChangesAsync(); 46 | 47 | return new OkObjectResult("Account created"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Controllers/AuthController.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using AngularASPNETCore2WebApiAuth.Auth; 6 | using AngularASPNETCore2WebApiAuth.Helpers; 7 | using AngularASPNETCore2WebApiAuth.Models; 8 | using AngularASPNETCore2WebApiAuth.Models.Entities; 9 | using AngularASPNETCore2WebApiAuth.ViewModels; 10 | using Microsoft.AspNetCore.Identity; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.Extensions.Options; 13 | using Newtonsoft.Json; 14 | 15 | 16 | namespace AngularASPNETCore2WebApiAuth.Controllers 17 | { 18 | [Route("api/[controller]")] 19 | public class AuthController : Controller 20 | { 21 | private readonly UserManager _userManager; 22 | private readonly IJwtFactory _jwtFactory; 23 | private readonly JwtIssuerOptions _jwtOptions; 24 | 25 | public AuthController(UserManager userManager, IJwtFactory jwtFactory, IOptions jwtOptions) 26 | { 27 | _userManager = userManager; 28 | _jwtFactory = jwtFactory; 29 | _jwtOptions = jwtOptions.Value; 30 | } 31 | 32 | // POST api/auth/login 33 | [HttpPost("login")] 34 | public async Task Post([FromBody]CredentialsViewModel credentials) 35 | { 36 | if (!ModelState.IsValid) 37 | { 38 | return BadRequest(ModelState); 39 | } 40 | 41 | var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password); 42 | if (identity == null) 43 | { 44 | return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState)); 45 | } 46 | 47 | var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented }); 48 | return new OkObjectResult(jwt); 49 | } 50 | 51 | private async Task GetClaimsIdentity(string userName, string password) 52 | { 53 | if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)) 54 | return await Task.FromResult(null); 55 | 56 | // get the user to verifty 57 | var userToVerify = await _userManager.FindByNameAsync(userName); 58 | 59 | if (userToVerify == null) return await Task.FromResult(null); 60 | 61 | // check the credentials 62 | if (await _userManager.CheckPasswordAsync(userToVerify, password)) 63 | { 64 | return await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(userName, userToVerify.Id)); 65 | } 66 | 67 | // Credentials are invalid, or account doesn't exist 68 | return await Task.FromResult(null); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Controllers/DashboardController.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Linq; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using AngularASPNETCore2WebApiAuth.Data; 6 | using AngularASPNETCore2WebApiAuth.Models.Entities; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.EntityFrameworkCore; 12 | 13 | 14 | namespace AngularASPNETCore2WebApiAuth.Controllers 15 | { 16 | [Authorize(Policy = "ApiUser")] 17 | [Route("api/[controller]/[action]")] 18 | public class DashboardController : Controller 19 | { 20 | private readonly ClaimsPrincipal _caller; 21 | private readonly ApplicationDbContext _appDbContext; 22 | 23 | public DashboardController(UserManager userManager, ApplicationDbContext appDbContext, IHttpContextAccessor httpContextAccessor) 24 | { 25 | _caller = httpContextAccessor.HttpContext.User; 26 | _appDbContext = appDbContext; 27 | } 28 | 29 | // GET api/dashboard/home 30 | [HttpGet] 31 | public async Task Home() 32 | { 33 | // retrieve the user info 34 | //HttpContext.User 35 | var userId = _caller.Claims.Single(c => c.Type == "id"); 36 | var customer = await _appDbContext.Customers.Include(c => c.Identity).SingleAsync(c => c.Identity.Id == userId.Value); 37 | 38 | return new OkObjectResult(new 39 | { 40 | Message = "This is secure API and user data!", 41 | customer.Identity.FirstName, 42 | customer.Identity.LastName, 43 | customer.Identity.PictureUrl, 44 | customer.Identity.FacebookId, 45 | customer.Location, 46 | customer.Locale, 47 | customer.Gender 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Controllers/ExternalAuthController.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using AngularASPNETCore2WebApiAuth.Auth; 7 | using AngularASPNETCore2WebApiAuth.Data; 8 | using AngularASPNETCore2WebApiAuth.Helpers; 9 | using AngularASPNETCore2WebApiAuth.Models; 10 | using AngularASPNETCore2WebApiAuth.Models.Entities; 11 | using AngularASPNETCore2WebApiAuth.ViewModels; 12 | using Microsoft.AspNetCore.Identity; 13 | using Microsoft.AspNetCore.Mvc; 14 | using Microsoft.Extensions.Options; 15 | using Newtonsoft.Json; 16 | 17 | 18 | namespace AngularASPNETCore2WebApiAuth.Controllers 19 | { 20 | [Route("api/[controller]/[action]")] 21 | public class ExternalAuthController : Controller 22 | { 23 | private readonly ApplicationDbContext _appDbContext; 24 | private readonly UserManager _userManager; 25 | private readonly FacebookAuthSettings _fbAuthSettings; 26 | private readonly IJwtFactory _jwtFactory; 27 | private readonly JwtIssuerOptions _jwtOptions; 28 | private static readonly HttpClient Client = new HttpClient(); 29 | 30 | public ExternalAuthController(IOptions fbAuthSettingsAccessor, UserManager userManager, ApplicationDbContext appDbContext, IJwtFactory jwtFactory, IOptions jwtOptions) 31 | { 32 | _fbAuthSettings = fbAuthSettingsAccessor.Value; 33 | _userManager = userManager; 34 | _appDbContext = appDbContext; 35 | _jwtFactory = jwtFactory; 36 | _jwtOptions = jwtOptions.Value; 37 | } 38 | 39 | // POST api/externalauth/facebook 40 | [HttpPost] 41 | public async Task Facebook([FromBody]FacebookAuthViewModel model) 42 | { 43 | // 1.generate an app access token 44 | var appAccessTokenResponse = await Client.GetStringAsync($"https://graph.facebook.com/oauth/access_token?client_id={_fbAuthSettings.AppId}&client_secret={_fbAuthSettings.AppSecret}&grant_type=client_credentials"); 45 | var appAccessToken = JsonConvert.DeserializeObject(appAccessTokenResponse); 46 | // 2. validate the user access token 47 | var userAccessTokenValidationResponse = await Client.GetStringAsync($"https://graph.facebook.com/debug_token?input_token={model.AccessToken}&access_token={appAccessToken.AccessToken}"); 48 | var userAccessTokenValidation = JsonConvert.DeserializeObject(userAccessTokenValidationResponse); 49 | 50 | if (!userAccessTokenValidation.Data.IsValid) 51 | { 52 | return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid facebook token.", ModelState)); 53 | } 54 | 55 | // 3. we've got a valid token so we can request user data from fb 56 | var userInfoResponse = await Client.GetStringAsync($"https://graph.facebook.com/v2.8/me?fields=id,email,first_name,last_name,name,gender,locale,birthday,picture&access_token={model.AccessToken}"); 57 | var userInfo = JsonConvert.DeserializeObject(userInfoResponse); 58 | 59 | // 4. ready to create the local user account (if necessary) and jwt 60 | var user = await _userManager.FindByEmailAsync(userInfo.Email); 61 | 62 | if (user == null) 63 | { 64 | var appUser = new AppUser 65 | { 66 | FirstName = userInfo.FirstName, 67 | LastName = userInfo.LastName, 68 | FacebookId = userInfo.Id, 69 | Email = userInfo.Email, 70 | UserName = userInfo.Email, 71 | PictureUrl = userInfo.Picture.Data.Url 72 | }; 73 | 74 | var result = await _userManager.CreateAsync(appUser, Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8)); 75 | 76 | if (!result.Succeeded) return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState)); 77 | 78 | await _appDbContext.Customers.AddAsync(new Customer { IdentityId = appUser.Id, Location = "",Locale = userInfo.Locale,Gender = userInfo.Gender}); 79 | await _appDbContext.SaveChangesAsync(); 80 | } 81 | 82 | // generate the jwt for the local user... 83 | var localUser = await _userManager.FindByNameAsync(userInfo.Email); 84 | 85 | if (localUser==null) 86 | { 87 | return BadRequest(Errors.AddErrorToModelState("login_failure", "Failed to create local user account.", ModelState)); 88 | } 89 | 90 | var jwt = await Tokens.GenerateJwt(_jwtFactory.GenerateClaimsIdentity(localUser.UserName, localUser.Id), 91 | _jwtFactory, localUser.UserName, _jwtOptions, new JsonSerializerSettings {Formatting = Formatting.Indented}); 92 | 93 | return new OkObjectResult(jwt); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 |  2 | using AngularASPNETCore2WebApiAuth.Models.Entities; 3 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace AngularASPNETCore2WebApiAuth.Data 7 | { 8 | public class ApplicationDbContext : IdentityDbContext 9 | { 10 | public ApplicationDbContext(DbContextOptions options) 11 | : base(options) 12 | { 13 | } 14 | 15 | public DbSet Customers { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Extensions/ResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace AngularASPNETCore2WebApiAuth.Extensions 5 | { 6 | public static class ResponseExtensions 7 | { 8 | public static void AddApplicationError(this HttpResponse response, string message) 9 | { 10 | response.Headers.Add("Application-Error", message); 11 | // CORS 12 | response.Headers.Add("access-control-expose-headers", "Application-Error"); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Helpers/Constants.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace AngularASPNETCore2WebApiAuth.Helpers 3 | { 4 | public static class Constants 5 | { 6 | public static class Strings 7 | { 8 | public static class JwtClaimIdentifiers 9 | { 10 | public const string Rol = "rol", Id = "id"; 11 | } 12 | 13 | public static class JwtClaims 14 | { 15 | public const string ApiAccess = "api_access"; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Helpers/Errors.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace AngularASPNETCore2WebApiAuth.Helpers 7 | { 8 | public static class Errors 9 | { 10 | public static ModelStateDictionary AddErrorsToModelState(IdentityResult identityResult, ModelStateDictionary modelState) 11 | { 12 | foreach (var e in identityResult.Errors) 13 | { 14 | modelState.TryAddModelError(e.Code, e.Description); 15 | } 16 | 17 | return modelState; 18 | } 19 | 20 | public static ModelStateDictionary AddErrorToModelState(string code, string description, ModelStateDictionary modelState) 21 | { 22 | modelState.TryAddModelError(code, description); 23 | return modelState; 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/Helpers/Tokens.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System.Linq; 4 | using System.Security.Claims; 5 | using System.Threading.Tasks; 6 | using AngularASPNETCore2WebApiAuth.Auth; 7 | using AngularASPNETCore2WebApiAuth.Models; 8 | using Newtonsoft.Json; 9 | 10 | namespace AngularASPNETCore2WebApiAuth.Helpers 11 | { 12 | public class Tokens 13 | { 14 | public static async Task GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory,string userName, JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings) 15 | { 16 | var response = new 17 | { 18 | id = identity.Claims.Single(c => c.Type == "id").Value, 19 | auth_token = await jwtFactory.GenerateEncodedToken(userName, identity), 20 | expires_in = (int)jwtOptions.ValidFor.TotalSeconds 21 | }; 22 | 23 | return JsonConvert.SerializeObject(response, serializerSettings); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Migrations/20180104134211_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using AngularASPNETCore2WebApiAuth.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace AngularASPNETCore2WebApiAuth.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | [Migration("20180104134211_initial")] 15 | partial class initial 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("AccessFailedCount"); 30 | 31 | b.Property("ConcurrencyStamp") 32 | .IsConcurrencyToken(); 33 | 34 | b.Property("Email") 35 | .HasMaxLength(256); 36 | 37 | b.Property("EmailConfirmed"); 38 | 39 | b.Property("FacebookId"); 40 | 41 | b.Property("FirstName"); 42 | 43 | b.Property("LastName"); 44 | 45 | b.Property("LockoutEnabled"); 46 | 47 | b.Property("LockoutEnd"); 48 | 49 | b.Property("NormalizedEmail") 50 | .HasMaxLength(256); 51 | 52 | b.Property("NormalizedUserName") 53 | .HasMaxLength(256); 54 | 55 | b.Property("PasswordHash"); 56 | 57 | b.Property("PhoneNumber"); 58 | 59 | b.Property("PhoneNumberConfirmed"); 60 | 61 | b.Property("PictureUrl"); 62 | 63 | b.Property("SecurityStamp"); 64 | 65 | b.Property("TwoFactorEnabled"); 66 | 67 | b.Property("UserName") 68 | .HasMaxLength(256); 69 | 70 | b.HasKey("Id"); 71 | 72 | b.HasIndex("NormalizedEmail") 73 | .HasName("EmailIndex"); 74 | 75 | b.HasIndex("NormalizedUserName") 76 | .IsUnique() 77 | .HasName("UserNameIndex") 78 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 79 | 80 | b.ToTable("AspNetUsers"); 81 | }); 82 | 83 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.Customer", b => 84 | { 85 | b.Property("Id") 86 | .ValueGeneratedOnAdd(); 87 | 88 | b.Property("Gender"); 89 | 90 | b.Property("IdentityId"); 91 | 92 | b.Property("Locale"); 93 | 94 | b.Property("Location"); 95 | 96 | b.HasKey("Id"); 97 | 98 | b.HasIndex("IdentityId"); 99 | 100 | b.ToTable("Customers"); 101 | }); 102 | 103 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 104 | { 105 | b.Property("Id") 106 | .ValueGeneratedOnAdd(); 107 | 108 | b.Property("ConcurrencyStamp") 109 | .IsConcurrencyToken(); 110 | 111 | b.Property("Name") 112 | .HasMaxLength(256); 113 | 114 | b.Property("NormalizedName") 115 | .HasMaxLength(256); 116 | 117 | b.HasKey("Id"); 118 | 119 | b.HasIndex("NormalizedName") 120 | .IsUnique() 121 | .HasName("RoleNameIndex") 122 | .HasFilter("[NormalizedName] IS NOT NULL"); 123 | 124 | b.ToTable("AspNetRoles"); 125 | }); 126 | 127 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 128 | { 129 | b.Property("Id") 130 | .ValueGeneratedOnAdd(); 131 | 132 | b.Property("ClaimType"); 133 | 134 | b.Property("ClaimValue"); 135 | 136 | b.Property("RoleId") 137 | .IsRequired(); 138 | 139 | b.HasKey("Id"); 140 | 141 | b.HasIndex("RoleId"); 142 | 143 | b.ToTable("AspNetRoleClaims"); 144 | }); 145 | 146 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 147 | { 148 | b.Property("Id") 149 | .ValueGeneratedOnAdd(); 150 | 151 | b.Property("ClaimType"); 152 | 153 | b.Property("ClaimValue"); 154 | 155 | b.Property("UserId") 156 | .IsRequired(); 157 | 158 | b.HasKey("Id"); 159 | 160 | b.HasIndex("UserId"); 161 | 162 | b.ToTable("AspNetUserClaims"); 163 | }); 164 | 165 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 166 | { 167 | b.Property("LoginProvider"); 168 | 169 | b.Property("ProviderKey"); 170 | 171 | b.Property("ProviderDisplayName"); 172 | 173 | b.Property("UserId") 174 | .IsRequired(); 175 | 176 | b.HasKey("LoginProvider", "ProviderKey"); 177 | 178 | b.HasIndex("UserId"); 179 | 180 | b.ToTable("AspNetUserLogins"); 181 | }); 182 | 183 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 184 | { 185 | b.Property("UserId"); 186 | 187 | b.Property("RoleId"); 188 | 189 | b.HasKey("UserId", "RoleId"); 190 | 191 | b.HasIndex("RoleId"); 192 | 193 | b.ToTable("AspNetUserRoles"); 194 | }); 195 | 196 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 197 | { 198 | b.Property("UserId"); 199 | 200 | b.Property("LoginProvider"); 201 | 202 | b.Property("Name"); 203 | 204 | b.Property("Value"); 205 | 206 | b.HasKey("UserId", "LoginProvider", "Name"); 207 | 208 | b.ToTable("AspNetUserTokens"); 209 | }); 210 | 211 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.Customer", b => 212 | { 213 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser", "Identity") 214 | .WithMany() 215 | .HasForeignKey("IdentityId"); 216 | }); 217 | 218 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 219 | { 220 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 221 | .WithMany() 222 | .HasForeignKey("RoleId") 223 | .OnDelete(DeleteBehavior.Cascade); 224 | }); 225 | 226 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 227 | { 228 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 229 | .WithMany() 230 | .HasForeignKey("UserId") 231 | .OnDelete(DeleteBehavior.Cascade); 232 | }); 233 | 234 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 235 | { 236 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 237 | .WithMany() 238 | .HasForeignKey("UserId") 239 | .OnDelete(DeleteBehavior.Cascade); 240 | }); 241 | 242 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 243 | { 244 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 245 | .WithMany() 246 | .HasForeignKey("RoleId") 247 | .OnDelete(DeleteBehavior.Cascade); 248 | 249 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 250 | .WithMany() 251 | .HasForeignKey("UserId") 252 | .OnDelete(DeleteBehavior.Cascade); 253 | }); 254 | 255 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 256 | { 257 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 258 | .WithMany() 259 | .HasForeignKey("UserId") 260 | .OnDelete(DeleteBehavior.Cascade); 261 | }); 262 | #pragma warning restore 612, 618 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Migrations/20180104134211_initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace AngularASPNETCore2WebApiAuth.Migrations 7 | { 8 | public partial class initial : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "AspNetRoles", 14 | columns: table => new 15 | { 16 | Id = table.Column(type: "nvarchar(450)", nullable: false), 17 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 18 | Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 19 | NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "AspNetUsers", 28 | columns: table => new 29 | { 30 | Id = table.Column(type: "nvarchar(450)", nullable: false), 31 | AccessFailedCount = table.Column(type: "int", nullable: false), 32 | ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), 33 | Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 34 | EmailConfirmed = table.Column(type: "bit", nullable: false), 35 | FacebookId = table.Column(type: "bigint", nullable: true), 36 | FirstName = table.Column(type: "nvarchar(max)", nullable: true), 37 | LastName = table.Column(type: "nvarchar(max)", nullable: true), 38 | LockoutEnabled = table.Column(type: "bit", nullable: false), 39 | LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), 40 | NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 41 | NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), 42 | PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), 43 | PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), 44 | PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), 45 | PictureUrl = table.Column(type: "nvarchar(max)", nullable: true), 46 | SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), 47 | TwoFactorEnabled = table.Column(type: "bit", nullable: false), 48 | UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true) 49 | }, 50 | constraints: table => 51 | { 52 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 53 | }); 54 | 55 | migrationBuilder.CreateTable( 56 | name: "AspNetRoleClaims", 57 | columns: table => new 58 | { 59 | Id = table.Column(type: "int", nullable: false) 60 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 61 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 62 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), 63 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 64 | }, 65 | constraints: table => 66 | { 67 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 68 | table.ForeignKey( 69 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 70 | column: x => x.RoleId, 71 | principalTable: "AspNetRoles", 72 | principalColumn: "Id", 73 | onDelete: ReferentialAction.Cascade); 74 | }); 75 | 76 | migrationBuilder.CreateTable( 77 | name: "AspNetUserClaims", 78 | columns: table => new 79 | { 80 | Id = table.Column(type: "int", nullable: false) 81 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 82 | ClaimType = table.Column(type: "nvarchar(max)", nullable: true), 83 | ClaimValue = table.Column(type: "nvarchar(max)", nullable: true), 84 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 85 | }, 86 | constraints: table => 87 | { 88 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 89 | table.ForeignKey( 90 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 91 | column: x => x.UserId, 92 | principalTable: "AspNetUsers", 93 | principalColumn: "Id", 94 | onDelete: ReferentialAction.Cascade); 95 | }); 96 | 97 | migrationBuilder.CreateTable( 98 | name: "AspNetUserLogins", 99 | columns: table => new 100 | { 101 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 102 | ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), 103 | ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), 104 | UserId = table.Column(type: "nvarchar(450)", nullable: false) 105 | }, 106 | constraints: table => 107 | { 108 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 109 | table.ForeignKey( 110 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 111 | column: x => x.UserId, 112 | principalTable: "AspNetUsers", 113 | principalColumn: "Id", 114 | onDelete: ReferentialAction.Cascade); 115 | }); 116 | 117 | migrationBuilder.CreateTable( 118 | name: "AspNetUserRoles", 119 | columns: table => new 120 | { 121 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 122 | RoleId = table.Column(type: "nvarchar(450)", nullable: false) 123 | }, 124 | constraints: table => 125 | { 126 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 127 | table.ForeignKey( 128 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 129 | column: x => x.RoleId, 130 | principalTable: "AspNetRoles", 131 | principalColumn: "Id", 132 | onDelete: ReferentialAction.Cascade); 133 | table.ForeignKey( 134 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 135 | column: x => x.UserId, 136 | principalTable: "AspNetUsers", 137 | principalColumn: "Id", 138 | onDelete: ReferentialAction.Cascade); 139 | }); 140 | 141 | migrationBuilder.CreateTable( 142 | name: "AspNetUserTokens", 143 | columns: table => new 144 | { 145 | UserId = table.Column(type: "nvarchar(450)", nullable: false), 146 | LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), 147 | Name = table.Column(type: "nvarchar(450)", nullable: false), 148 | Value = table.Column(type: "nvarchar(max)", nullable: true) 149 | }, 150 | constraints: table => 151 | { 152 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 153 | table.ForeignKey( 154 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 155 | column: x => x.UserId, 156 | principalTable: "AspNetUsers", 157 | principalColumn: "Id", 158 | onDelete: ReferentialAction.Cascade); 159 | }); 160 | 161 | migrationBuilder.CreateTable( 162 | name: "Customers", 163 | columns: table => new 164 | { 165 | Id = table.Column(type: "int", nullable: false) 166 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 167 | Gender = table.Column(type: "nvarchar(max)", nullable: true), 168 | IdentityId = table.Column(type: "nvarchar(450)", nullable: true), 169 | Locale = table.Column(type: "nvarchar(max)", nullable: true), 170 | Location = table.Column(type: "nvarchar(max)", nullable: true) 171 | }, 172 | constraints: table => 173 | { 174 | table.PrimaryKey("PK_Customers", x => x.Id); 175 | table.ForeignKey( 176 | name: "FK_Customers_AspNetUsers_IdentityId", 177 | column: x => x.IdentityId, 178 | principalTable: "AspNetUsers", 179 | principalColumn: "Id", 180 | onDelete: ReferentialAction.Restrict); 181 | }); 182 | 183 | migrationBuilder.CreateIndex( 184 | name: "IX_AspNetRoleClaims_RoleId", 185 | table: "AspNetRoleClaims", 186 | column: "RoleId"); 187 | 188 | migrationBuilder.CreateIndex( 189 | name: "RoleNameIndex", 190 | table: "AspNetRoles", 191 | column: "NormalizedName", 192 | unique: true, 193 | filter: "[NormalizedName] IS NOT NULL"); 194 | 195 | migrationBuilder.CreateIndex( 196 | name: "IX_AspNetUserClaims_UserId", 197 | table: "AspNetUserClaims", 198 | column: "UserId"); 199 | 200 | migrationBuilder.CreateIndex( 201 | name: "IX_AspNetUserLogins_UserId", 202 | table: "AspNetUserLogins", 203 | column: "UserId"); 204 | 205 | migrationBuilder.CreateIndex( 206 | name: "IX_AspNetUserRoles_RoleId", 207 | table: "AspNetUserRoles", 208 | column: "RoleId"); 209 | 210 | migrationBuilder.CreateIndex( 211 | name: "EmailIndex", 212 | table: "AspNetUsers", 213 | column: "NormalizedEmail"); 214 | 215 | migrationBuilder.CreateIndex( 216 | name: "UserNameIndex", 217 | table: "AspNetUsers", 218 | column: "NormalizedUserName", 219 | unique: true, 220 | filter: "[NormalizedUserName] IS NOT NULL"); 221 | 222 | migrationBuilder.CreateIndex( 223 | name: "IX_Customers_IdentityId", 224 | table: "Customers", 225 | column: "IdentityId"); 226 | } 227 | 228 | protected override void Down(MigrationBuilder migrationBuilder) 229 | { 230 | migrationBuilder.DropTable( 231 | name: "AspNetRoleClaims"); 232 | 233 | migrationBuilder.DropTable( 234 | name: "AspNetUserClaims"); 235 | 236 | migrationBuilder.DropTable( 237 | name: "AspNetUserLogins"); 238 | 239 | migrationBuilder.DropTable( 240 | name: "AspNetUserRoles"); 241 | 242 | migrationBuilder.DropTable( 243 | name: "AspNetUserTokens"); 244 | 245 | migrationBuilder.DropTable( 246 | name: "Customers"); 247 | 248 | migrationBuilder.DropTable( 249 | name: "AspNetRoles"); 250 | 251 | migrationBuilder.DropTable( 252 | name: "AspNetUsers"); 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using AngularASPNETCore2WebApiAuth.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace AngularASPNETCore2WebApiAuth.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("AccessFailedCount"); 29 | 30 | b.Property("ConcurrencyStamp") 31 | .IsConcurrencyToken(); 32 | 33 | b.Property("Email") 34 | .HasMaxLength(256); 35 | 36 | b.Property("EmailConfirmed"); 37 | 38 | b.Property("FacebookId"); 39 | 40 | b.Property("FirstName"); 41 | 42 | b.Property("LastName"); 43 | 44 | b.Property("LockoutEnabled"); 45 | 46 | b.Property("LockoutEnd"); 47 | 48 | b.Property("NormalizedEmail") 49 | .HasMaxLength(256); 50 | 51 | b.Property("NormalizedUserName") 52 | .HasMaxLength(256); 53 | 54 | b.Property("PasswordHash"); 55 | 56 | b.Property("PhoneNumber"); 57 | 58 | b.Property("PhoneNumberConfirmed"); 59 | 60 | b.Property("PictureUrl"); 61 | 62 | b.Property("SecurityStamp"); 63 | 64 | b.Property("TwoFactorEnabled"); 65 | 66 | b.Property("UserName") 67 | .HasMaxLength(256); 68 | 69 | b.HasKey("Id"); 70 | 71 | b.HasIndex("NormalizedEmail") 72 | .HasName("EmailIndex"); 73 | 74 | b.HasIndex("NormalizedUserName") 75 | .IsUnique() 76 | .HasName("UserNameIndex") 77 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 78 | 79 | b.ToTable("AspNetUsers"); 80 | }); 81 | 82 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.Customer", b => 83 | { 84 | b.Property("Id") 85 | .ValueGeneratedOnAdd(); 86 | 87 | b.Property("Gender"); 88 | 89 | b.Property("IdentityId"); 90 | 91 | b.Property("Locale"); 92 | 93 | b.Property("Location"); 94 | 95 | b.HasKey("Id"); 96 | 97 | b.HasIndex("IdentityId"); 98 | 99 | b.ToTable("Customers"); 100 | }); 101 | 102 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 103 | { 104 | b.Property("Id") 105 | .ValueGeneratedOnAdd(); 106 | 107 | b.Property("ConcurrencyStamp") 108 | .IsConcurrencyToken(); 109 | 110 | b.Property("Name") 111 | .HasMaxLength(256); 112 | 113 | b.Property("NormalizedName") 114 | .HasMaxLength(256); 115 | 116 | b.HasKey("Id"); 117 | 118 | b.HasIndex("NormalizedName") 119 | .IsUnique() 120 | .HasName("RoleNameIndex") 121 | .HasFilter("[NormalizedName] IS NOT NULL"); 122 | 123 | b.ToTable("AspNetRoles"); 124 | }); 125 | 126 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 127 | { 128 | b.Property("Id") 129 | .ValueGeneratedOnAdd(); 130 | 131 | b.Property("ClaimType"); 132 | 133 | b.Property("ClaimValue"); 134 | 135 | b.Property("RoleId") 136 | .IsRequired(); 137 | 138 | b.HasKey("Id"); 139 | 140 | b.HasIndex("RoleId"); 141 | 142 | b.ToTable("AspNetRoleClaims"); 143 | }); 144 | 145 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 146 | { 147 | b.Property("Id") 148 | .ValueGeneratedOnAdd(); 149 | 150 | b.Property("ClaimType"); 151 | 152 | b.Property("ClaimValue"); 153 | 154 | b.Property("UserId") 155 | .IsRequired(); 156 | 157 | b.HasKey("Id"); 158 | 159 | b.HasIndex("UserId"); 160 | 161 | b.ToTable("AspNetUserClaims"); 162 | }); 163 | 164 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 165 | { 166 | b.Property("LoginProvider"); 167 | 168 | b.Property("ProviderKey"); 169 | 170 | b.Property("ProviderDisplayName"); 171 | 172 | b.Property("UserId") 173 | .IsRequired(); 174 | 175 | b.HasKey("LoginProvider", "ProviderKey"); 176 | 177 | b.HasIndex("UserId"); 178 | 179 | b.ToTable("AspNetUserLogins"); 180 | }); 181 | 182 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 183 | { 184 | b.Property("UserId"); 185 | 186 | b.Property("RoleId"); 187 | 188 | b.HasKey("UserId", "RoleId"); 189 | 190 | b.HasIndex("RoleId"); 191 | 192 | b.ToTable("AspNetUserRoles"); 193 | }); 194 | 195 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 196 | { 197 | b.Property("UserId"); 198 | 199 | b.Property("LoginProvider"); 200 | 201 | b.Property("Name"); 202 | 203 | b.Property("Value"); 204 | 205 | b.HasKey("UserId", "LoginProvider", "Name"); 206 | 207 | b.ToTable("AspNetUserTokens"); 208 | }); 209 | 210 | modelBuilder.Entity("AngularASPNETCore2WebApiAuth.Models.Entities.Customer", b => 211 | { 212 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser", "Identity") 213 | .WithMany() 214 | .HasForeignKey("IdentityId"); 215 | }); 216 | 217 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 218 | { 219 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 220 | .WithMany() 221 | .HasForeignKey("RoleId") 222 | .OnDelete(DeleteBehavior.Cascade); 223 | }); 224 | 225 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 226 | { 227 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 228 | .WithMany() 229 | .HasForeignKey("UserId") 230 | .OnDelete(DeleteBehavior.Cascade); 231 | }); 232 | 233 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 234 | { 235 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 236 | .WithMany() 237 | .HasForeignKey("UserId") 238 | .OnDelete(DeleteBehavior.Cascade); 239 | }); 240 | 241 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 242 | { 243 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 244 | .WithMany() 245 | .HasForeignKey("RoleId") 246 | .OnDelete(DeleteBehavior.Cascade); 247 | 248 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 249 | .WithMany() 250 | .HasForeignKey("UserId") 251 | .OnDelete(DeleteBehavior.Cascade); 252 | }); 253 | 254 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 255 | { 256 | b.HasOne("AngularASPNETCore2WebApiAuth.Models.Entities.AppUser") 257 | .WithMany() 258 | .HasForeignKey("UserId") 259 | .OnDelete(DeleteBehavior.Cascade); 260 | }); 261 | #pragma warning restore 612, 618 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/Models/Entities/AppUser.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.AspNetCore.Identity; 3 | 4 | namespace AngularASPNETCore2WebApiAuth.Models.Entities 5 | { 6 | // Add profile data for application users by adding properties to this class 7 | public class AppUser : IdentityUser 8 | { 9 | // Extended Properties 10 | public string FirstName { get; set; } 11 | 12 | public string LastName { get; set; } 13 | public long? FacebookId { get; set; } 14 | public string PictureUrl { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Models/Entities/Customer.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace AngularASPNETCore2WebApiAuth.Models.Entities 4 | { 5 | public class Customer 6 | { 7 | public int Id { get; set; } 8 | public string IdentityId { get; set; } 9 | public AppUser Identity { get; set; } // navigation property 10 | public string Location { get; set; } 11 | public string Locale { get; set; } 12 | public string Gender { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Models/FacebookApiResponses.cs: -------------------------------------------------------------------------------- 1 | 2 | using Newtonsoft.Json; 3 | 4 | namespace AngularASPNETCore2WebApiAuth.Models 5 | { 6 | internal class FacebookUserData 7 | { 8 | public long Id { get; set; } 9 | public string Email { get; set; } 10 | public string Name { get; set; } 11 | [JsonProperty("first_name")] 12 | public string FirstName { get; set; } 13 | [JsonProperty("last_name")] 14 | public string LastName { get; set; } 15 | public string Gender { get; set; } 16 | public string Locale { get; set; } 17 | public FacebookPictureData Picture { get; set; } 18 | } 19 | 20 | internal class FacebookPictureData 21 | { 22 | public FacebookPicture Data { get; set; } 23 | } 24 | 25 | internal class FacebookPicture 26 | { 27 | public int Height { get; set; } 28 | public int Width { get; set; } 29 | [JsonProperty("is_silhouette")] 30 | public bool IsSilhouette { get; set; } 31 | public string Url { get; set; } 32 | } 33 | 34 | internal class FacebookUserAccessTokenData 35 | { 36 | [JsonProperty("app_id")] 37 | public long AppId { get; set; } 38 | public string Type { get; set; } 39 | public string Application { get; set; } 40 | [JsonProperty("expires_at")] 41 | public long ExpiresAt { get; set; } 42 | [JsonProperty("is_valid")] 43 | public bool IsValid { get; set; } 44 | [JsonProperty("user_id")] 45 | public long UserId { get; set; } 46 | } 47 | 48 | internal class FacebookUserAccessTokenValidation 49 | { 50 | public FacebookUserAccessTokenData Data { get; set; } 51 | } 52 | 53 | internal class FacebookAppAccessToken 54 | { 55 | [JsonProperty("token_type")] 56 | public string TokenType { get; set; } 57 | [JsonProperty("access_token")] 58 | public string AccessToken { get; set; } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Models/FacebookAuthSettings.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | namespace AngularASPNETCore2WebApiAuth.Models 5 | { 6 | public class FacebookAuthSettings 7 | { 8 | public string AppId { get; set; } 9 | public string AppSecret { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Models/JwtIssuerOptions.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace AngularASPNETCore2WebApiAuth.Models 8 | { 9 | public class JwtIssuerOptions 10 | { 11 | /// 12 | /// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT. 13 | /// 14 | public string Issuer { get; set; } 15 | 16 | /// 17 | /// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT. 18 | /// 19 | public string Subject { get; set; } 20 | 21 | /// 22 | /// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for. 23 | /// 24 | public string Audience { get; set; } 25 | 26 | /// 27 | /// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. 28 | /// 29 | public DateTime Expiration => IssuedAt.Add(ValidFor); 30 | 31 | /// 32 | /// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. 33 | /// 34 | public DateTime NotBefore => DateTime.UtcNow; 35 | 36 | /// 37 | /// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued. 38 | /// 39 | public DateTime IssuedAt => DateTime.UtcNow; 40 | 41 | /// 42 | /// Set the timespan the token will be valid for (default is 120 min) 43 | /// 44 | public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(120); 45 | 46 | 47 | 48 | /// 49 | /// "jti" (JWT ID) Claim (default ID is a GUID) 50 | /// 51 | public Func> JtiGenerator => 52 | () => Task.FromResult(Guid.NewGuid().ToString()); 53 | 54 | /// 55 | /// The signing key to use when generating tokens. 56 | /// 57 | public SigningCredentials SigningCredentials { get; set; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | 6 | namespace AngularASPNETCore2WebApiAuth 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | BuildWebHost(args).Run(); 13 | } 14 | 15 | public static IWebHost BuildWebHost(string[] args) => 16 | WebHost.CreateDefaultBuilder(args) 17 | .UseStartup() 18 | .Build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iis": { 6 | "applicationUrl": "http://localhost/AngularASPNETCore2WebApiAuth", 7 | "sslPort": 0 8 | }, 9 | "iisExpress": { 10 | "applicationUrl": "http://localhost:5000", 11 | "sslPort": 0 12 | } 13 | }, 14 | "profiles": { 15 | "IIS Express": { 16 | "commandName": "Project", 17 | "launchBrowser": true, 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | }, 21 | "applicationUrl": "http://localhost:5000" 22 | }, 23 | "AngularASPNETCore2WebApiAuth": { 24 | "commandName": "Project", 25 | "launchBrowser": true, 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | }, 29 | "applicationUrl": "http://localhost:5000" 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Startup.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | using System; 4 | using System.Net; 5 | using System.Text; 6 | using AngularASPNETCore2WebApiAuth.Auth; 7 | using AngularASPNETCore2WebApiAuth.Data; 8 | using AngularASPNETCore2WebApiAuth.Extensions; 9 | using AngularASPNETCore2WebApiAuth.Helpers; 10 | using AngularASPNETCore2WebApiAuth.Models; 11 | using AngularASPNETCore2WebApiAuth.Models.Entities; 12 | using Microsoft.AspNetCore.Builder; 13 | using Microsoft.AspNetCore.Hosting; 14 | using Microsoft.EntityFrameworkCore; 15 | using Microsoft.Extensions.Configuration; 16 | using Microsoft.Extensions.DependencyInjection; 17 | using AutoMapper; 18 | using FluentValidation.AspNetCore; 19 | using Microsoft.AspNetCore.Authentication.JwtBearer; 20 | using Microsoft.AspNetCore.Diagnostics; 21 | using Microsoft.AspNetCore.Http; 22 | using Microsoft.AspNetCore.Identity; 23 | using Microsoft.Extensions.DependencyInjection.Extensions; 24 | using Microsoft.IdentityModel.Tokens; 25 | 26 | 27 | namespace AngularASPNETCore2WebApiAuth 28 | { 29 | public class Startup 30 | { 31 | private const string SecretKey = "iNivDmHLpUA223sqsfhqGbMRdRj1PVkH"; // todo: get this from somewhere secure 32 | private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)); 33 | 34 | public Startup(IConfiguration configuration) 35 | { 36 | Configuration = configuration; 37 | } 38 | 39 | public IConfiguration Configuration { get; } 40 | 41 | // This method gets called by the runtime. Use this method to add services to the container. 42 | public void ConfigureServices(IServiceCollection services) 43 | { 44 | // Add framework services. 45 | services.AddDbContext(options => 46 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), 47 | b => b.MigrationsAssembly("AngularASPNETCore2WebApiAuth"))); 48 | 49 | services.AddSingleton(); 50 | 51 | // Register the ConfigurationBuilder instance of FacebookAuthSettings 52 | services.Configure(Configuration.GetSection(nameof(FacebookAuthSettings))); 53 | 54 | services.TryAddTransient(); 55 | 56 | // jwt wire up 57 | // Get options from app settings 58 | var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions)); 59 | 60 | // Configure JwtIssuerOptions 61 | services.Configure(options => 62 | { 63 | options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)]; 64 | options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)]; 65 | options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256); 66 | }); 67 | 68 | var tokenValidationParameters = new TokenValidationParameters 69 | { 70 | ValidateIssuer = true, 71 | ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)], 72 | 73 | ValidateAudience = true, 74 | ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)], 75 | 76 | ValidateIssuerSigningKey = true, 77 | IssuerSigningKey = _signingKey, 78 | 79 | RequireExpirationTime = false, 80 | ValidateLifetime = true, 81 | ClockSkew = TimeSpan.Zero 82 | }; 83 | 84 | services.AddAuthentication(options => 85 | { 86 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 87 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 88 | 89 | }).AddJwtBearer(configureOptions => 90 | { 91 | configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)]; 92 | configureOptions.TokenValidationParameters = tokenValidationParameters; 93 | configureOptions.SaveToken = true; 94 | }); 95 | 96 | // api user claim policy 97 | services.AddAuthorization(options => 98 | { 99 | options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess)); 100 | }); 101 | 102 | // add identity 103 | var builder = services.AddIdentityCore(o => 104 | { 105 | // configure identity options 106 | o.Password.RequireDigit = false; 107 | o.Password.RequireLowercase = false; 108 | o.Password.RequireUppercase = false; 109 | o.Password.RequireNonAlphanumeric = false; 110 | o.Password.RequiredLength = 6; 111 | }); 112 | builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services); 113 | builder.AddEntityFrameworkStores().AddDefaultTokenProviders(); 114 | 115 | services.AddAutoMapper(); 116 | services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining()); 117 | } 118 | 119 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 120 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 121 | { 122 | if (env.IsDevelopment()) 123 | { 124 | app.UseDeveloperExceptionPage(); 125 | } 126 | 127 | app.UseExceptionHandler( 128 | builder => 129 | { 130 | builder.Run( 131 | async context => 132 | { 133 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 134 | context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); 135 | 136 | var error = context.Features.Get(); 137 | if (error != null) 138 | { 139 | context.Response.AddApplicationError(error.Error.Message); 140 | await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false); 141 | } 142 | }); 143 | }); 144 | 145 | app.UseAuthentication(); 146 | app.UseDefaultFiles(); 147 | app.UseStaticFiles(); 148 | app.UseMvc(); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/ViewModels/CredentialsViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | using AngularASPNETCore2WebApiAuth.ViewModels.Validations; 3 | using FluentValidation.Attributes; 4 | 5 | namespace AngularASPNETCore2WebApiAuth.ViewModels 6 | { 7 | [Validator(typeof(CredentialsViewModelValidator))] 8 | public class CredentialsViewModel 9 | { 10 | public string UserName { get; set; } 11 | public string Password { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ViewModels/FacebookAuthViewModel.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace AngularASPNETCore2WebApiAuth.ViewModels 4 | { 5 | 6 | public class FacebookAuthViewModel 7 | { 8 | public string AccessToken { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ViewModels/Mappings/ViewModelToEntityMappingProfile.cs: -------------------------------------------------------------------------------- 1 |  2 | using AngularASPNETCore2WebApiAuth.Models.Entities; 3 | using AutoMapper; 4 | 5 | 6 | namespace AngularASPNETCore2WebApiAuth.ViewModels.Mappings 7 | { 8 | public class ViewModelToEntityMappingProfile : Profile 9 | { 10 | public ViewModelToEntityMappingProfile() 11 | { 12 | CreateMap().ForMember(au => au.UserName, map => map.MapFrom(vm => vm.Email)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ViewModels/RegistrationViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AngularASPNETCore2WebApiAuth.ViewModels 4 | { 5 | public class RegistrationViewModel 6 | { 7 | public string Email { get; set; } 8 | public string Password { get; set; } 9 | public string FirstName { get; set; } 10 | public string LastName { get; set; } 11 | public string Location { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ViewModels/Validations/CredentialsViewModelValidator.cs: -------------------------------------------------------------------------------- 1 |  2 | using FluentValidation; 3 | 4 | 5 | namespace AngularASPNETCore2WebApiAuth.ViewModels.Validations 6 | { 7 | public class CredentialsViewModelValidator : AbstractValidator 8 | { 9 | public CredentialsViewModelValidator() 10 | { 11 | RuleFor(vm => vm.UserName).NotEmpty().WithMessage("Username cannot be empty"); 12 | RuleFor(vm => vm.Password).NotEmpty().WithMessage("Password cannot be empty"); 13 | RuleFor(vm => vm.Password).Length(6, 12).WithMessage("Password must be between 6 and 12 characters"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ViewModels/Validations/RegistrationViewModelValidator.cs: -------------------------------------------------------------------------------- 1 |  2 | using FluentValidation; 3 | 4 | 5 | namespace AngularASPNETCore2WebApiAuth.ViewModels.Validations 6 | { 7 | public class RegistrationViewModelValidator : AbstractValidator 8 | { 9 | public RegistrationViewModelValidator() 10 | { 11 | RuleFor(vm => vm.Email).NotEmpty().WithMessage("Email cannot be empty"); 12 | RuleFor(vm => vm.Password).NotEmpty().WithMessage("Password cannot be empty"); 13 | RuleFor(vm => vm.FirstName).NotEmpty().WithMessage("FirstName cannot be empty"); 14 | RuleFor(vm => vm.LastName).NotEmpty().WithMessage("LastName cannot be empty"); 15 | RuleFor(vm => vm.Location).NotEmpty().WithMessage("Location cannot be empty"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "ConnectionStrings": { 11 | "DefaultConnection": "Server=.\\SQLExpress;Database=AngularASPNETCore2WebApiAuth;Trusted_Connection=True;MultipleActiveResultSets=true" 12 | }, 13 | "JwtIssuerOptions": { 14 | "Issuer": "webApi", 15 | "Audience": "http://localhost:5000/" 16 | }, 17 | "FacebookAuthSettings": { 18 | "AppId": "1528751870549294", 19 | "AppSecret": "bed77aaafe5c57fc8656c0a1e2533760" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | }, 15 | "ConnectionStrings": { 16 | "DefaultConnection": "Server=.\\SQLExpress;Database=AngularASPNETCore2WebApiAuth;Trusted_Connection=True;MultipleActiveResultSets=true" 17 | }, 18 | "JwtIssuerOptions": { 19 | "Issuer": "webApi", 20 | "Audience": "http://localhost:5000/" 21 | }, 22 | "FacebookAuthSettings": { 23 | "AppId": "1528751870549294", 24 | "AppSecret": "bed77aaafe5c57fc8656c0a1e2533760" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('jwt-auth-demo App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/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/cli'], 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/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-auth-demo", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --prod", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^5.0.0", 16 | "@angular/common": "^5.0.0", 17 | "@angular/compiler": "^5.0.0", 18 | "@angular/core": "^5.0.0", 19 | "@angular/forms": "^5.0.0", 20 | "@angular/http": "^5.0.0", 21 | "@angular/platform-browser": "^5.0.0", 22 | "@angular/platform-browser-dynamic": "^5.0.0", 23 | "@angular/router": "^5.0.0", 24 | "bootstrap": "4.0.0-beta.2", 25 | "core-js": "^2.4.1", 26 | "rxjs": "^5.5.2", 27 | "zone.js": "^0.8.14" 28 | }, 29 | "devDependencies": { 30 | "@angular/cli": "1.6.3", 31 | "@angular/compiler-cli": "^5.0.0", 32 | "@angular/language-service": "^5.0.0", 33 | "@types/jasmine": "~2.5.53", 34 | "@types/jasminewd2": "~2.0.2", 35 | "@types/node": "~6.0.60", 36 | "codelyzer": "^4.0.1", 37 | "jasmine-core": "~2.6.2", 38 | "jasmine-spec-reporter": "~4.1.0", 39 | "karma": "~1.7.0", 40 | "karma-chrome-launcher": "~2.1.1", 41 | "karma-cli": "~1.0.1", 42 | "karma-coverage-istanbul-reporter": "^1.2.1", 43 | "karma-jasmine": "~1.1.0", 44 | "karma-jasmine-html-reporter": "^0.2.2", 45 | "protractor": "~5.1.2", 46 | "ts-node": "~3.2.0", 47 | "tslint": "~5.7.0", 48 | "typescript": "~2.4.2" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/src/app/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { SharedModule } from '../shared/modules/shared.module'; 5 | 6 | import { UserService } from '../shared/services/user.service'; 7 | 8 | import { EmailValidator } from '../directives/email.validator.directive'; 9 | 10 | import { routing } from './account.routing'; 11 | import { RegistrationFormComponent } from './registration-form/registration-form.component'; 12 | import { LoginFormComponent } from './login-form/login-form.component'; 13 | import { FacebookLoginComponent } from './facebook-login/facebook-login.component'; 14 | 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule,FormsModule,routing,SharedModule 19 | ], 20 | declarations: [RegistrationFormComponent,EmailValidator, LoginFormComponent, FacebookLoginComponent], 21 | providers: [ UserService ] 22 | }) 23 | export class AccountModule { } 24 | -------------------------------------------------------------------------------- /src/src/app/account/account.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { RegistrationFormComponent } from './registration-form/registration-form.component'; 5 | import { LoginFormComponent } from './login-form/login-form.component'; 6 | import { FacebookLoginComponent } from './facebook-login/facebook-login.component'; 7 | 8 | export const routing: ModuleWithProviders = RouterModule.forChild([ 9 | { path: 'register', component: RegistrationFormComponent}, 10 | { path: 'login', component: LoginFormComponent}, 11 | { path: 'facebook-login', component: FacebookLoginComponent} 12 | ]); -------------------------------------------------------------------------------- /src/src/app/account/facebook-login/facebook-login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /src/src/app/account/facebook-login/facebook-login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/account/facebook-login/facebook-login.component.scss -------------------------------------------------------------------------------- /src/src/app/account/facebook-login/facebook-login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FacebookLoginComponent } from './facebook-login.component'; 4 | 5 | describe('FacebookLoginComponent', () => { 6 | let component: FacebookLoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FacebookLoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FacebookLoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/account/facebook-login/facebook-login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { UserService } from '../../shared/services/user.service'; 4 | import { Router } from '@angular/router'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-facebook-login', 9 | templateUrl: './facebook-login.component.html', 10 | styleUrls: ['./facebook-login.component.scss'] 11 | }) 12 | export class FacebookLoginComponent { 13 | 14 | private authWindow: Window; 15 | failed: boolean; 16 | error: string; 17 | errorDescription: string; 18 | isRequesting: boolean; 19 | 20 | launchFbLogin() { 21 | this.authWindow = window.open('https://www.facebook.com/v2.11/dialog/oauth?&response_type=token&display=popup&client_id=1528751870549294&display=popup&redirect_uri=http://localhost:5000/facebook-auth.html&scope=email',null,'width=600,height=400'); 22 | } 23 | 24 | constructor(private userService: UserService, private router: Router) { 25 | if (window.addEventListener) { 26 | window.addEventListener("message", this.handleMessage.bind(this), false); 27 | } else { 28 | (window).attachEvent("onmessage", this.handleMessage.bind(this)); 29 | } 30 | } 31 | 32 | handleMessage(event: Event) { 33 | const message = event as MessageEvent; 34 | // Only trust messages from the below origin. 35 | if (message.origin !== "http://localhost:5000") return; 36 | 37 | this.authWindow.close(); 38 | 39 | const result = JSON.parse(message.data); 40 | if (!result.status) 41 | { 42 | this.failed = true; 43 | this.error = result.error; 44 | this.errorDescription = result.errorDescription; 45 | } 46 | else 47 | { 48 | this.failed = false; 49 | this.isRequesting = true; 50 | 51 | this.userService.facebookLogin(result.accessToken) 52 | .finally(() => this.isRequesting = false) 53 | .subscribe( 54 | result => { 55 | if (result) { 56 | this.router.navigate(['/dashboard/home']); 57 | } 58 | }, 59 | error => { 60 | this.failed = true; 61 | this.error = error; 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/src/app/account/login-form/login-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |

Login

7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 |
15 | 16 | 17 | Please enter a valid email 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 | 32 | 33 |
34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /src/src/app/account/login-form/login-form.component.scss: -------------------------------------------------------------------------------- 1 | .new-user-alert{padding-top:2.5rem} -------------------------------------------------------------------------------- /src/src/app/account/login-form/login-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginFormComponent } from './login-form.component'; 4 | 5 | describe('LoginFormComponent', () => { 6 | let component: LoginFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/account/login-form/login-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from 'rxjs'; 2 | import { Component, OnInit,OnDestroy } from '@angular/core'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | 5 | import { Credentials } from '../../shared/models/credentials.interface'; 6 | import { UserService } from '../../shared/services/user.service'; 7 | 8 | @Component({ 9 | selector: 'app-login-form', 10 | templateUrl: './login-form.component.html', 11 | styleUrls: ['./login-form.component.scss'] 12 | }) 13 | 14 | export class LoginFormComponent implements OnInit, OnDestroy { 15 | 16 | private subscription: Subscription; 17 | 18 | brandNew: boolean; 19 | errors: string; 20 | isRequesting: boolean; 21 | submitted: boolean = false; 22 | credentials: Credentials = { email: '', password: '' }; 23 | 24 | constructor(private userService: UserService, private router: Router,private activatedRoute: ActivatedRoute) { } 25 | 26 | ngOnInit() { 27 | 28 | // subscribe to router event 29 | this.subscription = this.activatedRoute.queryParams.subscribe( 30 | (param: any) => { 31 | this.brandNew = param['brandNew']; 32 | this.credentials.email = param['email']; 33 | }); 34 | } 35 | 36 | ngOnDestroy() { 37 | // prevent memory leak by unsubscribing 38 | this.subscription.unsubscribe(); 39 | } 40 | 41 | login({ value, valid }: { value: Credentials, valid: boolean }) { 42 | this.submitted = true; 43 | this.isRequesting = true; 44 | this.errors=''; 45 | if (valid) { 46 | this.userService.login(value.email, value.password) 47 | .finally(() => this.isRequesting = false) 48 | .subscribe( 49 | result => { 50 | if (result) { 51 | this.router.navigate(['/dashboard/home']); 52 | } 53 | }, 54 | error => this.errors = error); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/src/app/account/registration-form/registration-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Please enter your information

4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 | Please enter a valid email 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 | 40 | 41 |
42 |
43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /src/src/app/account/registration-form/registration-form.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/account/registration-form/registration-form.component.scss -------------------------------------------------------------------------------- /src/src/app/account/registration-form/registration-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RegistrationFormComponent } from './registration-form.component'; 4 | 5 | describe('RegistrationFormComponent', () => { 6 | let component: RegistrationFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RegistrationFormComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RegistrationFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/account/registration-form/registration-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { UserRegistration } from '../../shared/models/user.registration.interface'; 5 | import { UserService } from '../../shared/services/user.service'; 6 | 7 | @Component({ 8 | selector: 'app-registration-form', 9 | templateUrl: './registration-form.component.html', 10 | styleUrls: ['./registration-form.component.scss'] 11 | }) 12 | export class RegistrationFormComponent implements OnInit { 13 | 14 | errors: string; 15 | isRequesting: boolean; 16 | submitted: boolean = false; 17 | 18 | constructor(private userService: UserService,private router: Router) { } 19 | 20 | ngOnInit() { 21 | } 22 | 23 | registerUser({ value, valid }: { value: UserRegistration, valid: boolean }) { 24 | this.submitted = true; 25 | this.isRequesting = true; 26 | this.errors=''; 27 | if(valid) 28 | { 29 | this.userService.register(value.email,value.password,value.firstName,value.lastName,value.location) 30 | .finally(() => this.isRequesting = false) 31 | .subscribe( 32 | result => {if(result){ 33 | this.router.navigate(['/login'],{queryParams: {brandNew: true,email:value.email}}); 34 | }}, 35 | errors => this.errors = errors); 36 | } 37 | } 38 | 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /src/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/app.component.scss -------------------------------------------------------------------------------- /src/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | describe('AppComponent', () => { 4 | beforeEach(async(() => { 5 | TestBed.configureTestingModule({ 6 | declarations: [ 7 | AppComponent 8 | ], 9 | }).compileComponents(); 10 | })); 11 | it('should create the app', async(() => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.debugElement.componentInstance; 14 | expect(app).toBeTruthy(); 15 | })); 16 | it(`should have as title 'app'`, async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app.title).toEqual('app'); 20 | })); 21 | it('should render title in a h1 tag', async(() => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | fixture.detectChanges(); 24 | const compiled = fixture.debugElement.nativeElement; 25 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); 26 | })); 27 | }); 28 | -------------------------------------------------------------------------------- /src/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | } 11 | -------------------------------------------------------------------------------- /src/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 { HttpModule, XHRBackend } from '@angular/http'; 5 | import { AuthenticateXHRBackend } from './authenticate-xhr.backend'; 6 | 7 | import { routing } from './app.routing'; 8 | 9 | /* App Root */ 10 | import { AppComponent } from './app.component'; 11 | import { HeaderComponent } from './header/header.component'; 12 | import { HomeComponent } from './home/home.component'; 13 | 14 | /* Account Imports */ 15 | import { AccountModule } from './account/account.module'; 16 | /* Dashboard Imports */ 17 | import { DashboardModule } from './dashboard/dashboard.module'; 18 | 19 | import { ConfigService } from './shared/utils/config.service'; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | AppComponent, 24 | HeaderComponent, 25 | HomeComponent 26 | ], 27 | imports: [ 28 | AccountModule, 29 | DashboardModule, 30 | BrowserModule, 31 | FormsModule, 32 | HttpModule, 33 | routing 34 | ], 35 | providers: [ConfigService, { 36 | provide: XHRBackend, 37 | useClass: AuthenticateXHRBackend 38 | }], 39 | bootstrap: [AppComponent] 40 | }) 41 | export class AppModule { } 42 | -------------------------------------------------------------------------------- /src/src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { HomeComponent } from './home/home.component'; 5 | 6 | const appRoutes: Routes = [ 7 | { path: '', component: HomeComponent } 8 | ]; 9 | 10 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /src/src/app/auth.guard.ts: -------------------------------------------------------------------------------- 1 | // auth.guard.ts 2 | import { Injectable } from '@angular/core'; 3 | import { Router, CanActivate } from '@angular/router'; 4 | import { UserService } from './shared/services/user.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | constructor(private user: UserService,private router: Router) {} 9 | 10 | canActivate() { 11 | 12 | if(!this.user.isLoggedIn()) 13 | { 14 | this.router.navigate(['/account/login']); 15 | return false; 16 | } 17 | 18 | return true; 19 | } 20 | } -------------------------------------------------------------------------------- /src/src/app/authenticate-xhr.backend.ts: -------------------------------------------------------------------------------- 1 | import { Request, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy, Response } from '@angular/http'; 2 | import { Observable } from 'rxjs/Observable'; 3 | import 'rxjs/add/operator/catch'; 4 | import 'rxjs/add/observable/throw'; 5 | import { Injectable } from '@angular/core'; 6 | 7 | // sweet global way to handle 401s - works in tandem with existing AuthGuard route checks 8 | // http://stackoverflow.com/questions/34934009/handling-401s-globally-with-angular-2 9 | 10 | @Injectable() 11 | export class AuthenticateXHRBackend extends XHRBackend { 12 | 13 | constructor(_browserXhr: BrowserXhr, _baseResponseOptions: ResponseOptions, _xsrfStrategy: XSRFStrategy) { 14 | super(_browserXhr, _baseResponseOptions, _xsrfStrategy); 15 | } 16 | 17 | createConnection(request: Request) { 18 | let xhrConnection = super.createConnection(request); 19 | xhrConnection.response = xhrConnection.response.catch((error: Response) => { 20 | if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) { 21 | 22 | console.log('The authentication session expired or the user is not authorized. Force refresh of the current page.'); 23 | /* Great solution for bundling with Auth Guard! 24 | 1. Auth Guard checks authorized user (e.g. by looking into LocalStorage). 25 | 2. On 401/403 response you clean authorized user for the Guard (e.g. by removing coresponding parameters in LocalStorage). 26 | 3. As at this early stage you can't access the Router for forwarding to the login page, 27 | 4. refreshing the same page will trigger the Guard checks, which will forward you to the login screen */ 28 | localStorage.removeItem('auth_token'); 29 | window.location.href = window.location.href + '?' + new Date().getMilliseconds(); 30 | } 31 | return Observable.throw(error); 32 | }); 33 | return xhrConnection; 34 | } 35 | } -------------------------------------------------------------------------------- /src/src/app/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { SharedModule } from '../shared/modules/shared.module'; 5 | 6 | import { routing } from './dashboard.routing'; 7 | import { RootComponent } from './root/root.component'; 8 | import { HomeComponent } from './home/home.component'; 9 | import { DashboardService } from './services/dashboard.service'; 10 | 11 | import { AuthGuard } from '../auth.guard'; 12 | import { SettingsComponent } from './settings/settings.component'; 13 | 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule, 18 | FormsModule, 19 | routing, 20 | SharedModule 21 | ], 22 | declarations: [RootComponent,HomeComponent, SettingsComponent], 23 | exports: [ ], 24 | providers: [AuthGuard,DashboardService] 25 | }) 26 | export class DashboardModule { } 27 | -------------------------------------------------------------------------------- /src/src/app/dashboard/dashboard.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { RootComponent } from './root/root.component'; 5 | import { HomeComponent } from './home/home.component'; 6 | import { SettingsComponent } from './settings/settings.component'; 7 | 8 | import { AuthGuard } from '../auth.guard'; 9 | 10 | export const routing: ModuleWithProviders = RouterModule.forChild([ 11 | { 12 | path: 'dashboard', 13 | component: RootComponent, canActivate: [AuthGuard], 14 | 15 | children: [ 16 | { path: '', component: HomeComponent }, 17 | { path: 'home', component: HomeComponent }, 18 | { path: 'settings', component: SettingsComponent }, 19 | ] 20 | } 21 | ]); 22 | 23 | -------------------------------------------------------------------------------- /src/src/app/dashboard/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

{{homeDetails?.message}}

5 |

Name: {{homeDetails?.firstName}} {{homeDetails?.lastName}}

6 |

Location: {{homeDetails?.location}}

7 |

Locale: {{homeDetails?.locale}}

8 |

Gender: {{homeDetails?.gender}}

9 |

Facebook Id: {{homeDetails?.facebookId}}

10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/src/app/dashboard/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/dashboard/home/home.component.scss -------------------------------------------------------------------------------- /src/src/app/dashboard/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/dashboard/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { HomeDetails } from '../models/home.details.interface'; 4 | import { DashboardService } from '../services/dashboard.service'; 5 | 6 | @Component({ 7 | selector: 'app-home', 8 | templateUrl: './home.component.html', 9 | styleUrls: ['./home.component.scss'] 10 | }) 11 | export class HomeComponent implements OnInit { 12 | 13 | homeDetails: HomeDetails; 14 | 15 | constructor(private dashboardService: DashboardService) { } 16 | 17 | ngOnInit() { 18 | 19 | this.dashboardService.getHomeDetails() 20 | .subscribe((homeDetails: HomeDetails) => { 21 | this.homeDetails = homeDetails; 22 | }, 23 | error => { 24 | //this.notificationService.printErrorMessage(error); 25 | }); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/src/app/dashboard/models/home.details.interface.ts: -------------------------------------------------------------------------------- 1 | export interface HomeDetails { 2 | message: string; 3 | firstName: string; 4 | lastName: string; 5 | location: string; 6 | locale: string; 7 | gender: string; 8 | pictureUrl: string; 9 | facebookId: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/src/app/dashboard/root/root.component.html: -------------------------------------------------------------------------------- 1 |
2 | 12 |
13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/src/app/dashboard/root/root.component.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Sidebar 3 | */ 4 | 5 | .sidebar { 6 | position: fixed; 7 | top: 51px; 8 | bottom: 0; 9 | left: 0; 10 | z-index: 1000; 11 | padding: 20px 0; 12 | overflow-x: hidden; 13 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 14 | border-right: 1px solid #eee; 15 | } 16 | 17 | .sidebar .nav { 18 | margin-bottom: 20px; 19 | } 20 | 21 | .sidebar .nav-item { 22 | width: 100%; 23 | } 24 | 25 | .sidebar .nav-item + .nav-item { 26 | margin-left: 0; 27 | } 28 | 29 | .sidebar .nav-link { 30 | border-radius: 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/src/app/dashboard/root/root.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RootComponent } from './root.component'; 4 | 5 | describe('RootComponent', () => { 6 | let component: RootComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RootComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RootComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/dashboard/root/root.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './root.component.html', 7 | styleUrls: ['./root.component.scss'], 8 | 9 | }) 10 | export class RootComponent implements OnInit { 11 | 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/src/app/dashboard/services/dashboard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response, Headers } from '@angular/http'; 3 | 4 | import { HomeDetails } from '../models/home.details.interface'; 5 | import { ConfigService } from '../../shared/utils/config.service'; 6 | 7 | import {BaseService} from '../../shared/services/base.service'; 8 | 9 | import { Observable } from 'rxjs/Rx'; 10 | 11 | // Add the RxJS Observable operators we need in this app. 12 | import '../../rxjs-operators'; 13 | 14 | @Injectable() 15 | 16 | export class DashboardService extends BaseService { 17 | 18 | baseUrl: string = ''; 19 | 20 | constructor(private http: Http, private configService: ConfigService) { 21 | super(); 22 | this.baseUrl = configService.getApiURI(); 23 | } 24 | 25 | getHomeDetails(): Observable { 26 | let headers = new Headers(); 27 | headers.append('Content-Type', 'application/json'); 28 | let authToken = localStorage.getItem('auth_token'); 29 | headers.append('Authorization', `Bearer ${authToken}`); 30 | 31 | return this.http.get(this.baseUrl + "/dashboard/home",{headers}) 32 | .map(response => response.json()) 33 | .catch(this.handleError); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/src/app/dashboard/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |

2 | settings works! 3 |

4 | -------------------------------------------------------------------------------- /src/src/app/dashboard/settings/settings.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/dashboard/settings/settings.component.scss -------------------------------------------------------------------------------- /src/src/app/dashboard/settings/settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingsComponent } from './settings.component'; 4 | 5 | describe('SettingsComponent', () => { 6 | let component: SettingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SettingsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SettingsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/dashboard/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-settings', 5 | templateUrl: './settings.component.html', 6 | styleUrls: ['./settings.component.scss'] 7 | }) 8 | export class SettingsComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/src/app/directives/email.validator.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, forwardRef } from '@angular/core'; 2 | import { NG_VALIDATORS, FormControl } from '@angular/forms'; 3 | 4 | function validateEmailFactory() { 5 | return (c: FormControl) => { 6 | let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 7 | 8 | return EMAIL_REGEXP.test(c.value) ? null : { 9 | validateEmail: { 10 | valid: false 11 | } 12 | }; 13 | }; 14 | } 15 | 16 | @Directive({ 17 | selector: '[validateEmail][ngModel],[validateEmail][formControl]', 18 | providers: [ 19 | { provide: NG_VALIDATORS, useExisting: forwardRef(() => EmailValidator), multi: true } 20 | ] 21 | }) 22 | export class EmailValidator { 23 | 24 | validator: Function; 25 | 26 | constructor() { 27 | this.validator = validateEmailFactory(); 28 | } 29 | 30 | validate(c: FormControl) { 31 | return this.validator(c); 32 | } 33 | } -------------------------------------------------------------------------------- /src/src/app/directives/focus.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, Renderer, OnInit } from "@angular/core"; 2 | 3 | @Directive({ selector: '[tmFocus]' }) 4 | 5 | export class myFocus implements OnInit { 6 | constructor(private el: ElementRef, private renderer: Renderer) { 7 | // focus won't work at construction time - too early 8 | } 9 | 10 | ngOnInit() { 11 | this.renderer.invokeElementMethod(this.el.nativeElement, 'focus', []); 12 | } 13 | } -------------------------------------------------------------------------------- /src/src/app/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 32 |
33 | -------------------------------------------------------------------------------- /src/src/app/header/header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/header/header.component.scss -------------------------------------------------------------------------------- /src/src/app/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit,OnDestroy } from '@angular/core'; 2 | import {Subscription} from 'rxjs/Subscription'; 3 | 4 | import { UserService } from '../shared/services/user.service'; 5 | 6 | @Component({ 7 | selector: 'app-header', 8 | templateUrl: './header.component.html', 9 | styleUrls: ['./header.component.scss'] 10 | }) 11 | 12 | export class HeaderComponent implements OnInit,OnDestroy { 13 | 14 | status: boolean; 15 | subscription:Subscription; 16 | 17 | constructor(private userService:UserService) { 18 | } 19 | 20 | logout() { 21 | this.userService.logout(); 22 | } 23 | 24 | ngOnInit() { 25 | this.subscription = this.userService.authNavStatus$.subscribe(status => this.status = status); 26 | } 27 | 28 | ngOnDestroy() { 29 | // prevent memory leak when component is destroyed 30 | this.subscription.unsubscribe(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

JWT Auth Demo

6 |

This demo shows local and facebook user registration and login flow using Angular v5.2.1, ASP.NET Core 2.0 WebApi and Facebook api

7 |

Signup with email Login with email Signup/login with facebook

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/src/app/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/app/home/home.component.scss -------------------------------------------------------------------------------- /src/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/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 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent { 9 | 10 | constructor() { } 11 | } 12 | -------------------------------------------------------------------------------- /src/src/app/rxjs-operators.js: -------------------------------------------------------------------------------- 1 | // import 'rxjs/Rx'; // adds ALL RxJS statics & operators to Observable 2 | 3 | // See node_module/rxjs/Rxjs.js 4 | // Import just the rxjs statics and operators we need for THIS app. 5 | 6 | // Statics 7 | import 'rxjs/add/observable/throw'; 8 | 9 | // Operators 10 | import 'rxjs/add/operator/catch'; 11 | import 'rxjs/add/operator/debounceTime'; 12 | import 'rxjs/add/operator/distinctUntilChanged'; 13 | import 'rxjs/add/operator/map'; 14 | import 'rxjs/add/operator/switchMap'; 15 | import 'rxjs/add/operator/toPromise'; -------------------------------------------------------------------------------- /src/src/app/shared/models/credentials.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Credentials { 2 | email: string; 3 | password: string; 4 | } -------------------------------------------------------------------------------- /src/src/app/shared/models/user.registration.interface.ts: -------------------------------------------------------------------------------- 1 | export interface UserRegistration { 2 | email: string; 3 | password: string; 4 | firstName: string; 5 | lastName: string; 6 | location: string; 7 | } -------------------------------------------------------------------------------- /src/src/app/shared/modules/shared.module.ts: -------------------------------------------------------------------------------- 1 | // include directives/components commonly used in features modules in this shared modules 2 | // and import me into the feature module 3 | // importing them individually results in: Type xxx is part of the declarations of 2 modules: ... Please consider moving to a higher module... 4 | // https://github.com/angular/angular/issues/10646 5 | 6 | import { NgModule } from '@angular/core'; 7 | import { CommonModule } from '@angular/common'; 8 | 9 | import { myFocus } from '../../directives/focus.directive'; 10 | import {SpinnerComponent} from '../../spinner/spinner.component'; 11 | 12 | 13 | @NgModule({ 14 | imports: [CommonModule], 15 | declarations: [myFocus,SpinnerComponent], 16 | exports: [myFocus,SpinnerComponent], 17 | providers: [] 18 | }) 19 | export class SharedModule { } -------------------------------------------------------------------------------- /src/src/app/shared/services/base.service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs/Rx'; 2 | 3 | 4 | export abstract class BaseService { 5 | 6 | constructor() { } 7 | 8 | protected handleError(error: any) { 9 | var applicationError = error.headers.get('Application-Error'); 10 | 11 | // either applicationError in header or model error in body 12 | if (applicationError) { 13 | return Observable.throw(applicationError); 14 | } 15 | 16 | var modelStateErrors: string = ''; 17 | var serverError = error.json(); 18 | 19 | if (!serverError.type) { 20 | for (var key in serverError) { 21 | if (serverError[key]) 22 | modelStateErrors += serverError[key] + '\n'; 23 | } 24 | } 25 | 26 | modelStateErrors = modelStateErrors = '' ? null : modelStateErrors; 27 | return Observable.throw(modelStateErrors || 'Server error'); 28 | } 29 | } -------------------------------------------------------------------------------- /src/src/app/shared/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response, Headers, RequestOptions } from '@angular/http'; 3 | 4 | import { UserRegistration } from '../models/user.registration.interface'; 5 | import { ConfigService } from '../utils/config.service'; 6 | 7 | import {BaseService} from "./base.service"; 8 | 9 | import { Observable } from 'rxjs/Rx'; 10 | import { BehaviorSubject } from 'rxjs/Rx'; 11 | 12 | // Add the RxJS Observable operators we need in this app. 13 | import '../../rxjs-operators'; 14 | 15 | @Injectable() 16 | 17 | export class UserService extends BaseService { 18 | 19 | baseUrl: string = ''; 20 | 21 | // Observable navItem source 22 | private _authNavStatusSource = new BehaviorSubject(false); 23 | // Observable navItem stream 24 | authNavStatus$ = this._authNavStatusSource.asObservable(); 25 | 26 | private loggedIn = false; 27 | 28 | constructor(private http: Http, private configService: ConfigService) { 29 | super(); 30 | this.loggedIn = !!localStorage.getItem('auth_token'); 31 | // ?? not sure if this the best way to broadcast the status but seems to resolve issue on page refresh where auth status is lost in 32 | // header component resulting in authed user nav links disappearing despite the fact user is still logged in 33 | this._authNavStatusSource.next(this.loggedIn); 34 | this.baseUrl = configService.getApiURI(); 35 | } 36 | 37 | register(email: string, password: string, firstName: string, lastName: string,location: string): Observable { 38 | let body = JSON.stringify({ email, password, firstName, lastName,location }); 39 | let headers = new Headers({ 'Content-Type': 'application/json' }); 40 | let options = new RequestOptions({ headers: headers }); 41 | 42 | return this.http.post(this.baseUrl + "/accounts", body, options) 43 | .map(res => true) 44 | .catch(this.handleError); 45 | } 46 | 47 | login(userName, password) { 48 | let headers = new Headers(); 49 | headers.append('Content-Type', 'application/json'); 50 | 51 | return this.http 52 | .post( 53 | this.baseUrl + '/auth/login', 54 | JSON.stringify({ userName, password }),{ headers } 55 | ) 56 | .map(res => res.json()) 57 | .map(res => { 58 | localStorage.setItem('auth_token', res.auth_token); 59 | this.loggedIn = true; 60 | this._authNavStatusSource.next(true); 61 | return true; 62 | }) 63 | .catch(this.handleError); 64 | } 65 | 66 | logout() { 67 | localStorage.removeItem('auth_token'); 68 | this.loggedIn = false; 69 | this._authNavStatusSource.next(false); 70 | } 71 | 72 | isLoggedIn() { 73 | return this.loggedIn; 74 | } 75 | 76 | facebookLogin(accessToken:string) { 77 | let headers = new Headers(); 78 | headers.append('Content-Type', 'application/json'); 79 | let body = JSON.stringify({ accessToken }); 80 | return this.http 81 | .post( 82 | this.baseUrl + '/externalauth/facebook', body, { headers }) 83 | .map(res => res.json()) 84 | .map(res => { 85 | localStorage.setItem('auth_token', res.auth_token); 86 | this.loggedIn = true; 87 | this._authNavStatusSource.next(true); 88 | return true; 89 | }) 90 | .catch(this.handleError); 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/src/app/shared/utils/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class ConfigService { 5 | 6 | _apiURI : string; 7 | 8 | constructor() { 9 | this._apiURI = 'http://localhost:5000/api'; 10 | } 11 | 12 | getApiURI() { 13 | return this._apiURI; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/src/app/spinner/spinner.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /src/src/app/spinner/spinner.component.scss: -------------------------------------------------------------------------------- 1 | .spinner { 2 | width: 40px; 3 | height: 40px; 4 | 5 | position: relative; 6 | margin: 30px auto; 7 | } 8 | 9 | .double-bounce1, .double-bounce2 { 10 | width: 100%; 11 | height: 100%; 12 | border-radius: 50%; 13 | background-color: #333; 14 | opacity: 0.6; 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | 19 | -webkit-animation: sk-bounce 2.0s infinite ease-in-out; 20 | animation: sk-bounce 2.0s infinite ease-in-out; 21 | } 22 | 23 | .double-bounce2 { 24 | -webkit-animation-delay: -1.0s; 25 | animation-delay: -1.0s; 26 | } 27 | 28 | @-webkit-keyframes sk-bounce { 29 | 0%, 100% { -webkit-transform: scale(0.0) } 30 | 50% { -webkit-transform: scale(1.0) } 31 | } 32 | 33 | @keyframes sk-bounce { 34 | 0%, 100% { 35 | transform: scale(0.0); 36 | -webkit-transform: scale(0.0); 37 | } 50% { 38 | transform: scale(1.0); 39 | -webkit-transform: scale(1.0); 40 | } 41 | } -------------------------------------------------------------------------------- /src/src/app/spinner/spinner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SpinnerComponent } from './spinner.component'; 4 | 5 | describe('SpinnerComponent', () => { 6 | let component: SpinnerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SpinnerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SpinnerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/src/app/spinner/spinner.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnDestroy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-spinner', 5 | templateUrl: './spinner.component.html', 6 | styleUrls: ['./spinner.component.scss'] 7 | }) 8 | 9 | 10 | export class SpinnerComponent implements OnDestroy { 11 | private currentTimeout: number; 12 | public isDelayedRunning: boolean = false; 13 | 14 | @Input() 15 | public delay: number = 150; 16 | 17 | @Input() 18 | public set isRunning(value: boolean) { 19 | if (!value) { 20 | this.cancelTimeout(); 21 | this.isDelayedRunning = false; 22 | return; 23 | } 24 | 25 | if (this.currentTimeout) { 26 | return; 27 | } 28 | 29 | // specify window to side-step conflict with node types: https://github.com/mgechev/angular2-seed/issues/901 30 | this.currentTimeout = window.setTimeout(() => { 31 | this.isDelayedRunning = value; 32 | this.cancelTimeout(); 33 | }, this.delay); 34 | } 35 | 36 | private cancelTimeout(): void { 37 | clearTimeout(this.currentTimeout); 38 | this.currentTimeout = undefined; 39 | } 40 | 41 | ngOnDestroy(): any { 42 | this.cancelTimeout(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/src/assets/facebook-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/assets/facebook-login.png -------------------------------------------------------------------------------- /src/src/assets/util.js: -------------------------------------------------------------------------------- 1 | function getParameterByName(name, url) { 2 | if (!url) url = window.location.href; 3 | name = name.replace(/[\[\]]/g, "\\$&"); 4 | var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)"), 5 | results = regex.exec(url); 6 | if (!results) return null; 7 | if (!results[2]) return ''; 8 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 9 | } 10 | -------------------------------------------------------------------------------- /src/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/src/facebook-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JwtAuthDemo - Facebook Auth 7 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/favicon.ico -------------------------------------------------------------------------------- /src/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JwtAuthDemo 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/src/npm-debug.log.141272917: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/src/npm-debug.log.141272917 -------------------------------------------------------------------------------- /src/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/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | -------------------------------------------------------------------------------- /src/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | /* 4 | * Base structure 5 | */ 6 | 7 | /* Move down content because we have a fixed navbar that is 3.5rem tall */ 8 | body { 9 | padding-top: 3.5rem; 10 | } 11 | 12 | /* 13 | * Typography 14 | */ 15 | 16 | h1 { 17 | padding-bottom: 9px; 18 | margin-bottom: 20px; 19 | border-bottom: 1px solid #eee; 20 | } -------------------------------------------------------------------------------- /src/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/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", 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": [ 26 | true, 27 | "spaces" 28 | ], 29 | "interface-over-type-literal": true, 30 | "label-position": true, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-access": false, 36 | "member-ordering": [ 37 | true, 38 | { 39 | "order": [ 40 | "static-field", 41 | "instance-field", 42 | "static-method", 43 | "instance-method" 44 | ] 45 | } 46 | ], 47 | "no-arg": true, 48 | "no-bitwise": true, 49 | "no-console": [ 50 | true, 51 | "debug", 52 | "info", 53 | "time", 54 | "timeEnd", 55 | "trace" 56 | ], 57 | "no-construct": true, 58 | "no-debugger": true, 59 | "no-duplicate-super": true, 60 | "no-empty": false, 61 | "no-empty-interface": true, 62 | "no-eval": true, 63 | "no-inferrable-types": [ 64 | true, 65 | "ignore-params" 66 | ], 67 | "no-misused-new": true, 68 | "no-non-null-assertion": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "typeof-compare": true, 111 | "unified-signatures": true, 112 | "variable-name": false, 113 | "whitespace": [ 114 | true, 115 | "check-branch", 116 | "check-decl", 117 | "check-operator", 118 | "check-separator", 119 | "check-type" 120 | ], 121 | "directive-selector": [ 122 | true, 123 | "attribute", 124 | "app", 125 | "camelCase" 126 | ], 127 | "component-selector": [ 128 | true, 129 | "element", 130 | "app", 131 | "kebab-case" 132 | ], 133 | "no-output-on-prefix": true, 134 | "use-input-property-decorator": true, 135 | "use-output-property-decorator": true, 136 | "use-host-property-decorator": true, 137 | "no-input-rename": true, 138 | "no-output-rename": true, 139 | "use-life-cycle-interface": true, 140 | "use-pipe-transform-interface": true, 141 | "component-class-suffix": true, 142 | "directive-class-suffix": true 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/wwwroot/3rdpartylicenses.txt: -------------------------------------------------------------------------------- 1 | core-js@2.5.3 2 | MIT 3 | Copyright (c) 2014-2017 Denis Pushkarev 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | webpack@3.10.0 24 | MIT 25 | Copyright JS Foundation and other contributors 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining 28 | a copy of this software and associated documentation files (the 29 | 'Software'), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to 32 | permit persons to whom the Software is furnished to do so, subject to 33 | the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be 36 | included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 39 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 41 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 42 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 43 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 44 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | 46 | zone.js@0.8.19 47 | MIT 48 | The MIT License 49 | 50 | Copyright (c) 2016 Google, Inc. 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the "Software"), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | 70 | @angular/core@5.1.3 71 | MIT 72 | MIT 73 | 74 | bootstrap@4.0.0-beta.2 75 | MIT 76 | The MIT License (MIT) 77 | 78 | Copyright (c) 2011-2017 Twitter, Inc. 79 | Copyright (c) 2011-2017 The Bootstrap Authors 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining a copy 82 | of this software and associated documentation files (the "Software"), to deal 83 | in the Software without restriction, including without limitation the rights 84 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 85 | copies of the Software, and to permit persons to whom the Software is 86 | furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in 89 | all copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 97 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/wwwroot/assets/facebook-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/wwwroot/assets/facebook-login.png -------------------------------------------------------------------------------- /src/wwwroot/assets/util.js: -------------------------------------------------------------------------------- 1 | function getParameterByName(name, url) { 2 | if (!url) url = window.location.href; 3 | name = name.replace(/[\[\]]/g, "\\$&"); 4 | var regex = new RegExp("[?&#]" + name + "(=([^&#]*)|&|#|$)"), 5 | results = regex.exec(url); 6 | if (!results) return null; 7 | if (!results[2]) return ''; 8 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 9 | } 10 | -------------------------------------------------------------------------------- /src/wwwroot/facebook-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JwtAuthDemo - Facebook Auth 7 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCore2WebApiAuth/43ee286acaf745f11b2e06a10248e2d00a169896/src/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | JwtAuthDemoLoading... -------------------------------------------------------------------------------- /src/wwwroot/inline.b6708a750e679a974885.bundle.js: -------------------------------------------------------------------------------- 1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,a){for(var u,i,f,l=0,s=[];l