├── .gitignore ├── Directory.Build.props ├── JWTSimpleServer.sln ├── LICENSE ├── README.md ├── appveyor.yml ├── build.ps1 ├── build └── dependencies.props ├── samples ├── SimpleServerEFCoreStorage │ ├── CustomAuthenticationProvider.cs │ ├── JwtSimpleServerContextFactory.cs │ ├── Migrations │ │ ├── 20180209140332_Initial.Designer.cs │ │ ├── 20180209140332_Initial.cs │ │ └── JwtSimpleServerDbContextModelSnapshot.cs │ ├── Program.cs │ ├── SimpleServerEFCoreStorage.csproj │ ├── Startup.cs │ ├── appsettings.json │ └── wwwroot │ │ ├── index.html │ │ ├── simple-server-client.js │ │ └── simple-server-client.js.map ├── SimpleServerInMemoryStorage │ ├── Controllers │ │ └── TestController.cs │ ├── CustomAuthenticationProvider.cs │ ├── Program.cs │ ├── SimpleServerInMemoryStorage.csproj │ ├── Startup.cs │ └── wwwroot │ │ ├── index.html │ │ ├── simple-server-client.js │ │ ├── simple-server-client.js.map │ │ └── styles.css └── SimpleServerMessagePackStorage │ ├── CustomAuthenticationProvider.cs │ ├── Program.cs │ ├── SimpleServerMessagePackStorage.csproj │ ├── Startup.cs │ └── wwwroot │ ├── index.html │ ├── simple-server-client.js │ ├── simple-server-client.js.map │ └── styles.css ├── src ├── JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore │ ├── EntityFrameworkCoreRefreshTokenStore.cs │ ├── EntityFrameworkCoreRefreshTokenStoreSeviceCollectionExtensions.cs │ ├── JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.csproj │ ├── JwtSimpleServerDbContext.cs │ ├── JwtStoreOptions.cs │ └── JwtToken.cs ├── JWTSimpleServer.InMemoryRefreshTokenStore │ ├── InMemoryRefreshTokenServiceCollectionExtensions.cs │ ├── InMemoryRefreshTokenStore.cs │ └── JWTSimpleServer.InMemoryRefreshTokenStore.csproj ├── JWTSimpleServer.MessagePackRefreshTokenStore │ ├── JWTSimpleServer.MessagePackRefreshTokenStore.csproj │ ├── JwtStoreOptions.cs │ ├── JwtToken.cs │ ├── MessagePackRefreshTokenStore.cs │ └── MessagePackRefreshTokenStoreCollectionExtensions.cs ├── JWTSimpleServer.RedisDistributedRefreshTokenStore │ ├── JWTSimpleServer.RedisDistributedRefreshTokenStore.csproj │ ├── RedisDistributedRefreshTokenStore.cs │ └── RedisRefreshTokenStoreServiceCollectionExtensions.cs └── JWTSimpleServer │ ├── Abstractions │ ├── IAuthenticationProvider.cs │ └── IRefreshTokenStore.cs │ ├── Constants.cs │ ├── GrantTypes │ ├── GrantType.cs │ ├── IGrantType.cs │ ├── InvalidGrantType.cs │ ├── PasswordGrantType.cs │ └── RefreshTokenGrantType.cs │ ├── JWTSimpleServer.csproj │ ├── JwtGrantTypesParser.cs │ ├── JwtSimpleServerAppBuilderExtensions.cs │ ├── JwtSimpleServerContext.cs │ ├── JwtSimpleServerMiddleware.cs │ ├── JwtSimpleServerOptions.cs │ ├── JwtSimpleServerServiceCollectionExtensions.cs │ ├── JwtTokenEncoder.cs │ ├── JwtTokenOptions.cs │ ├── JwtTokenResponse.cs │ ├── NoRefreshTokenStore.cs │ ├── ServerMessages.cs │ └── Token.cs ├── test └── FunctionalTests │ ├── Controllers │ └── TestController.cs │ ├── FunctionalTests.csproj │ ├── GrantTypes.cs │ ├── HostCollectionFixture.cs │ ├── HostFixture.cs │ ├── JwtSimpleServer │ └── JwtSimpleServerMiddlewareTest.cs │ └── TestServerBuilder.cs └── ts-client ├── .gitignore ├── package-lock.json ├── package.json ├── readme.md ├── src ├── http │ ├── httpClient.ts │ ├── httpError.ts │ ├── httpRequest.ts │ ├── httpResponse.ts │ └── index.ts ├── index.ts ├── observable.ts ├── refreshTokenService.ts └── serverClient.ts ├── test ├── jwtSimpleServerClient.test.ts ├── mocks │ └── httpClientMock.ts └── observable.test.ts ├── tsconfig.json └── webpack.config.js /.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 | dist/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # .NET Core 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | **/Properties/launchSettings.json 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # Visual Studio code coverage results 118 | *.coverage 119 | *.coveragexml 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | # TODO: Comment the next line if you want to checkin your web deploy settings 153 | # but database connection strings (with potential passwords) will be unencrypted 154 | *.pubxml 155 | *.publishproj 156 | 157 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 158 | # checkin your Azure Web App publish settings, but sensitive information contained 159 | # in these scripts will be unencrypted 160 | PublishScripts/ 161 | 162 | # NuGet Packages 163 | *.nupkg 164 | # The packages folder can be ignored because of Package Restore 165 | **/packages/* 166 | # except build/, which is used as an MSBuild target. 167 | !**/packages/build/ 168 | # Uncomment if necessary however generally it will be regenerated when needed 169 | #!**/packages/repositories.config 170 | # NuGet v3's project.json files produces more ignorable files 171 | *.nuget.props 172 | *.nuget.targets 173 | 174 | # Microsoft Azure Build Output 175 | csx/ 176 | *.build.csdef 177 | 178 | # Microsoft Azure Emulator 179 | ecf/ 180 | rcf/ 181 | 182 | # Windows Store app package directories and files 183 | AppPackages/ 184 | BundleArtifacts/ 185 | Package.StoreAssociation.xml 186 | _pkginfo.txt 187 | 188 | # Visual Studio cache files 189 | # files ending in .cache can be ignored 190 | *.[Cc]ache 191 | # but keep track of directories ending in .cache 192 | !*.[Cc]ache/ 193 | 194 | # Others 195 | ClientBin/ 196 | ~$* 197 | *~ 198 | *.dbmdl 199 | *.dbproj.schemaview 200 | *.jfm 201 | *.pfx 202 | *.publishsettings 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | *.ndf 224 | 225 | # Business Intelligence projects 226 | *.rdl.data 227 | *.bim.layout 228 | *.bim_*.settings 229 | 230 | # Microsoft Fakes 231 | FakesAssemblies/ 232 | 233 | # GhostDoc plugin setting file 234 | *.GhostDoc.xml 235 | 236 | # Node.js Tools for Visual Studio 237 | .ntvs_analysis.dat 238 | node_modules/ 239 | 240 | # Typescript v1 declaration files 241 | typings/ 242 | 243 | # Visual Studio 6 build log 244 | *.plg 245 | 246 | # Visual Studio 6 workspace options file 247 | *.opt 248 | 249 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 250 | *.vbw 251 | 252 | # Visual Studio LightSwitch build output 253 | **/*.HTMLClient/GeneratedArtifacts 254 | **/*.DesktopClient/GeneratedArtifacts 255 | **/*.DesktopClient/ModelManifest.xml 256 | **/*.Server/GeneratedArtifacts 257 | **/*.Server/ModelManifest.xml 258 | _Pvt_Extensions 259 | 260 | # Paket dependency manager 261 | .paket/paket.exe 262 | paket-files/ 263 | 264 | # FAKE - F# Make 265 | .fake/ 266 | 267 | # JetBrains Rider 268 | .idea/ 269 | *.sln.iml 270 | 271 | # CodeRush 272 | .cr/ 273 | 274 | # Python Tools for Visual Studio (PTVS) 275 | __pycache__/ 276 | *.pyc 277 | 278 | # Cake - Uncomment if you are using it 279 | # tools/** 280 | # !tools/packages.config 281 | 282 | # Telerik's JustMock configuration file 283 | *.jmconfig 284 | 285 | # BizTalk build output 286 | *.btp.cs 287 | *.btm.cs 288 | *.odx.cs 289 | *.xsd.cs 290 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /JWTSimpleServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTSimpleServer", "src\JWTSimpleServer\JWTSimpleServer.csproj", "{EDDF433C-3FFE-4CDF-A932-63C5DF3C8536}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAECD48F-CFFE-4B45-93B6-54F4DB13712B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{C0A49F2E-833D-45EB-B2A9-D69FF751BEEC}" 11 | ProjectSection(SolutionItems) = preProject 12 | build\dependencies.props = build\dependencies.props 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A9680453-5993-41BF-8D9C-02E28A597B44}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3E772E92-057F-4793-B208-F155773DA623}" 18 | ProjectSection(SolutionItems) = preProject 19 | Directory.Build.props = Directory.Build.props 20 | EndProjectSection 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\FunctionalTests\FunctionalTests.csproj", "{6737BA92-1DA8-485F-A45B-42255DDCBFEC}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTSimpleServer.InMemoryRefreshTokenStore", "src\JWTSimpleServer.InMemoryRefreshTokenStore\JWTSimpleServer.InMemoryRefreshTokenStore.csproj", "{CC785929-A1F5-4741-A7AA-618DC65BA70B}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore", "src\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.csproj", "{043914AD-EA66-4961-AC7D-C484193D33AA}" 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EA29F96B-996C-4724-9F20-E115EB0F0E4E}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleServerInMemoryStorage", "samples\SimpleServerInMemoryStorage\SimpleServerInMemoryStorage.csproj", "{9DDA6A11-9898-4250-842A-24E2DBB435BF}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleServerEFCoreStorage", "samples\SimpleServerEFCoreStorage\SimpleServerEFCoreStorage.csproj", "{D25EB26D-CEFA-488A-B0F5-3908D6E73041}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTSimpleServer.MessagePackRefreshTokenStore", "src\JWTSimpleServer.MessagePackRefreshTokenStore\JWTSimpleServer.MessagePackRefreshTokenStore.csproj", "{4B0584E9-EA7B-4B48-83CB-F86FBDC29167}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleServerMessagePackStorage", "samples\SimpleServerMessagePackStorage\SimpleServerMessagePackStorage.csproj", "{B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF}" 37 | EndProject 38 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWTSimpleServer.RedisDistributedRefreshTokenStore", "src\JWTSimpleServer.RedisDistributedRefreshTokenStore\JWTSimpleServer.RedisDistributedRefreshTokenStore.csproj", "{7B63A02C-54DB-4406-AE17-984CAF5C455E}" 39 | EndProject 40 | Global 41 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 42 | Debug|Any CPU = Debug|Any CPU 43 | Release|Any CPU = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 46 | {EDDF433C-3FFE-4CDF-A932-63C5DF3C8536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {EDDF433C-3FFE-4CDF-A932-63C5DF3C8536}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {EDDF433C-3FFE-4CDF-A932-63C5DF3C8536}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {EDDF433C-3FFE-4CDF-A932-63C5DF3C8536}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {6737BA92-1DA8-485F-A45B-42255DDCBFEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {6737BA92-1DA8-485F-A45B-42255DDCBFEC}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {6737BA92-1DA8-485F-A45B-42255DDCBFEC}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {6737BA92-1DA8-485F-A45B-42255DDCBFEC}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {CC785929-A1F5-4741-A7AA-618DC65BA70B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {CC785929-A1F5-4741-A7AA-618DC65BA70B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {CC785929-A1F5-4741-A7AA-618DC65BA70B}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {CC785929-A1F5-4741-A7AA-618DC65BA70B}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {043914AD-EA66-4961-AC7D-C484193D33AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {043914AD-EA66-4961-AC7D-C484193D33AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {043914AD-EA66-4961-AC7D-C484193D33AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {043914AD-EA66-4961-AC7D-C484193D33AA}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {9DDA6A11-9898-4250-842A-24E2DBB435BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {9DDA6A11-9898-4250-842A-24E2DBB435BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {9DDA6A11-9898-4250-842A-24E2DBB435BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {9DDA6A11-9898-4250-842A-24E2DBB435BF}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {D25EB26D-CEFA-488A-B0F5-3908D6E73041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {D25EB26D-CEFA-488A-B0F5-3908D6E73041}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {D25EB26D-CEFA-488A-B0F5-3908D6E73041}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {D25EB26D-CEFA-488A-B0F5-3908D6E73041}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {4B0584E9-EA7B-4B48-83CB-F86FBDC29167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {4B0584E9-EA7B-4B48-83CB-F86FBDC29167}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {4B0584E9-EA7B-4B48-83CB-F86FBDC29167}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {4B0584E9-EA7B-4B48-83CB-F86FBDC29167}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {7B63A02C-54DB-4406-AE17-984CAF5C455E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {7B63A02C-54DB-4406-AE17-984CAF5C455E}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {7B63A02C-54DB-4406-AE17-984CAF5C455E}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {7B63A02C-54DB-4406-AE17-984CAF5C455E}.Release|Any CPU.Build.0 = Release|Any CPU 82 | EndGlobalSection 83 | GlobalSection(SolutionProperties) = preSolution 84 | HideSolutionNode = FALSE 85 | EndGlobalSection 86 | GlobalSection(NestedProjects) = preSolution 87 | {EDDF433C-3FFE-4CDF-A932-63C5DF3C8536} = {AAECD48F-CFFE-4B45-93B6-54F4DB13712B} 88 | {6737BA92-1DA8-485F-A45B-42255DDCBFEC} = {A9680453-5993-41BF-8D9C-02E28A597B44} 89 | {CC785929-A1F5-4741-A7AA-618DC65BA70B} = {AAECD48F-CFFE-4B45-93B6-54F4DB13712B} 90 | {043914AD-EA66-4961-AC7D-C484193D33AA} = {AAECD48F-CFFE-4B45-93B6-54F4DB13712B} 91 | {9DDA6A11-9898-4250-842A-24E2DBB435BF} = {EA29F96B-996C-4724-9F20-E115EB0F0E4E} 92 | {D25EB26D-CEFA-488A-B0F5-3908D6E73041} = {EA29F96B-996C-4724-9F20-E115EB0F0E4E} 93 | {4B0584E9-EA7B-4B48-83CB-F86FBDC29167} = {AAECD48F-CFFE-4B45-93B6-54F4DB13712B} 94 | {B7CDCF60-6B1B-4AEA-AA66-AC1FBCD38BBF} = {EA29F96B-996C-4724-9F20-E115EB0F0E4E} 95 | {7B63A02C-54DB-4406-AE17-984CAF5C455E} = {AAECD48F-CFFE-4B45-93B6-54F4DB13712B} 96 | EndGlobalSection 97 | GlobalSection(ExtensibilityGlobals) = postSolution 98 | SolutionGuid = {29D6F500-ABDC-4D2D-810F-F7BE7A624499} 99 | EndGlobalSection 100 | EndGlobal 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/xkosic4gm7salll3?svg=true)](https://ci.appveyor.com/project/Xabaril/jwtsimpleserver) [![MyGet CI](https://img.shields.io/myget/xabaril/v/JWTSimpleServer.svg)](http://myget.org/gallery/jwtsimpleserver) [![NuGet](https://img.shields.io/nuget/v/JWTSimpleServer.svg)](https://www.nuget.org/packages/JWTSimpleServer/) 2 | [![npm version](https://badge.fury.io/js/jwt-simpleserver-client.svg)](https://badge.fury.io/js/jwt-simpleserver-client) 3 | 4 | [![Build history](https://buildstats.info/appveyor/chart/xabaril/jwtsimpleserver)](https://ci.appveyor.com/project/Xabaril/jwtsimpleserver/history?branch=master) 5 | 6 | # JWT Simple Server 7 | A light-weight, dynamic jwt server for ASP.NET Core 2.1 8 | 9 | ## What is the motivation behind it? 10 | 11 | JWT Simple server arises from the need of having an ease-to-use JWT server in ASP.NET, avoiding the user all the ceremony configuration and providing additional features. 12 | 13 | ## What JWT Simple Server offers? 14 | 15 | * Easy to use JWT Server, configured with a few lines of code. 16 | * Flexible and customizable. You can provide your own authentication and store mechanisms. 17 | * Implements middleware that exposes the token endpoint so you don't have to create and mantain your own. 18 | * Provides refresh tokens feature with several store implementations (InMemory, Entity Framework, Redis, Message Pack). 19 | * Provides a typescript library that will allow you to interact with JWT Server easily. This library offers a JWT Client to request and refresh access tokens and a refresh token automatic renewal service. 20 | 21 | ## Getting Started 22 | 23 | 1. Install the standard Nuget package into your ASP.NET Core application. 24 | 25 | ``` 26 | Install-Package JWTSimpleServer 27 | ``` 28 | ``` 29 | Install-Package JWTSimpleServer.InMemoryRefreshTokenStore 30 | ``` 31 | 32 | 2. Create your own IAuthenticationProvider for user authentication. You should execute context.success and provide the user claims that will be encoded in the token or context.Reject if the authentication was not successful. 33 | 34 | ```csharp 35 | public class CustomAuthenticationProvider : IAuthenticationProvider 36 | { 37 | public Task ValidateClientAuthentication(JwtSimpleServerContext context) 38 | { 39 | if(context.UserName == "demo" && context.Password == "demo") 40 | { 41 | var claims = new List(); 42 | claims.Add(new Claim(ClaimTypes.Name, "demo")); 43 | 44 | context.Success(claims); 45 | } 46 | else 47 | { 48 | context.Reject("Invalid user authentication"); 49 | } 50 | 51 | return Task.CompletedTask; 52 | } 53 | } 54 | ``` 55 | 56 | 3. In the _ConfigureServices_ method of _Startup.cs_, register JWTSimpleServer services, defining one refresh token store (Optional: By default we register [NoRefreshTokenStore](https://github.com/Xabaril/JWTSimpleServer/blob/c5aeca936105942b96a56419b56c42159896881d/src/JWTSimpleServer/JwtSimpleServerServiceCollectionExtensions.cs#L24) implementation). 57 | 58 | ```csharp 59 | public void ConfigureServices(IServiceCollection services) 60 | { 61 | services 62 | .AddSingleton() 63 | .AddJwtSimpleServer(setup => 64 | { 65 | setup.IssuerSigningKey = SigningKey; 66 | }) 67 | .AddJwtInMemoryRefreshTokenStore(); 68 | } 69 | ``` 70 | 71 | 4. In the _Configure_ method, add the middleware to the server exposing the token endpoint and handling it's requests. 72 | 73 | ```csharp 74 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 75 | { 76 | app.UseJwtSimpleServer(setup => 77 | { 78 | setup.IssuerSigningKey = SigningKey; 79 | }); 80 | } 81 | ``` 82 | 5. Two grant types are supported right now by the server: **_password_** and **_refresh_token_** 83 | 84 | A **_password_** grant type request will require username and password parameters and will allow you to obtain an **_access token_**. 85 | 86 | Sample request: 87 | ```html 88 | POST https://localhost:44305/Token HTTP/1.1 89 | Host: localhost:44305 90 | User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0 91 | Accept: */* 92 | Content-Type: application/x-www-form-urlencoded; charset=UTF-8 93 | X-Requested-With: XMLHttpRequest 94 | Referer: https://localhost:44305/ 95 | Content-Length: 68 96 | 97 | grant_type=password&username=demo&password=demo 98 | ``` 99 | HTTP Response 100 | 101 | ```json 102 | { 103 | "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....", 104 | "expires_in": 900, 105 | "refresh_token": "77e248a4a3814308931d63b10fb1e7f7" 106 | } 107 | ``` 108 | 109 | A **_refresh_token_** grant type will allow you to generate a new access token with a new expiry time and obtain a new **_refresh token_**. (The previous refresh token will be invalidated once used). 110 | 111 | The required parameter for this grant type is the refresh token you were previously provided. 112 | 113 | Sample request: 114 | ```html 115 | POST https://localhost:44305/Token HTTP/1.1 116 | Host: localhost:44305 117 | User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0 118 | Accept: */* 119 | Content-Type: application/x-www-form-urlencoded; charset=UTF-8 120 | X-Requested-With: XMLHttpRequest 121 | Referer: https://localhost:44305/ 122 | Content-Length: 68 123 | 124 | grant_type:refresh_token&refresh_token:77e248a4a3814308931d63b10fb1e7f7 125 | ``` 126 | 127 | HTTP Response 128 | 129 | ```json 130 | { 131 | "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....", 132 | "expires_in": 900, 133 | "refresh_token": "3521442655fc4ec5b41a1b2d9ce846aa" 134 | } 135 | ``` 136 | 137 | ## Available stores 138 | 139 | JWT Simple Server has four different store implementations: 140 | 141 | * In-memory store 142 | 143 | ```csharp 144 | public void ConfigureServices(IServiceCollection services) 145 | { 146 | services 147 | .AddSingleton() 148 | .AddJwtSimpleServer(setup => 149 | { 150 | setup.IssuerSigningKey = SigningKey; 151 | }) 152 | .AddJwtInMemoryRefreshTokenStore(); 153 | } 154 | ``` 155 | 156 | * Entity framework store 157 | 158 | ```csharp 159 | public void ConfigureServices(IServiceCollection services) 160 | { 161 | services 162 | .AddScoped() 163 | .AddJwtSimpleServer(options => options.IssuerSigningKey = SigningKey) 164 | .AddJwtEntityFrameworkCoreRefreshTokenStore(options => 165 | { 166 | options.ConfigureDbContext = builder => 167 | { 168 | builder.UseSqlServer( 169 | Configuration["ConnectionStrings:DefaultConnection"], 170 | sqlServerOptions => sqlServerOptions.MigrationsAssembly(typeof(Startup).Assembly.FullName)); 171 | }; 172 | }); 173 | } 174 | ``` 175 | * Redis store 176 | 177 | ```csharp 178 | public void ConfigureServices(IServiceCollection services) 179 | { 180 | services.AddSingleton() 181 | .AddJwtSimpleServer(setup => 182 | { 183 | setup.IssuerSigningKey = SigningKey; 184 | }) 185 | .AddDistributedRedisRefreshStokenStore( setup => 186 | { 187 | setup.Configuration = "localhost"; //Provide your redis server configuration 188 | setup.InstanceName = "JwtSimpleServerInstance"; 189 | }); 190 | } 191 | ``` 192 | 193 | * Message pack binary store 194 | 195 | ```csharp 196 | public void ConfigureServices(IServiceCollection services) 197 | { 198 | services.AddSingleton() 199 | .AddJwtSimpleServer(setup => 200 | { 201 | setup.IssuerSigningKey = SigningKey; 202 | }) 203 | .AddJwtMessagePackRefreshTokenStore(setup => 204 | { 205 | setup.Path = "MyBinaryStore.bin"; 206 | }); 207 | } 208 | ``` 209 | 210 | You can create your own store service by implementing __IRefreshTokenStore__ interface and registering it in the inversion of control container. 211 | 212 | ## Samples 213 | 214 | We have some samples with different store configurations available [here](https://github.com/Xabaril/JWTSimpleServer/tree/master/samples). 215 | 216 | If you launch the projects you can try a simple playground to get access tokens and try the refresh token renewal service. 217 | 218 | ![JWTSimpleServer playground](https://preview.ibb.co/mkhSAn/playground.png) 219 | 220 | 221 | ## Typescript library 222 | 223 | The typescript library will allow you to easily interact will the token endpoint. 224 | 225 | Follow this steps to create your client if you are using the **browser** bundled library: 226 | 227 | **NPM - Installing the library** 228 | ``` 229 | npm install jwt-simpleserver-client --save 230 | ``` 231 | 232 | **1. Create the client options** 233 | 234 | ```javascript 235 | var defaultServerOptions = new JwtSimpleServer.ClientOptions(); 236 | ``` 237 | 238 | Client options parameters have default values listed in this table: 239 | 240 | | Parameter | default value | 241 | |--:|---| 242 | | tokenEndpoint | "/token" | 243 | | host | window.location.origin | 244 | | httpClient | XMLHttpRequestClient | 245 | 246 | NOTE: You can implement your own **HttpClient** by implementing our HttpClient abstract class 247 | 248 | 249 | **2. Creat the client providing the options object:** 250 | 251 | ```javascript 252 | var simpleServerClient = new JwtSimpleServer.ServerClient(defaultServerOptions); 253 | ``` 254 | 255 | 3. Request an access token by executing _requestAccessToken_ method: 256 | 257 | ```javascript 258 | simpleServerClient.requestAccessToken({ userName: "demo", password: "demo" }) 259 | .then(token => { 260 | // your token object will have the access token and expiral, and if configured: the refresh token 261 | }): 262 | ``` 263 | 264 | *_Client events_ 265 | 266 | JWT client have several observables you can subscribe to: 267 | 268 | | Observable | return value | description | 269 | |--:|--: |---| 270 | | onBeforeRequestAccessToken | void | Will notify observers before starting the token request to the server | 271 | | onRequestAccessTokenSuccess | Token | Will notify observers passing the retrieved token as parameter | 272 | | onBeforeRequestRefreshToken | void | Will notify observers before starting the refresh token request to the server | 273 | | onRequestRefreshTokenSuccess | Token | Will notify observers passing the retrieved refresh token as parameter | 274 | 275 | 276 | 277 | 278 | **4. Optional: If you want the library to request new access tokens given an interval you can configure the __RefreshTokenService__** 279 | 280 | ```javascript 281 | var refreshService = new JwtSimpleServer.RefreshTokenService(simpleServerClient); 282 | 283 | let onTokenRefreshedFunction = (token) => { 284 | console.log("Refresh token service:", token); 285 | } 286 | 287 | //Start the renewal service 288 | refreshService.start({ 289 | intervalSeconds: 10, 290 | refreshToken, 291 | onRefreshTokenSuccessCallback: onTokenRefreshedFunction 292 | }); 293 | 294 | //Stop the renewal service 295 | refreshService.stop(); 296 | ``` 297 | 298 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | image: Visual Studio 2017 5 | nuget: 6 | disable_publish_on_pr: true 7 | build_script: 8 | - ps: .\Build.ps1 9 | test: off 10 | artifacts: 11 | - path: .\artifacts\*.nupkg 12 | name: NuGet 13 | deploy: 14 | - provider: NuGet 15 | server: https://www.myget.org/F/xabaril/api/v2/package 16 | api_key: 17 | secure: JCTBs2rhU7P11G6ZbxDKOuQoyIKhILGcaKtILxCDwdVlnzvvaXc92nNXQlVsR7u0 18 | skip_symbols: true 19 | on: 20 | branch: master 21 | - provider: NuGet 22 | name: production 23 | api_key: 24 | secure: 44bohP+05nCVBsZvK3tklX4yK8Rwi7vWRKTj1sMI1V26twqZJayTtRxv0fLF1t2o 25 | on: 26 | appveyor_repo_tag: true -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | # Taken from psake https://github.com/psake/psake 2 | 3 | <# 4 | .SYNOPSIS 5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode 6 | to see if an error occcured. If an error is detected then an exception is thrown. 7 | This function allows you to run command-line programs without having to 8 | explicitly check the $lastexitcode variable. 9 | .EXAMPLE 10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" 11 | #> 12 | function Exec 13 | { 14 | [CmdletBinding()] 15 | param( 16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, 17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) 18 | ) 19 | & $cmd 20 | if ($lastexitcode -ne 0) { 21 | throw ("Exec: " + $errorMessage) 22 | } 23 | } 24 | 25 | if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse } 26 | 27 | exec { & dotnet restore } 28 | 29 | $tag = $(git tag -l --points-at HEAD) 30 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; 31 | $suffix = @{ $true = ""; $false = "ci-$revision"}[$tag -ne $NULL -and $revision -ne "local"] 32 | $commitHash = $(git rev-parse --short HEAD) 33 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] 34 | 35 | echo "build: Tag is $tag" 36 | echo "build: Package version suffix is $suffix" 37 | echo "build: Build version suffix is $buildSuffix" 38 | 39 | exec { & dotnet build JWTSimpleServer.sln -c Release --version-suffix=$buildSuffix -v q /nologo } 40 | 41 | echo "running tests" 42 | 43 | try { 44 | 45 | Push-Location -Path .\test\FunctionalTests 46 | 47 | exec { & dotnet test } 48 | } finally { 49 | Pop-Location 50 | } 51 | 52 | 53 | if ($suffix -eq "") { 54 | exec { & dotnet pack .\src\JWTSimpleServer\JWTSimpleServer.csproj -c Release -o ..\..\artifacts --include-symbols --no-build } 55 | exec { & dotnet pack .\src\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build } 56 | exec { & dotnet pack .\src\JWTSimpleServer.InMemoryRefreshTokenStore\JWTSimpleServer.InMemoryRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build } 57 | exec { & dotnet pack .\src\JWTSimpleServer.MessagePackRefreshTokenStore\JWTSimpleServer.MessagePackRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build } 58 | exec { & dotnet pack .\src\JWTSimpleServer.RedisDistributedRefreshTokenStore\JWTSimpleServer.RedisDistributedRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build } 59 | } else { 60 | exec { & dotnet pack .\src\JWTSimpleServer\JWTSimpleServer.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix } 61 | exec { & dotnet pack .\src\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore\JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix } 62 | exec { & dotnet pack .\src\JWTSimpleServer.InMemoryRefreshTokenStore\JWTSimpleServer.InMemoryRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix } 63 | exec { & dotnet pack .\src\JWTSimpleServer.MessagePackRefreshTokenStore\JWTSimpleServer.MessagePackRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix } 64 | exec { & dotnet pack .\src\JWTSimpleServer.RedisDistributedRefreshTokenStore\JWTSimpleServer.RedisDistributedRefreshTokenStore.csproj -c Release -o ..\..\artifacts --include-symbols --no-build --version-suffix=$suffix } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /build/dependencies.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | netcoreapp2.1 5 | 15.7.2 6 | 7 | 8 | 9 | 2.1.0 10 | 2.1.0 11 | 2.1.0 12 | 2.1.0 13 | 2.1.0 14 | 2.1.0 15 | 2.1.0 16 | 2.1.0 17 | 5.3.2 18 | 2.3.1 19 | 2.3.1 20 | 2.1.0 21 | 2.1.0 22 | 2.1.0 23 | 1.7.3.4 24 | 25 | 26 | 27 | 2.3.1 28 | 29 | 30 | 31 | https://github.com/Xabaril/JWTSimpleServer/blob/master/LICENSE 32 | https://github.com/Xabaril/JWTSimpleServer 33 | https://github.com/Xabaril/JWTSimpleServer 34 | Xabaril Contributors 35 | Xabaril 36 | 0.0.2 37 | 38 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/CustomAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using JWTSimpleServer.Abstractions; 3 | using System.Collections.Generic; 4 | using System.Security.Claims; 5 | using System.Threading.Tasks; 6 | 7 | namespace SimpleServerEFCoreStorage 8 | { 9 | public class CustomAuthenticationProvider : IAuthenticationProvider 10 | { 11 | public Task ValidateClientAuthentication(JwtSimpleServerContext context) 12 | { 13 | if(context.UserName == "demo" && context.Password == "demo") 14 | { 15 | var claims = new List(); 16 | claims.Add(new Claim(ClaimTypes.Name, "demo")); 17 | 18 | context.Success(claims); 19 | } 20 | else 21 | { 22 | context.Reject("Invalid user authentication"); 23 | } 24 | 25 | return Task.CompletedTask; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/JwtSimpleServerContextFactory.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Design; 4 | using Microsoft.Extensions.Configuration; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace SimpleServerEFCoreStorage 11 | { 12 | public class JwtSimpleServerContextFactory : IDesignTimeDbContextFactory 13 | { 14 | public JwtSimpleServerDbContext CreateDbContext(string[] args) 15 | { 16 | var configuration = new ConfigurationBuilder() 17 | .AddUserSecrets() 18 | .Build(); 19 | var optionsBuilder = new DbContextOptionsBuilder(); 20 | optionsBuilder.UseSqlServer( 21 | configuration["ConnectionStrings:DefaultConnection"], 22 | sqlServerOptions => sqlServerOptions.MigrationsAssembly(typeof(Startup).Assembly.FullName)); 23 | 24 | return new JwtSimpleServerDbContext(optionsBuilder.Options); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/Migrations/20180209140332_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore; 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 SimpleServerEFCoreStorage.Migrations 12 | { 13 | [DbContext(typeof(JwtSimpleServerDbContext))] 14 | [Migration("20180209140332_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.1-rtm-125") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.JwtToken", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("AccessToken"); 30 | 31 | b.Property("CreatedAt"); 32 | 33 | b.Property("RefreshToken"); 34 | 35 | b.HasKey("Id"); 36 | 37 | b.ToTable("Tokens"); 38 | }); 39 | #pragma warning restore 612, 618 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/Migrations/20180209140332_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace SimpleServerEFCoreStorage.Migrations 7 | { 8 | public partial class Initial : Migration 9 | { 10 | protected override void Up(MigrationBuilder migrationBuilder) 11 | { 12 | migrationBuilder.CreateTable( 13 | name: "Tokens", 14 | columns: table => new 15 | { 16 | Id = table.Column(nullable: false) 17 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 18 | AccessToken = table.Column(nullable: true), 19 | CreatedAt = table.Column(nullable: false), 20 | RefreshToken = table.Column(nullable: true) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Tokens", x => x.Id); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "Tokens"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/Migrations/JwtSimpleServerDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore; 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 SimpleServerEFCoreStorage.Migrations 12 | { 13 | [DbContext(typeof(JwtSimpleServerDbContext))] 14 | partial class JwtSimpleServerDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.1-rtm-125") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.JwtToken", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("AccessToken"); 29 | 30 | b.Property("CreatedAt"); 31 | 32 | b.Property("RefreshToken"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.ToTable("Tokens"); 37 | }); 38 | #pragma warning restore 612, 618 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace SimpleServerEFCoreStorage 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildMyWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildMyWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/SimpleServerEFCoreStorage.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(NetCoreTargetVersion) 5 | JwtSimpleServerEFCore 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JWTSimpleServer.Abstractions; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.Extensions.Configuration; 12 | 13 | namespace SimpleServerEFCoreStorage 14 | { 15 | public class Startup 16 | { 17 | public const string SigningKey = "InMemorySampleSigningKey"; 18 | 19 | public IConfiguration Configuration { get; } 20 | 21 | public Startup(IConfiguration configuration) 22 | { 23 | Configuration = configuration; 24 | } 25 | 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services 29 | .AddScoped() 30 | .AddJwtSimpleServer(options => options.IssuerSigningKey = SigningKey) 31 | .AddJwtEntityFrameworkCoreRefreshTokenStore(options => 32 | { 33 | options.ConfigureDbContext = builder => 34 | { 35 | builder.UseSqlServer( 36 | Configuration["ConnectionStrings:DefaultConnection"], 37 | sqlServerOptions => sqlServerOptions.MigrationsAssembly(typeof(Startup).Assembly.FullName)); 38 | }; 39 | }) 40 | .AddMvcCore() 41 | .AddAuthorization() 42 | .AddJsonFormatters(); 43 | } 44 | 45 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 46 | { 47 | app 48 | .UseDefaultFiles() 49 | .UseStaticFiles() 50 | .UseJwtSimpleServer(setup => 51 | { 52 | setup.IssuerSigningKey = SigningKey; 53 | }) 54 | .UseMvc(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | JWT Simple Server Playground 15 |
16 | 17 |
18 |
19 |
20 | Request token 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | Call api/test protected controller with token: 31 | 32 | 33 |
34 | 35 |
36 |
37 |
38 | Refresh token service setup 39 |
40 |
41 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 138 | 139 | 140 | 141 | 155 | -------------------------------------------------------------------------------- /samples/SimpleServerEFCoreStorage/wwwroot/simple-server-client.js: -------------------------------------------------------------------------------- 1 | window["JwtSimpleServer"] = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { 41 | /******/ configurable: false, 42 | /******/ enumerable: true, 43 | /******/ get: getter 44 | /******/ }); 45 | /******/ } 46 | /******/ }; 47 | /******/ 48 | /******/ // getDefaultExport function for compatibility with non-harmony modules 49 | /******/ __webpack_require__.n = function(module) { 50 | /******/ var getter = module && module.__esModule ? 51 | /******/ function getDefault() { return module['default']; } : 52 | /******/ function getModuleExports() { return module; }; 53 | /******/ __webpack_require__.d(getter, 'a', getter); 54 | /******/ return getter; 55 | /******/ }; 56 | /******/ 57 | /******/ // Object.prototype.hasOwnProperty.call 58 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 59 | /******/ 60 | /******/ // __webpack_public_path__ 61 | /******/ __webpack_require__.p = ""; 62 | /******/ 63 | /******/ // Load entry module and return exports 64 | /******/ return __webpack_require__(__webpack_require__.s = 0); 65 | /******/ }) 66 | /************************************************************************/ 67 | /******/ ([ 68 | /* 0 */ 69 | /***/ (function(module, exports, __webpack_require__) { 70 | 71 | "use strict"; 72 | 73 | function __export(m) { 74 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 75 | } 76 | Object.defineProperty(exports, "__esModule", { value: true }); 77 | __export(__webpack_require__(1)); 78 | __export(__webpack_require__(6)); 79 | 80 | 81 | /***/ }), 82 | /* 1 */ 83 | /***/ (function(module, exports, __webpack_require__) { 84 | 85 | "use strict"; 86 | 87 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 88 | return new (P || (P = Promise))(function (resolve, reject) { 89 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 90 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 91 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 92 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 93 | }); 94 | }; 95 | var __generator = (this && this.__generator) || function (thisArg, body) { 96 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 97 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 98 | function verb(n) { return function (v) { return step([n, v]); }; } 99 | function step(op) { 100 | if (f) throw new TypeError("Generator is already executing."); 101 | while (_) try { 102 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 103 | if (y = 0, t) op = [0, t.value]; 104 | switch (op[0]) { 105 | case 0: case 1: t = op; break; 106 | case 4: _.label++; return { value: op[1], done: false }; 107 | case 5: _.label++; y = op[1]; op = [0]; continue; 108 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 109 | default: 110 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 111 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 112 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 113 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 114 | if (t[2]) _.ops.pop(); 115 | _.trys.pop(); continue; 116 | } 117 | op = body.call(thisArg, _); 118 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 119 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 120 | } 121 | }; 122 | Object.defineProperty(exports, "__esModule", { value: true }); 123 | var httpClient_1 = __webpack_require__(2); 124 | var observable_1 = __webpack_require__(5); 125 | var ClientOptions = /** @class */ (function () { 126 | function ClientOptions() { 127 | this.tokenEndpoint = "/token"; 128 | this.host = window.location.origin; 129 | } 130 | return ClientOptions; 131 | }()); 132 | exports.ClientOptions = ClientOptions; 133 | var ServerClient = /** @class */ (function () { 134 | function ServerClient(options) { 135 | this.options = options; 136 | this.onBeforeRequestAccessToken = new observable_1.Observable(); 137 | this.onRequestAccessTokenSuccess = new observable_1.Observable(); 138 | this.onBeforeRequestRefreshToken = new observable_1.Observable(); 139 | this.onRequestRefreshTokenSuccess = new observable_1.Observable(); 140 | this._httpClient = options.httpClient || new httpClient_1.XMLHttpRequestClient(); 141 | } 142 | ServerClient.prototype.requestAccessToken = function (credentials) { 143 | return __awaiter(this, void 0, void 0, function () { 144 | var requestContent, token; 145 | return __generator(this, function (_a) { 146 | switch (_a.label) { 147 | case 0: 148 | this.onBeforeRequestAccessToken.notify(undefined); 149 | requestContent = "grant_type=password&username=" + credentials.userName + "&password=" + credentials.password; 150 | return [4 /*yield*/, this._postTokenRequest(requestContent)]; 151 | case 1: 152 | token = _a.sent(); 153 | this.onRequestAccessTokenSuccess.notify(token); 154 | return [2 /*return*/, token]; 155 | } 156 | }); 157 | }); 158 | }; 159 | ServerClient.prototype.refreshAccessToken = function (credentials) { 160 | return __awaiter(this, void 0, void 0, function () { 161 | var content, token; 162 | return __generator(this, function (_a) { 163 | switch (_a.label) { 164 | case 0: 165 | this.onBeforeRequestRefreshToken.notify(undefined); 166 | content = "grant_type=refresh_token&refresh_token=" + credentials.refreshToken; 167 | return [4 /*yield*/, this._postTokenRequest(content)]; 168 | case 1: 169 | token = _a.sent(); 170 | this.onRequestRefreshTokenSuccess.notify(token); 171 | return [2 /*return*/, token]; 172 | } 173 | }); 174 | }); 175 | }; 176 | ServerClient.prototype._postTokenRequest = function (content) { 177 | return __awaiter(this, void 0, void 0, function () { 178 | var _a, host, tokenEndpoint, response; 179 | return __generator(this, function (_b) { 180 | switch (_b.label) { 181 | case 0: 182 | _a = this.options, host = _a.host, tokenEndpoint = _a.tokenEndpoint; 183 | return [4 /*yield*/, this._httpClient.post("" + host + tokenEndpoint, { 184 | content: content 185 | })]; 186 | case 1: 187 | response = _b.sent(); 188 | return [2 /*return*/, this._buildTokenFromResponse(response)]; 189 | } 190 | }); 191 | }); 192 | }; 193 | ServerClient.prototype._buildTokenFromResponse = function (response) { 194 | return JSON.parse(response.content); 195 | }; 196 | return ServerClient; 197 | }()); 198 | exports.ServerClient = ServerClient; 199 | 200 | 201 | /***/ }), 202 | /* 2 */ 203 | /***/ (function(module, exports, __webpack_require__) { 204 | 205 | "use strict"; 206 | 207 | var __extends = (this && this.__extends) || (function () { 208 | var extendStatics = Object.setPrototypeOf || 209 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 210 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 211 | return function (d, b) { 212 | extendStatics(d, b); 213 | function __() { this.constructor = d; } 214 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 215 | }; 216 | })(); 217 | var __assign = (this && this.__assign) || Object.assign || function(t) { 218 | for (var s, i = 1, n = arguments.length; i < n; i++) { 219 | s = arguments[i]; 220 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 221 | t[p] = s[p]; 222 | } 223 | return t; 224 | }; 225 | Object.defineProperty(exports, "__esModule", { value: true }); 226 | var httpResponse_1 = __webpack_require__(3); 227 | var httpError_1 = __webpack_require__(4); 228 | var HttpClient = /** @class */ (function () { 229 | function HttpClient() { 230 | } 231 | HttpClient.prototype.post = function (url, options) { 232 | return this.send(__assign({}, options, { method: "POST", url: url })); 233 | }; 234 | return HttpClient; 235 | }()); 236 | exports.HttpClient = HttpClient; 237 | var XMLHttpRequestClient = /** @class */ (function (_super) { 238 | __extends(XMLHttpRequestClient, _super); 239 | function XMLHttpRequestClient() { 240 | return _super !== null && _super.apply(this, arguments) || this; 241 | } 242 | XMLHttpRequestClient.prototype.send = function (request) { 243 | return new Promise(function (resolve, reject) { 244 | var xhr = new XMLHttpRequest(); 245 | xhr.open(request.method, request.url, true); 246 | xhr.setRequestHeader("X-Request-Client", "XMLHttpClient"); 247 | xhr.setRequestHeader("Content-type", request.contentType || "application/x-www-form-urlencoded"); 248 | xhr.onload = function () { 249 | if (xhr.status >= 200 && xhr.status < 300) { 250 | resolve(new httpResponse_1.HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText)); 251 | } 252 | else { 253 | reject(new httpError_1.HttpError(xhr.statusText, xhr.status)); 254 | } 255 | }; 256 | xhr.onerror = function () { 257 | reject(new httpError_1.HttpError(xhr.statusText, xhr.status)); 258 | }; 259 | xhr.ontimeout = function () { 260 | reject(new httpError_1.HttpError("Operation timeout", 500)); 261 | }; 262 | xhr.send(request.content || ""); 263 | }); 264 | }; 265 | return XMLHttpRequestClient; 266 | }(HttpClient)); 267 | exports.XMLHttpRequestClient = XMLHttpRequestClient; 268 | 269 | 270 | /***/ }), 271 | /* 3 */ 272 | /***/ (function(module, exports, __webpack_require__) { 273 | 274 | "use strict"; 275 | 276 | Object.defineProperty(exports, "__esModule", { value: true }); 277 | var HttpResponse = /** @class */ (function () { 278 | function HttpResponse(statusCode, statusText, content) { 279 | this.statusCode = statusCode; 280 | this.statusText = statusText; 281 | this.content = content; 282 | } 283 | return HttpResponse; 284 | }()); 285 | exports.HttpResponse = HttpResponse; 286 | 287 | 288 | /***/ }), 289 | /* 4 */ 290 | /***/ (function(module, exports, __webpack_require__) { 291 | 292 | "use strict"; 293 | 294 | var __extends = (this && this.__extends) || (function () { 295 | var extendStatics = Object.setPrototypeOf || 296 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 297 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 298 | return function (d, b) { 299 | extendStatics(d, b); 300 | function __() { this.constructor = d; } 301 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 302 | }; 303 | })(); 304 | Object.defineProperty(exports, "__esModule", { value: true }); 305 | var HttpError = /** @class */ (function (_super) { 306 | __extends(HttpError, _super); 307 | function HttpError(errorMessage, statusCode) { 308 | var _this = _super.call(this, errorMessage) || this; 309 | _this.statusCode = statusCode; 310 | return _this; 311 | } 312 | return HttpError; 313 | }(Error)); 314 | exports.HttpError = HttpError; 315 | 316 | 317 | /***/ }), 318 | /* 5 */ 319 | /***/ (function(module, exports, __webpack_require__) { 320 | 321 | "use strict"; 322 | 323 | Object.defineProperty(exports, "__esModule", { value: true }); 324 | /** 325 | * Creates a new observer 326 | * @param callback defines the callback to call when the observer is notified 327 | * @param scope defines the current scope used to restore the JS context 328 | */ 329 | var Observer = /** @class */ (function () { 330 | function Observer(callback, scope) { 331 | if (scope === void 0) { scope = null; } 332 | this.callback = callback; 333 | this.scope = scope; 334 | } 335 | return Observer; 336 | }()); 337 | exports.Observer = Observer; 338 | /** 339 | * The Observable class is a simple implementation of the Observable pattern. 340 | */ 341 | var Observable = /** @class */ (function () { 342 | function Observable() { 343 | this._observers = new Array(); 344 | } 345 | Observable.prototype.subscribe = function (callback) { 346 | if (!callback) 347 | throw Error("You should provide a callback to subscribe to an observable"); 348 | var observer = new Observer(callback); 349 | this._observers.push(observer); 350 | return observer; 351 | }; 352 | Observable.prototype.notify = function (eventData) { 353 | if (!this.hasObservers()) 354 | return; 355 | for (var _i = 0, _a = this._observers; _i < _a.length; _i++) { 356 | var observer = _a[_i]; 357 | if (observer.scope) { 358 | observer.callback.call(observer.scope, eventData); 359 | } 360 | else { 361 | observer.callback(eventData); 362 | } 363 | } 364 | }; 365 | Observable.prototype.remove = function (observer) { 366 | if (!observer) 367 | return false; 368 | var index = this._observers.indexOf(observer); 369 | if (index !== -1) { 370 | this._observers.splice(index, 1); 371 | return true; 372 | } 373 | return false; 374 | }; 375 | Observable.prototype.hasObservers = function () { 376 | return this._observers.length > 0; 377 | }; 378 | return Observable; 379 | }()); 380 | exports.Observable = Observable; 381 | 382 | 383 | /***/ }), 384 | /* 6 */ 385 | /***/ (function(module, exports, __webpack_require__) { 386 | 387 | "use strict"; 388 | 389 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 390 | return new (P || (P = Promise))(function (resolve, reject) { 391 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 392 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 393 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 394 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 395 | }); 396 | }; 397 | var __generator = (this && this.__generator) || function (thisArg, body) { 398 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 399 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 400 | function verb(n) { return function (v) { return step([n, v]); }; } 401 | function step(op) { 402 | if (f) throw new TypeError("Generator is already executing."); 403 | while (_) try { 404 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 405 | if (y = 0, t) op = [0, t.value]; 406 | switch (op[0]) { 407 | case 0: case 1: t = op; break; 408 | case 4: _.label++; return { value: op[1], done: false }; 409 | case 5: _.label++; y = op[1]; op = [0]; continue; 410 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 411 | default: 412 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 413 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 414 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 415 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 416 | if (t[2]) _.ops.pop(); 417 | _.trys.pop(); continue; 418 | } 419 | op = body.call(thisArg, _); 420 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 421 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 422 | } 423 | }; 424 | Object.defineProperty(exports, "__esModule", { value: true }); 425 | var RefreshTokenServiceOptions = /** @class */ (function () { 426 | function RefreshTokenServiceOptions(intervalSeconds, refreshToken, onRefreshTokenSuccessCallback) { 427 | if (intervalSeconds === void 0) { intervalSeconds = null; } 428 | if (refreshToken === void 0) { refreshToken = ""; } 429 | this.intervalSeconds = intervalSeconds; 430 | this.refreshToken = refreshToken; 431 | this.onRefreshTokenSuccessCallback = onRefreshTokenSuccessCallback; 432 | } 433 | return RefreshTokenServiceOptions; 434 | }()); 435 | exports.RefreshTokenServiceOptions = RefreshTokenServiceOptions; 436 | var RefreshTokenService = /** @class */ (function () { 437 | function RefreshTokenService(client) { 438 | this.client = client; 439 | this._aborted = false; 440 | } 441 | RefreshTokenService.prototype.start = function (refreshTokenOptions) { 442 | var _this = this; 443 | this._aborted = false; 444 | this._ensureOptions(refreshTokenOptions); 445 | this._refreshSubscription = this.client.onRequestRefreshTokenSuccess.subscribe(function (token) { 446 | refreshTokenOptions.onRefreshTokenSuccessCallback && 447 | refreshTokenOptions.onRefreshTokenSuccessCallback(token); 448 | }); 449 | this._intervalSubscription = setInterval(function () { return __awaiter(_this, void 0, void 0, function () { 450 | var token; 451 | return __generator(this, function (_a) { 452 | switch (_a.label) { 453 | case 0: 454 | if (this._aborted) 455 | return [2 /*return*/]; 456 | return [4 /*yield*/, this.client.refreshAccessToken({ refreshToken: refreshTokenOptions.refreshToken })]; 457 | case 1: 458 | token = _a.sent(); 459 | refreshTokenOptions.refreshToken = token.refresh_token; 460 | return [2 /*return*/]; 461 | } 462 | }); 463 | }); }, refreshTokenOptions.intervalSeconds * 1000); 464 | }; 465 | RefreshTokenService.prototype.stop = function () { 466 | this._aborted = true; 467 | if (this._intervalSubscription !== 0) { 468 | clearInterval(this._intervalSubscription); 469 | this._intervalSubscription = 0; 470 | } 471 | if (this._refreshSubscription) { 472 | this.client.onRequestRefreshTokenSuccess.remove(this._refreshSubscription); 473 | this._refreshSubscription = undefined; 474 | } 475 | }; 476 | RefreshTokenService.prototype._ensureOptions = function (options) { 477 | if (!options.onRefreshTokenSuccessCallback) { 478 | throw Error("You must provide a callback to start the RefreshTokenService"); 479 | } 480 | if (!options.intervalSeconds) { 481 | throw Error("You must provide the refresh token interval"); 482 | } 483 | }; 484 | return RefreshTokenService; 485 | }()); 486 | exports.RefreshTokenService = RefreshTokenService; 487 | 488 | 489 | /***/ }) 490 | /******/ ]); 491 | //# sourceMappingURL=simple-server-client.js.map -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/Controllers/TestController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 9 | 10 | namespace SimpleServerInMemoryStorage.Controllers 11 | { 12 | [Route("api/[controller]")] 13 | [Authorize] 14 | public class TestController : Controller 15 | { 16 | public IActionResult Get() 17 | { 18 | return Ok("Answer from controller : Authorization successful"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/CustomAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using JWTSimpleServer.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleServerInMemoryStorage 10 | { 11 | public class CustomAuthenticationProvider : IAuthenticationProvider 12 | { 13 | public Task ValidateClientAuthentication(JwtSimpleServerContext context) 14 | { 15 | if(context.UserName == "demo" && context.Password == "demo") 16 | { 17 | var claims = new List(); 18 | claims.Add(new Claim(ClaimTypes.Name, "demo")); 19 | 20 | context.Success(claims); 21 | } 22 | else 23 | { 24 | context.Reject("Invalid user authentication"); 25 | } 26 | 27 | return Task.CompletedTask; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace SimpleServerInMemoryStorage 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/SimpleServerInMemoryStorage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetCoreTargetVersion) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JWTSimpleServer.Abstractions; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | namespace SimpleServerInMemoryStorage 12 | { 13 | public class Startup 14 | { 15 | public const string SigningKey = "InMemorySampleSigningKey"; 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | services.AddSingleton() 19 | .AddJwtSimpleServer(setup => 20 | { 21 | setup.IssuerSigningKey = SigningKey; 22 | }) 23 | .AddJwtInMemoryRefreshTokenStore() 24 | .AddMvcCore(). 25 | AddAuthorization(); 26 | } 27 | 28 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 29 | { 30 | app.UseDefaultFiles(); 31 | app.UseStaticFiles(); 32 | 33 | app.UseJwtSimpleServer(setup => 34 | { 35 | setup.IssuerSigningKey = SigningKey; 36 | }); 37 | 38 | app.UseMvc(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | JWT Simple Server Playground 15 |
16 | 17 |
18 |
19 |
20 | Request token 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | Call api/test protected controller with token: 31 | 32 | 33 |
34 | 35 |
36 |
37 |
38 | Refresh token service setup 39 |
40 |
41 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 138 | 139 | 140 | 141 | 155 | -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/wwwroot/simple-server-client.js: -------------------------------------------------------------------------------- 1 | window["JwtSimpleServer"] = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { 41 | /******/ configurable: false, 42 | /******/ enumerable: true, 43 | /******/ get: getter 44 | /******/ }); 45 | /******/ } 46 | /******/ }; 47 | /******/ 48 | /******/ // getDefaultExport function for compatibility with non-harmony modules 49 | /******/ __webpack_require__.n = function(module) { 50 | /******/ var getter = module && module.__esModule ? 51 | /******/ function getDefault() { return module['default']; } : 52 | /******/ function getModuleExports() { return module; }; 53 | /******/ __webpack_require__.d(getter, 'a', getter); 54 | /******/ return getter; 55 | /******/ }; 56 | /******/ 57 | /******/ // Object.prototype.hasOwnProperty.call 58 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 59 | /******/ 60 | /******/ // __webpack_public_path__ 61 | /******/ __webpack_require__.p = ""; 62 | /******/ 63 | /******/ // Load entry module and return exports 64 | /******/ return __webpack_require__(__webpack_require__.s = 0); 65 | /******/ }) 66 | /************************************************************************/ 67 | /******/ ([ 68 | /* 0 */ 69 | /***/ (function(module, exports, __webpack_require__) { 70 | 71 | "use strict"; 72 | 73 | function __export(m) { 74 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 75 | } 76 | Object.defineProperty(exports, "__esModule", { value: true }); 77 | __export(__webpack_require__(1)); 78 | __export(__webpack_require__(6)); 79 | 80 | 81 | /***/ }), 82 | /* 1 */ 83 | /***/ (function(module, exports, __webpack_require__) { 84 | 85 | "use strict"; 86 | 87 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 88 | return new (P || (P = Promise))(function (resolve, reject) { 89 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 90 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 91 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 92 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 93 | }); 94 | }; 95 | var __generator = (this && this.__generator) || function (thisArg, body) { 96 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 97 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 98 | function verb(n) { return function (v) { return step([n, v]); }; } 99 | function step(op) { 100 | if (f) throw new TypeError("Generator is already executing."); 101 | while (_) try { 102 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 103 | if (y = 0, t) op = [0, t.value]; 104 | switch (op[0]) { 105 | case 0: case 1: t = op; break; 106 | case 4: _.label++; return { value: op[1], done: false }; 107 | case 5: _.label++; y = op[1]; op = [0]; continue; 108 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 109 | default: 110 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 111 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 112 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 113 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 114 | if (t[2]) _.ops.pop(); 115 | _.trys.pop(); continue; 116 | } 117 | op = body.call(thisArg, _); 118 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 119 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 120 | } 121 | }; 122 | Object.defineProperty(exports, "__esModule", { value: true }); 123 | var httpClient_1 = __webpack_require__(2); 124 | var observable_1 = __webpack_require__(5); 125 | var ClientOptions = /** @class */ (function () { 126 | function ClientOptions() { 127 | this.tokenEndpoint = "/token"; 128 | this.host = window.location.origin; 129 | } 130 | return ClientOptions; 131 | }()); 132 | exports.ClientOptions = ClientOptions; 133 | var ServerClient = /** @class */ (function () { 134 | function ServerClient(options) { 135 | this.options = options; 136 | this.onBeforeRequestAccessToken = new observable_1.Observable(); 137 | this.onRequestAccessTokenSuccess = new observable_1.Observable(); 138 | this.onBeforeRequestRefreshToken = new observable_1.Observable(); 139 | this.onRequestRefreshTokenSuccess = new observable_1.Observable(); 140 | this._httpClient = options.httpClient || new httpClient_1.XMLHttpRequestClient(); 141 | } 142 | ServerClient.prototype.requestAccessToken = function (credentials) { 143 | return __awaiter(this, void 0, void 0, function () { 144 | var requestContent, token; 145 | return __generator(this, function (_a) { 146 | switch (_a.label) { 147 | case 0: 148 | this.onBeforeRequestAccessToken.notify(undefined); 149 | requestContent = "grant_type=password&username=" + credentials.userName + "&password=" + credentials.password; 150 | return [4 /*yield*/, this._postTokenRequest(requestContent)]; 151 | case 1: 152 | token = _a.sent(); 153 | this.onRequestAccessTokenSuccess.notify(token); 154 | return [2 /*return*/, token]; 155 | } 156 | }); 157 | }); 158 | }; 159 | ServerClient.prototype.refreshAccessToken = function (credentials) { 160 | return __awaiter(this, void 0, void 0, function () { 161 | var content, token; 162 | return __generator(this, function (_a) { 163 | switch (_a.label) { 164 | case 0: 165 | this.onBeforeRequestRefreshToken.notify(undefined); 166 | content = "grant_type=refresh_token&refresh_token=" + credentials.refreshToken; 167 | return [4 /*yield*/, this._postTokenRequest(content)]; 168 | case 1: 169 | token = _a.sent(); 170 | this.onRequestRefreshTokenSuccess.notify(token); 171 | return [2 /*return*/, token]; 172 | } 173 | }); 174 | }); 175 | }; 176 | ServerClient.prototype._postTokenRequest = function (content) { 177 | return __awaiter(this, void 0, void 0, function () { 178 | var _a, host, tokenEndpoint, response; 179 | return __generator(this, function (_b) { 180 | switch (_b.label) { 181 | case 0: 182 | _a = this.options, host = _a.host, tokenEndpoint = _a.tokenEndpoint; 183 | return [4 /*yield*/, this._httpClient.post("" + host + tokenEndpoint, { 184 | content: content 185 | })]; 186 | case 1: 187 | response = _b.sent(); 188 | return [2 /*return*/, this._buildTokenFromResponse(response)]; 189 | } 190 | }); 191 | }); 192 | }; 193 | ServerClient.prototype._buildTokenFromResponse = function (response) { 194 | return JSON.parse(response.content); 195 | }; 196 | return ServerClient; 197 | }()); 198 | exports.ServerClient = ServerClient; 199 | 200 | 201 | /***/ }), 202 | /* 2 */ 203 | /***/ (function(module, exports, __webpack_require__) { 204 | 205 | "use strict"; 206 | 207 | var __extends = (this && this.__extends) || (function () { 208 | var extendStatics = Object.setPrototypeOf || 209 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 210 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 211 | return function (d, b) { 212 | extendStatics(d, b); 213 | function __() { this.constructor = d; } 214 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 215 | }; 216 | })(); 217 | var __assign = (this && this.__assign) || Object.assign || function(t) { 218 | for (var s, i = 1, n = arguments.length; i < n; i++) { 219 | s = arguments[i]; 220 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 221 | t[p] = s[p]; 222 | } 223 | return t; 224 | }; 225 | Object.defineProperty(exports, "__esModule", { value: true }); 226 | var httpResponse_1 = __webpack_require__(3); 227 | var httpError_1 = __webpack_require__(4); 228 | var HttpClient = /** @class */ (function () { 229 | function HttpClient() { 230 | } 231 | HttpClient.prototype.post = function (url, options) { 232 | return this.send(__assign({}, options, { method: "POST", url: url })); 233 | }; 234 | return HttpClient; 235 | }()); 236 | exports.HttpClient = HttpClient; 237 | var XMLHttpRequestClient = /** @class */ (function (_super) { 238 | __extends(XMLHttpRequestClient, _super); 239 | function XMLHttpRequestClient() { 240 | return _super !== null && _super.apply(this, arguments) || this; 241 | } 242 | XMLHttpRequestClient.prototype.send = function (request) { 243 | return new Promise(function (resolve, reject) { 244 | var xhr = new XMLHttpRequest(); 245 | xhr.open(request.method, request.url, true); 246 | xhr.setRequestHeader("X-Request-Client", "XMLHttpClient"); 247 | xhr.setRequestHeader("Content-type", request.contentType || "application/x-www-form-urlencoded"); 248 | xhr.onload = function () { 249 | if (xhr.status >= 200 && xhr.status < 300) { 250 | resolve(new httpResponse_1.HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText)); 251 | } 252 | else { 253 | reject(new httpError_1.HttpError(xhr.statusText, xhr.status)); 254 | } 255 | }; 256 | xhr.onerror = function () { 257 | reject(new httpError_1.HttpError(xhr.statusText, xhr.status)); 258 | }; 259 | xhr.ontimeout = function () { 260 | reject(new httpError_1.HttpError("Operation timeout", 500)); 261 | }; 262 | xhr.send(request.content || ""); 263 | }); 264 | }; 265 | return XMLHttpRequestClient; 266 | }(HttpClient)); 267 | exports.XMLHttpRequestClient = XMLHttpRequestClient; 268 | 269 | 270 | /***/ }), 271 | /* 3 */ 272 | /***/ (function(module, exports, __webpack_require__) { 273 | 274 | "use strict"; 275 | 276 | Object.defineProperty(exports, "__esModule", { value: true }); 277 | var HttpResponse = /** @class */ (function () { 278 | function HttpResponse(statusCode, statusText, content) { 279 | this.statusCode = statusCode; 280 | this.statusText = statusText; 281 | this.content = content; 282 | } 283 | return HttpResponse; 284 | }()); 285 | exports.HttpResponse = HttpResponse; 286 | 287 | 288 | /***/ }), 289 | /* 4 */ 290 | /***/ (function(module, exports, __webpack_require__) { 291 | 292 | "use strict"; 293 | 294 | var __extends = (this && this.__extends) || (function () { 295 | var extendStatics = Object.setPrototypeOf || 296 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 297 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 298 | return function (d, b) { 299 | extendStatics(d, b); 300 | function __() { this.constructor = d; } 301 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 302 | }; 303 | })(); 304 | Object.defineProperty(exports, "__esModule", { value: true }); 305 | var HttpError = /** @class */ (function (_super) { 306 | __extends(HttpError, _super); 307 | function HttpError(errorMessage, statusCode) { 308 | var _this = _super.call(this, errorMessage) || this; 309 | _this.statusCode = statusCode; 310 | return _this; 311 | } 312 | return HttpError; 313 | }(Error)); 314 | exports.HttpError = HttpError; 315 | 316 | 317 | /***/ }), 318 | /* 5 */ 319 | /***/ (function(module, exports, __webpack_require__) { 320 | 321 | "use strict"; 322 | 323 | Object.defineProperty(exports, "__esModule", { value: true }); 324 | /** 325 | * Creates a new observer 326 | * @param callback defines the callback to call when the observer is notified 327 | * @param scope defines the current scope used to restore the JS context 328 | */ 329 | var Observer = /** @class */ (function () { 330 | function Observer(callback, scope) { 331 | if (scope === void 0) { scope = null; } 332 | this.callback = callback; 333 | this.scope = scope; 334 | } 335 | return Observer; 336 | }()); 337 | exports.Observer = Observer; 338 | /** 339 | * The Observable class is a simple implementation of the Observable pattern. 340 | */ 341 | var Observable = /** @class */ (function () { 342 | function Observable() { 343 | this._observers = new Array(); 344 | } 345 | Observable.prototype.subscribe = function (callback) { 346 | if (!callback) 347 | throw Error("You should provide a callback to subscribe to an observable"); 348 | var observer = new Observer(callback); 349 | this._observers.push(observer); 350 | return observer; 351 | }; 352 | Observable.prototype.notify = function (eventData) { 353 | if (!this.hasObservers()) 354 | return; 355 | for (var _i = 0, _a = this._observers; _i < _a.length; _i++) { 356 | var observer = _a[_i]; 357 | if (observer.scope) { 358 | observer.callback.call(observer.scope, eventData); 359 | } 360 | else { 361 | observer.callback(eventData); 362 | } 363 | } 364 | }; 365 | Observable.prototype.remove = function (observer) { 366 | if (!observer) 367 | return false; 368 | var index = this._observers.indexOf(observer); 369 | if (index !== -1) { 370 | this._observers.splice(index, 1); 371 | return true; 372 | } 373 | return false; 374 | }; 375 | Observable.prototype.hasObservers = function () { 376 | return this._observers.length > 0; 377 | }; 378 | return Observable; 379 | }()); 380 | exports.Observable = Observable; 381 | 382 | 383 | /***/ }), 384 | /* 6 */ 385 | /***/ (function(module, exports, __webpack_require__) { 386 | 387 | "use strict"; 388 | 389 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 390 | return new (P || (P = Promise))(function (resolve, reject) { 391 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 392 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 393 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 394 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 395 | }); 396 | }; 397 | var __generator = (this && this.__generator) || function (thisArg, body) { 398 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 399 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 400 | function verb(n) { return function (v) { return step([n, v]); }; } 401 | function step(op) { 402 | if (f) throw new TypeError("Generator is already executing."); 403 | while (_) try { 404 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 405 | if (y = 0, t) op = [0, t.value]; 406 | switch (op[0]) { 407 | case 0: case 1: t = op; break; 408 | case 4: _.label++; return { value: op[1], done: false }; 409 | case 5: _.label++; y = op[1]; op = [0]; continue; 410 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 411 | default: 412 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 413 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 414 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 415 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 416 | if (t[2]) _.ops.pop(); 417 | _.trys.pop(); continue; 418 | } 419 | op = body.call(thisArg, _); 420 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 421 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 422 | } 423 | }; 424 | Object.defineProperty(exports, "__esModule", { value: true }); 425 | var RefreshTokenServiceOptions = /** @class */ (function () { 426 | function RefreshTokenServiceOptions(intervalSeconds, refreshToken, onRefreshTokenSuccessCallback) { 427 | if (intervalSeconds === void 0) { intervalSeconds = null; } 428 | if (refreshToken === void 0) { refreshToken = ""; } 429 | this.intervalSeconds = intervalSeconds; 430 | this.refreshToken = refreshToken; 431 | this.onRefreshTokenSuccessCallback = onRefreshTokenSuccessCallback; 432 | } 433 | return RefreshTokenServiceOptions; 434 | }()); 435 | exports.RefreshTokenServiceOptions = RefreshTokenServiceOptions; 436 | var RefreshTokenService = /** @class */ (function () { 437 | function RefreshTokenService(client) { 438 | this.client = client; 439 | this._aborted = false; 440 | } 441 | RefreshTokenService.prototype.start = function (refreshTokenOptions) { 442 | var _this = this; 443 | this._aborted = false; 444 | this._ensureOptions(refreshTokenOptions); 445 | this._refreshSubscription = this.client.onRequestRefreshTokenSuccess.subscribe(function (token) { 446 | refreshTokenOptions.onRefreshTokenSuccessCallback && 447 | refreshTokenOptions.onRefreshTokenSuccessCallback(token); 448 | }); 449 | this._intervalSubscription = setInterval(function () { return __awaiter(_this, void 0, void 0, function () { 450 | var token; 451 | return __generator(this, function (_a) { 452 | switch (_a.label) { 453 | case 0: 454 | if (this._aborted) 455 | return [2 /*return*/]; 456 | return [4 /*yield*/, this.client.refreshAccessToken({ refreshToken: refreshTokenOptions.refreshToken })]; 457 | case 1: 458 | token = _a.sent(); 459 | refreshTokenOptions.refreshToken = token.refresh_token; 460 | return [2 /*return*/]; 461 | } 462 | }); 463 | }); }, refreshTokenOptions.intervalSeconds * 1000); 464 | }; 465 | RefreshTokenService.prototype.stop = function () { 466 | this._aborted = true; 467 | if (this._intervalSubscription !== 0) { 468 | clearInterval(this._intervalSubscription); 469 | this._intervalSubscription = 0; 470 | } 471 | if (this._refreshSubscription) { 472 | this.client.onRequestRefreshTokenSuccess.remove(this._refreshSubscription); 473 | this._refreshSubscription = undefined; 474 | } 475 | }; 476 | RefreshTokenService.prototype._ensureOptions = function (options) { 477 | if (!options.onRefreshTokenSuccessCallback) { 478 | throw Error("You must provide a callback to start the RefreshTokenService"); 479 | } 480 | if (!options.intervalSeconds) { 481 | throw Error("You must provide the refresh token interval"); 482 | } 483 | }; 484 | return RefreshTokenService; 485 | }()); 486 | exports.RefreshTokenService = RefreshTokenService; 487 | 488 | 489 | /***/ }) 490 | /******/ ]); 491 | //# sourceMappingURL=simple-server-client.js.map -------------------------------------------------------------------------------- /samples/SimpleServerInMemoryStorage/wwwroot/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI'; 3 | } 4 | 5 | .top-buffer-10 { 6 | margin-top: 10px; 7 | } 8 | 9 | .top-buffer-20 { 10 | margin-top: 20px; 11 | } 12 | 13 | .left { 14 | float: left; 15 | } 16 | 17 | .center { 18 | display: flex; 19 | justify-content: center; 20 | } 21 | 22 | .logo { 23 | float: left; 24 | } 25 | 26 | .logo img { 27 | width: 100px; 28 | height: 120px; 29 | } 30 | 31 | .caption { 32 | color: #2e74b0; 33 | display: block; 34 | font-weight: bold; 35 | } 36 | .block { 37 | display: block; 38 | } 39 | .success { 40 | color: green; 41 | } 42 | 43 | .commit { 44 | text-overflow: ellipsis; 45 | width: 600px; 46 | display: block; 47 | overflow: hidden; 48 | margin-top: 15px; 49 | } 50 | 51 | textarea { 52 | resize: none; 53 | } 54 | .error { 55 | color:red; 56 | } 57 | 58 | .hidden { 59 | display:none; 60 | } 61 | 62 | .container { 63 | width: 100%; 64 | } 65 | 66 | .wrapper { 67 | display: grid; 68 | grid-template-columns: 700px 700px; 69 | grid-template-rows: 200px 200px 200px; 70 | grid-gap: 10px; 71 | background-color: #fff; 72 | } 73 | 74 | .header { 75 | grid-column: 1/6; 76 | } 77 | 78 | .first-left { 79 | 80 | padding: 15px; 81 | grid-column: 1 / 2; 82 | grid-row: 1 / 2; 83 | /*border: 0.2px solid lightgray;*/ 84 | } 85 | 86 | .text-box { 87 | width:580px; 88 | } 89 | 90 | .second-left { 91 | padding: 15px; 92 | grid-column: 1 / 2; 93 | grid-row: 2/ 3; 94 | } 95 | 96 | .third-left { 97 | padding: 15px; 98 | grid-column: 1/ 2; 99 | grid-row: 3 / 3; 100 | } 101 | 102 | .second-right { 103 | padding: 15px; 104 | grid-column: 2 / 2; 105 | grid-row: 2/ 3; 106 | } 107 | 108 | 109 | .first-right { 110 | padding: 15px; 111 | grid-column: 2/ 2; 112 | grid-row: 1 / 6; 113 | /*border: 0.2px solid lightgray;*/ 114 | } 115 | 116 | li { 117 | list-style-type: circle; 118 | } 119 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/CustomAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using JWTSimpleServer.Abstractions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Security.Claims; 7 | using System.Threading.Tasks; 8 | 9 | namespace SimpleServerMessagePackStorage 10 | { 11 | public class CustomAuthenticationProvider : IAuthenticationProvider 12 | { 13 | public Task ValidateClientAuthentication(JwtSimpleServerContext context) 14 | { 15 | if(context.UserName == "demo" && context.Password == "demo") 16 | { 17 | var claims = new List(); 18 | claims.Add(new Claim(ClaimTypes.Name, "demo")); 19 | 20 | context.Success(claims); 21 | } 22 | else 23 | { 24 | context.Reject("Invalid user authentication"); 25 | } 26 | 27 | return Task.CompletedTask; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace SimpleServerMessagePackStorage 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/SimpleServerMessagePackStorage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetCoreTargetVersion) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JWTSimpleServer.Abstractions; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | namespace SimpleServerMessagePackStorage 12 | { 13 | public class Startup 14 | { 15 | public const string SigningKey = "MessagePackSampleSigningKey"; 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | services.AddSingleton() 19 | .AddJwtSimpleServer(setup => 20 | { 21 | setup.IssuerSigningKey = SigningKey; 22 | }) 23 | .AddJwtMessagePackRefreshTokenStore(setup => 24 | { 25 | setup.Path = "MyBinaryStore.bin"; 26 | }) 27 | .AddMvcCore(). 28 | AddAuthorization(); 29 | } 30 | 31 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 32 | { 33 | app.UseDefaultFiles(); 34 | app.UseStaticFiles(); 35 | 36 | app.UseJwtSimpleServer(setup => 37 | { 38 | setup.IssuerSigningKey = SigningKey; 39 | }); 40 | 41 | app.UseMvc(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | JWT Simple Server Playground 15 |
16 | 17 |
18 |
19 |
20 | Request token 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 | Call api/test protected controller with token: 31 | 32 | 33 |
34 | 35 |
36 |
37 |
38 | Refresh token service setup 39 |
40 |
41 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 138 | 139 | 140 | 141 | 155 | -------------------------------------------------------------------------------- /samples/SimpleServerMessagePackStorage/wwwroot/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI'; 3 | } 4 | 5 | .top-buffer-10 { 6 | margin-top: 10px; 7 | } 8 | 9 | .top-buffer-20 { 10 | margin-top: 20px; 11 | } 12 | 13 | .left { 14 | float: left; 15 | } 16 | 17 | .center { 18 | display: flex; 19 | justify-content: center; 20 | } 21 | 22 | .logo { 23 | float: left; 24 | } 25 | 26 | .logo img { 27 | width: 100px; 28 | height: 120px; 29 | } 30 | 31 | .caption { 32 | color: #2e74b0; 33 | display: block; 34 | font-weight: bold; 35 | } 36 | .block { 37 | display: block; 38 | } 39 | .success { 40 | color: green; 41 | } 42 | 43 | .commit { 44 | text-overflow: ellipsis; 45 | width: 600px; 46 | display: block; 47 | overflow: hidden; 48 | margin-top: 15px; 49 | } 50 | 51 | textarea { 52 | resize: none; 53 | } 54 | .error { 55 | color:red; 56 | } 57 | 58 | .hidden { 59 | display:none; 60 | } 61 | 62 | .container { 63 | width: 100%; 64 | } 65 | 66 | .wrapper { 67 | display: grid; 68 | grid-template-columns: 700px 700px; 69 | grid-template-rows: 200px 200px 200px; 70 | grid-gap: 10px; 71 | background-color: #fff; 72 | } 73 | 74 | .header { 75 | grid-column: 1/6; 76 | } 77 | 78 | .first-left { 79 | 80 | padding: 15px; 81 | grid-column: 1 / 2; 82 | grid-row: 1 / 2; 83 | /*border: 0.2px solid lightgray;*/ 84 | } 85 | 86 | .text-box { 87 | width:580px; 88 | } 89 | 90 | .second-left { 91 | padding: 15px; 92 | grid-column: 1 / 2; 93 | grid-row: 2/ 3; 94 | } 95 | 96 | .third-left { 97 | padding: 15px; 98 | grid-column: 1/ 2; 99 | grid-row: 3 / 3; 100 | } 101 | 102 | .second-right { 103 | padding: 15px; 104 | grid-column: 2 / 2; 105 | grid-row: 2/ 3; 106 | } 107 | 108 | 109 | .first-right { 110 | padding: 15px; 111 | grid-column: 2/ 2; 112 | grid-row: 1 / 6; 113 | /*border: 0.2px solid lightgray;*/ 114 | } 115 | 116 | li { 117 | list-style-type: circle; 118 | } 119 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/EntityFrameworkCoreRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using Microsoft.EntityFrameworkCore; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | namespace JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore 7 | { 8 | /// 9 | /// EF implementation of RefreshTokenStore 10 | /// 11 | public class EntityFrameworkCoreRefreshTokenStore : IRefreshTokenStore 12 | { 13 | private readonly JwtSimpleServerDbContext context; 14 | 15 | public EntityFrameworkCoreRefreshTokenStore(JwtSimpleServerDbContext context) 16 | { 17 | this.context = context ?? throw new ArgumentException(nameof(context)); 18 | } 19 | 20 | public async Task GetTokenAsync(string refreshToken) 21 | { 22 | var token = await context.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken); 23 | 24 | if (token != null) 25 | { 26 | return Token.Create(token.AccessToken, token.RefreshToken, token.CreatedAt); 27 | } 28 | 29 | return null; 30 | } 31 | 32 | public async Task InvalidateRefreshTokenAsync(string refreshToken) 33 | { 34 | var token = await context.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken); 35 | 36 | if (token != null) 37 | { 38 | context.Remove(token); 39 | await context.SaveChangesAsync(); 40 | } 41 | } 42 | 43 | public async Task StoreTokenAsync(Token token) 44 | { 45 | if (token == null) 46 | { 47 | throw new ArgumentException(nameof(token)); 48 | } 49 | 50 | var entity = JwtToken.CopyFrom(token); 51 | await context.AddAsync(entity); 52 | await context.SaveChangesAsync(); 53 | } 54 | 55 | public string GenerateRefreshToken() 56 | { 57 | return Guid.NewGuid().ToString().Replace("-", String.Empty); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/EntityFrameworkCoreRefreshTokenStoreSeviceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore; 3 | using System; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection 6 | { 7 | /// 8 | /// Extension methods to add EF database support to JwtSimpleServer. 9 | /// 10 | public static class EntityFrameworkCoreRefreshTokenStoreSeviceCollectionExtensions 11 | { 12 | /// 13 | /// Configures EF implementation of RefreshTokenStore with JwtSimpleServer. 14 | /// 15 | /// Service collection 16 | /// The store options action. 17 | /// 18 | public static IServiceCollection AddJwtEntityFrameworkCoreRefreshTokenStore(this IServiceCollection services, Action jwtStoreOptions) 19 | { 20 | var options = new JwtStoreOptions(); 21 | jwtStoreOptions(options); 22 | 23 | services.AddDbContext(builder => 24 | { 25 | options.ConfigureDbContext?.Invoke(builder); 26 | }); 27 | 28 | services.AddScoped(); 29 | return services; 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetStandardTargetVersion) 5 | 6 | 7 | $(PackageLicenseUrl) 8 | $(PackageProjectUrl) 9 | Entity Framework Core refresh token store for JWT Simple Server 10 | jwt-server;jwt-token;entity-framework; refresh-token;security;typescript 11 | $(Version) 12 | $(RepositoryUrl) 13 | $(Authors) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/JwtSimpleServerDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | 4 | namespace JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore 5 | { 6 | /// 7 | /// DbContext for the JwtSimpleServer tokens data. 8 | /// 9 | public class JwtSimpleServerDbContext : DbContext 10 | { 11 | public DbSet Tokens { get; set; } 12 | 13 | public JwtSimpleServerDbContext(DbContextOptions options) : base(options) 14 | { 15 | if (options == null) 16 | { 17 | throw new ArgumentException(nameof(options)); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/JwtStoreOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore 7 | { 8 | public class JwtStoreOptions 9 | { 10 | /// 11 | /// Callback to configure the EF DbContext. 12 | /// 13 | /// 14 | /// The configure database context. 15 | /// 16 | public Action ConfigureDbContext { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore/JwtToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.EntityFrameworkCoreRefreshTokenStore 6 | { 7 | public class JwtToken : Token 8 | { 9 | public int Id { get; set; } 10 | 11 | public static JwtToken CopyFrom(Token token) 12 | { 13 | return new JwtToken 14 | { 15 | AccessToken = token.AccessToken, 16 | RefreshToken = token.RefreshToken, 17 | CreatedAt = token.CreatedAt 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.InMemoryRefreshTokenStore/InMemoryRefreshTokenServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using JWTSimpleServer.InMemoryRefreshTokenStore; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | public static class InMemoryRefreshTokenServiceCollectionExtensions 11 | { 12 | public static IServiceCollection AddJwtInMemoryRefreshTokenStore(this IServiceCollection services) 13 | { 14 | services.AddMemoryCache(); 15 | services.AddSingleton(); 16 | return services; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.InMemoryRefreshTokenStore/InMemoryRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading.Tasks; 4 | using JWTSimpleServer.Abstractions; 5 | using Microsoft.Extensions.Caching.Memory; 6 | 7 | namespace JWTSimpleServer.InMemoryRefreshTokenStore 8 | { 9 | public class InMemoryRefreshTokenStore : IRefreshTokenStore 10 | { 11 | private readonly IMemoryCache _cache; 12 | private const string TOKEN_CACHE_KEY = "JWT_SERVER_TOKEN_"; 13 | 14 | public InMemoryRefreshTokenStore(IMemoryCache cache) 15 | { 16 | _cache = cache; 17 | } 18 | 19 | public Task GetTokenAsync(string refreshToken) 20 | { 21 | if (string.IsNullOrEmpty(refreshToken)) 22 | { 23 | return null; 24 | } 25 | var token = _cache.Get($"{TOKEN_CACHE_KEY}{refreshToken}"); 26 | return Task.FromResult(token); 27 | } 28 | 29 | public Task InvalidateRefreshTokenAsync(string refreshToken) 30 | { 31 | _cache.Remove($"{TOKEN_CACHE_KEY}{refreshToken}"); 32 | return Task.CompletedTask; 33 | } 34 | 35 | public Task StoreTokenAsync(Token token) 36 | { 37 | _cache.Set($"{TOKEN_CACHE_KEY}{token.RefreshToken}", token); 38 | return Task.CompletedTask; 39 | } 40 | public string GenerateRefreshToken() 41 | { 42 | return Guid.NewGuid().ToString().Replace("-", ""); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.InMemoryRefreshTokenStore/JWTSimpleServer.InMemoryRefreshTokenStore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetStandardTargetVersion) 5 | 6 | 7 | 8 | $(PackageLicenseUrl) 9 | $(PackageProjectUrl) 10 | In Memory refresh token store for JWT Simple Server 11 | jwt-server;jwt-token;in-memory; refresh-token;security;typescript 12 | $(Version) 13 | $(RepositoryUrl) 14 | $(Authors) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.MessagePackRefreshTokenStore/JWTSimpleServer.MessagePackRefreshTokenStore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(NetStandardTargetVersion) 5 | 6 | 7 | 8 | $(PackageLicenseUrl) 9 | $(PackageProjectUrl) 10 | MessagePack binary refresh token store for JWT Simple Server 11 | jwt-server;jwt-token;message-pack; refresh-token;security;typescript 12 | $(Version) 13 | $(RepositoryUrl) 14 | $(Authors) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.MessagePackRefreshTokenStore/JwtStoreOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.MessagePackRefreshTokenStore 6 | { 7 | public class JwtStoreOptions 8 | { 9 | public string Path { get; set; } = "binarystore"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.MessagePackRefreshTokenStore/JwtToken.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JWTSimpleServer.MessagePackRefreshTokenStore 7 | { 8 | [MessagePackObject] 9 | public class JwtToken 10 | { 11 | [Key(0)] 12 | public string AccessToken { get; set; } 13 | [Key(1)] 14 | public string RefreshToken { get; set; } 15 | [Key(2)] 16 | public DateTime CreatedAt { get; set; } 17 | 18 | public Token CopyTo() 19 | { 20 | return Token.Create(AccessToken, RefreshToken, CreatedAt); 21 | } 22 | 23 | public static JwtToken CopyFrom(Token token) 24 | { 25 | return new JwtToken() 26 | { 27 | AccessToken = token.AccessToken, 28 | RefreshToken = token.RefreshToken, 29 | CreatedAt = token.CreatedAt 30 | }; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.MessagePackRefreshTokenStore/MessagePackRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using MessagePack; 3 | using System; 4 | using System.Threading.Tasks; 5 | using System.IO; 6 | using System.Collections.Concurrent; 7 | using System.Threading; 8 | 9 | namespace JWTSimpleServer.MessagePackRefreshTokenStore 10 | { 11 | public class MessagePackRefreshTokenStore : IRefreshTokenStore 12 | { 13 | private readonly JwtStoreOptions _storeOptions; 14 | private readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1); 15 | private readonly SemaphoreSlim _readSemaphore = new SemaphoreSlim(1, 1); 16 | public MessagePackRefreshTokenStore(JwtStoreOptions storeOptions) 17 | { 18 | _storeOptions = storeOptions; 19 | EnsureStore(); 20 | } 21 | 22 | public async Task GetTokenAsync(string refreshToken) 23 | { 24 | var tokenStore = await ReadBinaryStoreAsync(); 25 | if (tokenStore.ContainsKey(refreshToken)) 26 | { 27 | return tokenStore[refreshToken].CopyTo(); 28 | } 29 | return null; 30 | } 31 | 32 | public async Task InvalidateRefreshTokenAsync(string refreshToken) 33 | { 34 | var tokenStore = await ReadBinaryStoreAsync(); 35 | tokenStore.TryRemove(refreshToken, out JwtToken token); 36 | await WriteBinaryStoreAsync(tokenStore); 37 | } 38 | 39 | public async Task StoreTokenAsync(Token token) 40 | { 41 | var tokenStore = await ReadBinaryStoreAsync(); 42 | tokenStore.TryAdd(token.RefreshToken, JwtToken.CopyFrom(token)); 43 | await WriteBinaryStoreAsync(tokenStore); 44 | } 45 | 46 | private async Task> ReadBinaryStoreAsync() 47 | { 48 | await _readSemaphore.WaitAsync(); 49 | try 50 | { 51 | using (var binaryStore = new FileStream(_storeOptions.Path, FileMode.Open)) 52 | { 53 | var bytes = new byte[binaryStore.Length]; 54 | await binaryStore.ReadAsync(bytes, 0, (int)bytes.Length); 55 | return MessagePackSerializer.Deserialize>(bytes); 56 | } 57 | } 58 | finally 59 | { 60 | _readSemaphore.Release(); 61 | } 62 | } 63 | 64 | private async Task WriteBinaryStoreAsync(ConcurrentDictionary store) 65 | { 66 | await _writeSemaphore.WaitAsync(); 67 | try 68 | { 69 | var content = MessagePackSerializer.Serialize(store); 70 | using(var binaryStore = new FileStream(_storeOptions.Path, FileMode.Create)) 71 | { 72 | await binaryStore.WriteAsync(content, 0, content.Length); 73 | } 74 | } 75 | finally 76 | { 77 | _writeSemaphore.Release(); 78 | } 79 | } 80 | 81 | private void EnsureStore() 82 | { 83 | if (!File.Exists(_storeOptions.Path)) 84 | { 85 | using (var fs = File.Create(_storeOptions.Path)) { }; 86 | var initialDictionary = new ConcurrentDictionary(); 87 | WriteBinaryStoreAsync(initialDictionary).GetAwaiter().GetResult(); 88 | } 89 | } 90 | 91 | public string GenerateRefreshToken() 92 | { 93 | return Guid.NewGuid().ToString().Replace("-", String.Empty); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.MessagePackRefreshTokenStore/MessagePackRefreshTokenStoreCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using JWTSimpleServer.MessagePackRefreshTokenStore; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection 8 | { 9 | public static class MessagePackRefreshTokenStoreCollectionExtensions 10 | { 11 | public static IServiceCollection AddJwtMessagePackRefreshTokenStore(this IServiceCollection services, Action setup) 12 | { 13 | var jwtStoreOptions = new JwtStoreOptions(); 14 | setup(jwtStoreOptions); 15 | services.AddSingleton 16 | (sp => new MessagePackRefreshTokenStore(jwtStoreOptions)); 17 | 18 | return services; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.RedisDistributedRefreshTokenStore/JWTSimpleServer.RedisDistributedRefreshTokenStore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | $(PackageLicenseUrl) 9 | $(PackageProjectUrl) 10 | Distributed Redis based refresh token store for JWT Simple Server 11 | jwt-server;jwt-token;redis; distribute-cache;refresh-token;security;typescript 12 | $(Version) 13 | $(RepositoryUrl) 14 | $(Authors) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.RedisDistributedRefreshTokenStore/RedisDistributedRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using JWTSimpleServer.Abstractions; 3 | using Microsoft.Extensions.Caching.Distributed; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection 11 | { 12 | public class RedisDistributedRefreshTokenStore : IRefreshTokenStore 13 | { 14 | private readonly IDistributedCache _distributedCache; 15 | private const string TOKEN_CACHE_KEY = "JWT_SERVER_TOKEN_"; 16 | 17 | public RedisDistributedRefreshTokenStore(IDistributedCache distributedCache) 18 | { 19 | _distributedCache = distributedCache; 20 | } 21 | 22 | public async Task GetTokenAsync(string refreshToken) 23 | { 24 | var token = await _distributedCache.GetStringAsync($"{TOKEN_CACHE_KEY}{refreshToken}"); 25 | return string.IsNullOrEmpty(token) ? null : JsonConvert.DeserializeObject(token); 26 | } 27 | 28 | public Task InvalidateRefreshTokenAsync(string refreshToken) 29 | { 30 | return _distributedCache.RemoveAsync($"{TOKEN_CACHE_KEY}{refreshToken}"); 31 | } 32 | 33 | public Task StoreTokenAsync(Token token) 34 | { 35 | var serializedToken = JsonConvert.SerializeObject(token); 36 | return _distributedCache.SetStringAsync($"{TOKEN_CACHE_KEY}{token.RefreshToken}", serializedToken); 37 | } 38 | public string GenerateRefreshToken() 39 | { 40 | return Guid.NewGuid().ToString().Replace("-", ""); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/JWTSimpleServer.RedisDistributedRefreshTokenStore/RedisRefreshTokenStoreServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using Microsoft.Extensions.Caching.Redis; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public static class RedisRefreshTokenStoreServiceCollectionExtensions 9 | { 10 | const string JwtSimpleServerRedisInstanceName = "JwtSimpleServerInstance"; 11 | public static IServiceCollection AddDistributedRedisRefreshStokenStore( 12 | this IServiceCollection services, 13 | Action cacheSetup 14 | ) 15 | { 16 | services.AddSingleton(); 17 | services.AddDistributedRedisCache(setup => cacheSetup(setup)); 18 | return services; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/Abstractions/IAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace JWTSimpleServer.Abstractions 8 | { 9 | public interface IAuthenticationProvider 10 | { 11 | Task ValidateClientAuthentication(JwtSimpleServerContext context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/Abstractions/IRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace JWTSimpleServer.Abstractions 7 | { 8 | public interface IRefreshTokenStore 9 | { 10 | Task GetTokenAsync(string refreshToken); 11 | Task StoreTokenAsync(Token token); 12 | Task InvalidateRefreshTokenAsync(string refreshToken); 13 | string GenerateRefreshToken(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer 6 | { 7 | public sealed class Constants 8 | { 9 | public const string DefaultIssuer = "SimpleServer"; 10 | public const string DefaultSigningKey = "SimpleServerDefaultSigningKey"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/GrantTypes/GrantType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.GrantTypes 6 | { 7 | public class GrantType 8 | { 9 | public const string Password = "password"; 10 | public const string RefreshToken = "refresh_token"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/GrantTypes/IGrantType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.GrantTypes 6 | { 7 | public interface IGrantType { } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/GrantTypes/InvalidGrantType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.GrantTypes 6 | { 7 | public class InvalidGrantType: IGrantType 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/GrantTypes/PasswordGrantType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.GrantTypes 6 | { 7 | public class PasswordGrantType: IGrantType 8 | { 9 | public string UserName { get; set; } 10 | public string Password { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/GrantTypes/RefreshTokenGrantType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer.GrantTypes 6 | { 7 | public class RefreshTokenGrantType: IGrantType 8 | { 9 | public string RefreshToken { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JWTSimpleServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetStandardTargetVersion) 5 | 6 | 7 | 8 | $(PackageLicenseUrl) 9 | $(PackageProjectUrl) 10 | A light weight, dynamic jwt server for ASP.NET Core 11 | jwt-server;jwt-token;refresh-token;security;typescript 12 | $(Version) 13 | $(RepositoryUrl) 14 | $(Authors) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtGrantTypesParser.cs: -------------------------------------------------------------------------------- 1 |  2 | using JWTSimpleServer.GrantTypes; 3 | using Microsoft.AspNetCore.Http; 4 | using System; 5 | using System.Linq; 6 | namespace JWTSimpleServer 7 | { 8 | public class JwtGrantTypesParser 9 | { 10 | private const string GrandTypeParameter = "grant_type"; 11 | 12 | public static IGrantType Parse(HttpContext context) 13 | { 14 | return ParseRequest(context.Request); 15 | } 16 | private static IGrantType ParseRequest(HttpRequest request) 17 | { 18 | var requestForm = request.Form; 19 | 20 | if (requestForm.ContainsKey(GrandTypeParameter)) 21 | { 22 | var grandTypeValue = requestForm[GrandTypeParameter].FirstOrDefault(); 23 | switch (grandTypeValue) 24 | { 25 | case GrantType.Password: 26 | return new PasswordGrantType() 27 | { 28 | UserName = requestForm["username"].FirstOrDefault(), 29 | Password = requestForm["password"].FirstOrDefault() 30 | }; 31 | 32 | case GrantType.RefreshToken: 33 | return new RefreshTokenGrantType() 34 | { 35 | RefreshToken = requestForm["refresh_token"].FirstOrDefault() 36 | }; 37 | default: 38 | return new InvalidGrantType(); 39 | 40 | } 41 | } 42 | else 43 | { 44 | return new InvalidGrantType(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtSimpleServerAppBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using Microsoft.AspNetCore.Http; 3 | using System; 4 | 5 | namespace Microsoft.AspNetCore.Builder 6 | { 7 | public static class JwtSimpleServerAppBuilderExtensions 8 | { 9 | private const string XFormUrlEncoded = "application/x-www-form-urlencoded"; 10 | public static IApplicationBuilder UseJwtSimpleServer(this IApplicationBuilder app, Action serverSetup) 11 | { 12 | var simpleServerOptions = new JwtSimpleServerOptions(); 13 | serverSetup(simpleServerOptions); 14 | 15 | if (string.IsNullOrEmpty(simpleServerOptions.IssuerSigningKey)) 16 | { 17 | throw new ArgumentNullException(nameof(JwtSimpleServerOptions.IssuerSigningKey)); 18 | } 19 | 20 | app.MapWhen( context => IsValidJwtMiddlewareRequest(context, simpleServerOptions), 21 | appBuilder => appBuilder.UseMiddleware(simpleServerOptions)); 22 | 23 | app.UseAuthentication(); 24 | return app; 25 | } 26 | private static bool IsValidJwtMiddlewareRequest(HttpContext context, JwtSimpleServerOptions options) 27 | { 28 | return context.Request.Method == HttpMethods.Post && 29 | context.Request.ContentType == XFormUrlEncoded && 30 | context.Request.Path == options.Path; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtSimpleServerContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | 6 | namespace JWTSimpleServer 7 | { 8 | public class JwtSimpleServerContext 9 | { 10 | private JwtSimpleServerContext(string userName, string password) 11 | { 12 | UserName = userName; 13 | Password = password; 14 | } 15 | 16 | public static JwtSimpleServerContext Create(string userName, string password) 17 | { 18 | return new JwtSimpleServerContext(userName, password); 19 | } 20 | 21 | public string UserName { get; } 22 | public string Password { get; } 23 | private bool _contextValidated = false; 24 | private string _errorMessage = ServerMessages.InvalidAuthentication; 25 | internal List Claims { get; private set; } = null; 26 | internal bool IsValid() => _contextValidated; 27 | 28 | internal string GetError() => _errorMessage; 29 | 30 | public void Reject(string errorMessage) 31 | { 32 | _contextValidated = false; 33 | _errorMessage = errorMessage; 34 | } 35 | public void Success(List claims) 36 | { 37 | _contextValidated = true; 38 | Claims = claims; 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtSimpleServerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using JWTSimpleServer.GrantTypes; 3 | using Microsoft.AspNetCore.Http; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.IdentityModel.Tokens.Jwt; 7 | using System.Linq; 8 | using System.Net.Mime; 9 | using System.Threading.Tasks; 10 | 11 | namespace JWTSimpleServer 12 | { 13 | public class JwtSimpleServerMiddleware 14 | { 15 | private readonly JwtSimpleServerOptions _serverOptions; 16 | private readonly RequestDelegate _next; 17 | private JwtTokenEncoder _jwtTokenEncoder; 18 | 19 | public JwtSimpleServerMiddleware( 20 | RequestDelegate next, 21 | JwtSimpleServerOptions serverOptions) 22 | { 23 | _next = next; 24 | _serverOptions = serverOptions; 25 | 26 | } 27 | public async Task InvokeAsync( 28 | HttpContext context, 29 | IAuthenticationProvider authenticationProvider, 30 | IRefreshTokenStore refreshTokenStore) 31 | { 32 | _jwtTokenEncoder = new JwtTokenEncoder(_serverOptions, refreshTokenStore); 33 | await ProcessGrantType( 34 | JwtGrantTypesParser.Parse(context), 35 | context, 36 | authenticationProvider ?? throw new Exception(ServerMessages.AuthenticationProviderNotRegistered), 37 | refreshTokenStore); 38 | } 39 | 40 | private Task ProcessGrantType( 41 | IGrantType grantType, 42 | HttpContext context, 43 | IAuthenticationProvider authenticationProvider, 44 | IRefreshTokenStore refreshTokenStore) 45 | { 46 | switch (grantType) 47 | { 48 | case PasswordGrantType password: 49 | return GenerateJwtToken(password, context, authenticationProvider); 50 | 51 | case RefreshTokenGrantType refresh: 52 | return RefreshToken(refresh, context, refreshTokenStore); 53 | 54 | default: 55 | return WriteResponseError(context, ServerMessages.InvalidGrantType); 56 | } 57 | } 58 | 59 | private async Task GenerateJwtToken( 60 | PasswordGrantType passwordGrandType, 61 | HttpContext context, 62 | IAuthenticationProvider authenticationProvider) 63 | { 64 | var simpleServerContext = JwtSimpleServerContext.Create(passwordGrandType.UserName, passwordGrandType.Password); 65 | await authenticationProvider.ValidateClientAuthentication(simpleServerContext); 66 | 67 | if (simpleServerContext.IsValid()) 68 | { 69 | var jwtToken = await _jwtTokenEncoder.WriteToken(simpleServerContext.Claims); 70 | 71 | await WriteResponseAsync(StatusCodes.Status200OK, context, JsonConvert.SerializeObject(jwtToken)); 72 | } 73 | else 74 | { 75 | await WriteAuthenticationError(context, simpleServerContext); 76 | } 77 | } 78 | 79 | public async Task RefreshToken( 80 | RefreshTokenGrantType refreshTokenGrant, 81 | HttpContext context, 82 | IRefreshTokenStore refreshTokenStore) 83 | { 84 | if (IsRefreshTokenStoreRegistered(refreshTokenStore)) 85 | { 86 | var token = await refreshTokenStore.GetTokenAsync(refreshTokenGrant.RefreshToken); 87 | if (token == null) 88 | { 89 | await WriteResponseAsync(StatusCodes.Status404NotFound, context); 90 | } 91 | else 92 | { 93 | var jwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(token.AccessToken); 94 | var jwtToken = await _jwtTokenEncoder.WriteToken(jwtSecurityToken.Claims.ToList()); 95 | await refreshTokenStore.InvalidateRefreshTokenAsync(token.RefreshToken); 96 | await WriteResponseAsync(StatusCodes.Status200OK, context, JsonConvert.SerializeObject(jwtToken)); 97 | } 98 | } 99 | else 100 | { 101 | await WriteNoContentResponse(context, ServerMessages.NoRefreshTokenStoreRegistered); 102 | } 103 | } 104 | 105 | private Task WriteResponseAsync( 106 | int statusCode, 107 | HttpContext context, 108 | string content = "", 109 | string contentType = "application/json") 110 | { 111 | context.Response.Headers["Content-Type"] = contentType; 112 | context.Response.StatusCode = statusCode; 113 | return context.Response.WriteAsync(content); 114 | } 115 | 116 | private Task WriteNoContentResponse( 117 | HttpContext context, 118 | string content) 119 | { 120 | return WriteResponseAsync( 121 | StatusCodes.Status204NoContent, 122 | context, 123 | content, 124 | MediaTypeNames.Text.Plain); 125 | 126 | } 127 | 128 | private Task WriteAuthenticationError(HttpContext context, JwtSimpleServerContext jwtSimpleServerContext) 129 | { 130 | return WriteResponseError(context, jwtSimpleServerContext.GetError()); 131 | } 132 | 133 | private Task WriteResponseError(HttpContext context, string error) 134 | { 135 | context.Response.StatusCode = StatusCodes.Status400BadRequest; 136 | return context.Response.WriteAsync(error); 137 | } 138 | 139 | private bool IsRefreshTokenStoreRegistered(IRefreshTokenStore refreshTokenStore) 140 | { 141 | return !(refreshTokenStore is NoRefreshTokenStore); 142 | } 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtSimpleServerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace JWTSimpleServer 7 | { 8 | public class JwtSimpleServerOptions 9 | { 10 | public string Path { get; set; } = "/token"; 11 | public string Issuer { get; set; } = Constants.DefaultIssuer; 12 | public string IssuerSigningKey { get; set; } 13 | public Func NotBefore = () => DateTime.UtcNow; 14 | public Func Expires = () => DateTime.UtcNow.AddMinutes(15); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtSimpleServerServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using JWTSimpleServer.Abstractions; 3 | using Microsoft.AspNetCore.Authentication.JwtBearer; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using Microsoft.IdentityModel.Tokens; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection 11 | { 12 | public static class JwtSimpleServerServiceCollectionExtensions 13 | { 14 | public static IServiceCollection AddJwtSimpleServer(this IServiceCollection services, Action setup) 15 | { 16 | var jwtTokenOptions = new JwtTokenOptions(); 17 | setup(jwtTokenOptions); 18 | 19 | if (string.IsNullOrEmpty(jwtTokenOptions.IssuerSigningKey)) 20 | { 21 | throw new ArgumentNullException(nameof(jwtTokenOptions.IssuerSigningKey)); 22 | }; 23 | 24 | var tokenValidationParameters = jwtTokenOptions.BuildTokenValidationParameters(); 25 | 26 | services.TryAddSingleton(); 27 | 28 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 29 | .AddJwtBearer(options => 30 | { 31 | options.TokenValidationParameters = tokenValidationParameters; 32 | }); 33 | 34 | return services; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtTokenEncoder.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using Microsoft.IdentityModel.Tokens; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IdentityModel.Tokens.Jwt; 6 | using System.Security.Claims; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace JWTSimpleServer 11 | { 12 | public class JwtTokenEncoder 13 | { 14 | private readonly JwtSimpleServerOptions _options; 15 | private readonly IRefreshTokenStore _refreshTokenStore; 16 | 17 | public JwtTokenEncoder( 18 | JwtSimpleServerOptions options, 19 | IRefreshTokenStore refreshTokenStore) 20 | { 21 | _options = options; 22 | _refreshTokenStore = refreshTokenStore; 23 | } 24 | public async Task WriteToken(List claims) 25 | { 26 | var expiresDate = _options.Expires(); 27 | var startingDate = _options.NotBefore(); 28 | var createdAt = DateTime.UtcNow; 29 | 30 | claims.Add(new Claim("DateCreated", createdAt.ToString())); 31 | 32 | var jwt = new JwtSecurityToken( 33 | issuer: _options.Issuer, 34 | claims: claims, 35 | notBefore: startingDate, 36 | expires: expiresDate, 37 | signingCredentials: new SigningCredentials( 38 | new SymmetricSecurityKey( 39 | Encoding.ASCII.GetBytes(_options.IssuerSigningKey)), 40 | SecurityAlgorithms.HmacSha256 41 | ) 42 | ); 43 | 44 | var encodedAccessToken = new JwtSecurityTokenHandler().WriteToken(jwt); 45 | var refreshToken = _refreshTokenStore.GenerateRefreshToken(); 46 | 47 | await _refreshTokenStore.StoreTokenAsync( 48 | Token.Create(encodedAccessToken, refreshToken, createdAt) 49 | ); 50 | 51 | return new JwtTokenResponse() 52 | { 53 | AccessToken = encodedAccessToken, 54 | ExpiresIn = GetTokenExpiral(startingDate, expiresDate), 55 | RefreshToken = refreshToken 56 | }; 57 | } 58 | private int GetTokenExpiral(DateTime startingDate, DateTime expiryDate) => 59 | Convert.ToInt32((expiryDate.ToUniversalTime() - startingDate.ToUniversalTime()).TotalSeconds); 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtTokenOptions.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.IdentityModel.Tokens; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace JWTSimpleServer 9 | { 10 | public class JwtTokenOptions 11 | { 12 | 13 | public string ValidIssuer { get; set; } = Constants.DefaultIssuer; 14 | public bool ValidateIssuer { get; set; } = true; 15 | public string IssuerSigningKey { get; set; } 16 | public bool ValidateAudience { get; set; } = false; 17 | public bool ValidateIssuerSigningKey { get; set; } = true; 18 | public bool RequireExpirationTime { get; set; } = true; 19 | public bool ValidateLifetime { get; set; } = true; 20 | public string ValidAudience { get; set; } 21 | public TimeSpan ClockSkew { get; set; } = TimeSpan.FromSeconds(300); 22 | 23 | internal TokenValidationParameters BuildTokenValidationParameters() 24 | { 25 | return new TokenValidationParameters() 26 | { 27 | ValidAudience = ValidAudience, 28 | ValidIssuer = ValidIssuer, 29 | ValidateAudience = ValidateAudience, 30 | ValidateIssuerSigningKey = ValidateIssuerSigningKey, 31 | RequireExpirationTime = RequireExpirationTime, 32 | ValidateLifetime = ValidateLifetime, 33 | ValidateIssuer = ValidateIssuer, 34 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(IssuerSigningKey)), 35 | ClockSkew = ClockSkew 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/JwtTokenResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace JWTSimpleServer 7 | { 8 | public class JwtTokenResponse 9 | { 10 | [JsonProperty("access_token")] 11 | public string AccessToken { get; set; } 12 | [JsonProperty("expires_in")] 13 | public int ExpiresIn { get; set; } 14 | [JsonProperty("refresh_token")] 15 | public string RefreshToken { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/NoRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer.Abstractions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace JWTSimpleServer 8 | { 9 | public class NoRefreshTokenStore : IRefreshTokenStore 10 | { 11 | public Task GetTokenAsync(string refreshToken) 12 | { 13 | return null; 14 | } 15 | 16 | public Task InvalidateRefreshTokenAsync(string refreshToken) 17 | { 18 | return Task.CompletedTask; 19 | } 20 | 21 | public Task StoreTokenAsync(Token token) 22 | { 23 | return Task.CompletedTask; 24 | } 25 | public string GenerateRefreshToken() 26 | { 27 | return string.Empty; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/ServerMessages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer 6 | { 7 | public sealed class ServerMessages 8 | { 9 | public const string InvalidGrantType = "Invalid grant_type"; 10 | public const string InvalidAuthentication = "Invalid Authentication"; 11 | public const string AuthenticationProviderNotRegistered = "No IAuthenticationProvider service was registered.Please register an IAuthenticationProvider implementation"; 12 | public const string NoRefreshTokenStoreRegistered = "No IRefreshTokenStore service was registered. Please register an IRefreshTokenStore implementation"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/JWTSimpleServer/Token.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace JWTSimpleServer 6 | { 7 | public class Token 8 | { 9 | public string AccessToken { get; set; } 10 | public string RefreshToken { get; set; } 11 | public DateTime CreatedAt { get; set; } 12 | 13 | public static Token Create(string accessToken, string refreshToken, DateTime createdAt) 14 | { 15 | return new Token 16 | { 17 | AccessToken = accessToken, 18 | RefreshToken = refreshToken, 19 | CreatedAt = createdAt 20 | }; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/FunctionalTests/Controllers/TestController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace FunctionalTests.Controllers 8 | { 9 | [Route("api/[controller]")] 10 | public class TestController: ControllerBase 11 | { 12 | 13 | [Authorize] 14 | public IActionResult Get() 15 | { 16 | return Ok(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/FunctionalTests/FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(NetCoreTargetVersion) 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/FunctionalTests/GrantTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Text; 5 | 6 | namespace FunctionalTests 7 | { 8 | public class GrantTypes 9 | { 10 | public static FormUrlEncodedContent AnInvalidGrantType() 11 | { 12 | var formValues = new Dictionary 13 | { 14 | { "grant_type" , "foo"} 15 | }; 16 | 17 | return new FormUrlEncodedContent(formValues); 18 | } 19 | 20 | public static FormUrlEncodedContent APasswordGrantType() 21 | { 22 | var formValues = new Dictionary 23 | { 24 | {"grant_type", "password"}, 25 | {"username", "SimpleServer" }, 26 | {"password", "SimpleServerPassword" } 27 | }; 28 | 29 | return new FormUrlEncodedContent(formValues); 30 | } 31 | 32 | public static FormUrlEncodedContent ARefreshTokenGranType(string refreshToken) 33 | { 34 | var formValues = new Dictionary 35 | { 36 | {"grant_type", "refresh_token"}, 37 | {"refresh_token", refreshToken } 38 | }; 39 | 40 | return new FormUrlEncodedContent(formValues); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/FunctionalTests/HostCollectionFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Xunit; 5 | 6 | namespace FunctionalTests 7 | { 8 | [CollectionDefinition("server")] 9 | public class HostCollectionFixture 10 | : ICollectionFixture 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/FunctionalTests/HostFixture.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.TestHost; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Security.Claims; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace FunctionalTests 14 | { 15 | public class HostFixture 16 | { 17 | public TestServer Server { get; } 18 | public HostFixture() 19 | { 20 | var builder = new WebHostBuilder() 21 | .ConfigureServices(services => 22 | { 23 | services.AddJwtSimpleServer(setup => { }); 24 | }).Configure(app => 25 | { 26 | app.UseJwtSimpleServer(serverSetup => { }); 27 | }); 28 | 29 | Server = new TestServer(builder); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/FunctionalTests/JwtSimpleServer/JwtSimpleServerMiddlewareTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using JWTSimpleServer; 3 | using Microsoft.AspNetCore.Http; 4 | using Newtonsoft.Json; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace FunctionalTests.JwtSimpleServer 11 | { 12 | 13 | public class JwtSimpleServerMiddlewareTestShould 14 | { 15 | [Fact] 16 | public async Task reject_invalid_grant_type() 17 | { 18 | var server = new TestServerBuilder() 19 | .WithSuccessAuthentication() 20 | .Build(); 21 | 22 | var response = await server.CreateClient().PostAsync("/token", GrantTypes.AnInvalidGrantType()); 23 | var content = await response.Content.ReadAsStringAsync(); 24 | 25 | content.Should().Be(ServerMessages.InvalidGrantType); 26 | response.StatusCode.Should().Be(StatusCodes.Status400BadRequest); 27 | } 28 | 29 | [Fact] 30 | public async Task return_a_valid_jwt_token_when_grant_type_password() 31 | { 32 | var server = new TestServerBuilder() 33 | .WithSuccessAuthentication() 34 | .Build(); 35 | 36 | var response = await server.CreateClient().PostAsync("/token", GrantTypes.APasswordGrantType()); 37 | response.EnsureSuccessStatusCode(); 38 | var content = await response.Content.ReadAsStringAsync(); 39 | var jwtTokenResponse = JsonConvert.DeserializeObject(content); 40 | 41 | jwtTokenResponse.Should().NotBeNull(); 42 | jwtTokenResponse.AccessToken.Should().NotBeNull(); 43 | jwtTokenResponse.ExpiresIn.Should().BeGreaterThan(0); 44 | 45 | } 46 | 47 | [Fact] 48 | public async Task allow_invoking_authorized_controller_with_a_token() 49 | { 50 | var server = new TestServerBuilder() 51 | .WithSuccessAuthentication() 52 | .Build(); 53 | 54 | var client = server.CreateClient(); 55 | 56 | var response = await client.PostAsync("/token", GrantTypes.APasswordGrantType()); 57 | response.EnsureSuccessStatusCode(); 58 | var content = await response.Content.ReadAsStringAsync(); 59 | var jwtTokenResponse = JsonConvert.DeserializeObject(content); 60 | 61 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtTokenResponse.AccessToken); 62 | response = await client.GetAsync("/api/test"); 63 | 64 | response.EnsureSuccessStatusCode(); 65 | } 66 | 67 | [Fact] 68 | public async Task allow_refresh_an_accessToken_from_refresh_token() 69 | { 70 | 71 | var server = new TestServerBuilder() 72 | .WithSuccessAuthentication() 73 | .WithInMemoryStore() 74 | .Build(); 75 | 76 | var client = server.CreateClient(); 77 | 78 | var response = await client.PostAsync("/token", GrantTypes.APasswordGrantType()); 79 | response.EnsureSuccessStatusCode(); 80 | var tokenResponse = await ReadRequestResponseToJwtTokenResponse(response); 81 | response = await client.PostAsync("/token", GrantTypes.ARefreshTokenGranType(tokenResponse.RefreshToken)); 82 | response.EnsureSuccessStatusCode(); 83 | var refreshTokenResponse = await ReadRequestResponseToJwtTokenResponse(response); 84 | 85 | refreshTokenResponse.AccessToken.Should().NotBeEmpty(); 86 | refreshTokenResponse.RefreshToken.Should().NotBeEmpty(); 87 | 88 | refreshTokenResponse.AccessToken.Should().NotBe(tokenResponse.AccessToken); 89 | refreshTokenResponse.RefreshToken.Should().NotBe(tokenResponse.RefreshToken); 90 | } 91 | 92 | private async Task ReadRequestResponseToJwtTokenResponse(HttpResponseMessage response) 93 | { 94 | var content = await response.Content.ReadAsStringAsync(); 95 | return JsonConvert.DeserializeObject(content); 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /test/FunctionalTests/TestServerBuilder.cs: -------------------------------------------------------------------------------- 1 | using JWTSimpleServer; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.TestHost; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Security.Claims; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.AspNetCore.Builder; 11 | using System.Linq; 12 | using JWTSimpleServer.Abstractions; 13 | 14 | namespace FunctionalTests 15 | { 16 | public class TestServerBuilder 17 | { 18 | private IAuthenticationProvider authProvider; 19 | private bool _useInMemoryStore = false; 20 | public TestServerBuilder WithSuccessAuthentication() 21 | { 22 | authProvider = new FakeSuccessAuthenticationProvider(); 23 | return this; 24 | } 25 | 26 | public TestServerBuilder WithInMemoryStore() 27 | { 28 | _useInMemoryStore = true; 29 | return this; 30 | } 31 | public TestServer Build() 32 | { 33 | var webhostBuilder = new WebHostBuilder() 34 | .ConfigureServices(services => 35 | { 36 | var serviceCollection = services.AddJwtSimpleServer(options => { 37 | options.IssuerSigningKey = Constants.DefaultSigningKey; 38 | }) 39 | .AddTransient(ctx => 40 | { 41 | return authProvider; 42 | }); 43 | 44 | if (_useInMemoryStore) 45 | { 46 | serviceCollection.AddJwtInMemoryRefreshTokenStore(); 47 | } 48 | 49 | serviceCollection.AddMvcCore() 50 | .AddAuthorization(); 51 | 52 | }).Configure(app => 53 | { 54 | app.UseJwtSimpleServer(setup => { 55 | setup.IssuerSigningKey = Constants.DefaultSigningKey; 56 | }) 57 | .UseMvc(); 58 | }); 59 | 60 | return new TestServer(webhostBuilder); 61 | } 62 | 63 | internal class FakeSuccessAuthenticationProvider : IAuthenticationProvider 64 | { 65 | public Task ValidateClientAuthentication(JwtSimpleServerContext context) 66 | { 67 | context.Success(Enumerable.Empty().ToList()); 68 | return Task.CompletedTask; 69 | } 70 | } 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /ts-client/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | dist/ -------------------------------------------------------------------------------- /ts-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-simpleserver-client", 3 | "version": "0.0.1", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/Xabaril/JWTSimpleServer" 7 | }, 8 | "description": "Javascript client library for Asp.Net Core Jwt Simple Server package. The client API offers easy token and refresh token retrieval and a configurable interval refresh token service with client notifications", 9 | "scripts": { 10 | "build": "webpack --config ./webpack.config.js && tsc", 11 | "test": "jest" 12 | }, 13 | "author": "Xabaril", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@types/jest": "^22.1.1", 17 | "jest": "^22.2.1", 18 | "ts-jest": "^22.0.3", 19 | "ts-loader": "^3.5.0", 20 | "typescript": "^2.7.1", 21 | "webpack": "^3.10.0" 22 | }, 23 | "main": "dist/index.js", 24 | "typings": "dist/index.d.ts", 25 | "files": [ 26 | "dist/**/*.*" 27 | ], 28 | "keywords": [ 29 | "jwt token", 30 | "jwt simple server", 31 | "refresh tokens", 32 | "jwt server", 33 | "typescript" 34 | ], 35 | "jest": { 36 | "transform": { 37 | "^.+\\.ts?$": "ts-jest" 38 | }, 39 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(js?|ts?)$", 40 | "moduleFileExtensions": [ 41 | "ts", 42 | "js", 43 | "json" 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /ts-client/readme.md: -------------------------------------------------------------------------------- 1 | ## Javascript JWT Simple Server client library 2 | 3 | Home Repository: 4 | 5 | https://github.com/Xabaril/JWTSimpleServer 6 | 7 | 8 | **NPM - Installing the library** 9 | ``` 10 | npm install jwt-simpleserver-client --save 11 | ``` 12 | 13 | 14 | 15 | The typescript library will allow you to easily interact will the token endpoint. 16 | 17 | Follow this steps to create your client if you are using the **browser** bundled library: 18 | 19 | **1. Create the client options** 20 | 21 | ```javascript 22 | var defaultServerOptions = new JwtSimpleServer.ClientOptions(); 23 | ``` 24 | 25 | Client options parameters have default values listed in this table: 26 | 27 | | Parameter | default value | 28 | |--:|---| 29 | | tokenEndpoint | "/token" | 30 | | host | window.location.origin | 31 | | httpClient | XMLHttpRequestClient | 32 | 33 | NOTE: You can implement your own **HttpClient** by implementing our HttpClient abstract class 34 | 35 | 36 | **2. Creat the client providing the options object:** 37 | 38 | ```javascript 39 | var simpleServerClient = new JwtSimpleServer.ServerClient(defaultServerOptions); 40 | ``` 41 | 42 | 3. Request an access token by executing _requestAccessToken_ method: 43 | 44 | ```javascript 45 | simpleServerClient.requestAccessToken({ userName: "demo", password: "demo" }) 46 | .then(token => { 47 | // your token object will have the access token and expiral, and if configured: the refresh token 48 | }): 49 | ``` 50 | 51 | *_Client events_ 52 | 53 | JWT client have several observables you can subscribe to: 54 | 55 | | Observable | return value | description | 56 | |--:|--: |---| 57 | | onBeforeRequestAccessToken | void | Will notify observers before starting the token request to the server | 58 | | onRequestAccessTokenSuccess | Token | Will notify observers passing the retrieved token as parameter | 59 | | onBeforeRequestRefreshToken | void | Will notify observers before starting the refresh token request to the server | 60 | | onRequestRefreshTokenSuccess | Token | Will notify observers passing the retrieved refresh token as parameter | 61 | 62 | 63 | 64 | 65 | **4. Optional: If you want the library to request new access tokens given an interval you can configure the __RefreshTokenService__** 66 | 67 | ```javascript 68 | var refreshService = new JwtSimpleServer.RefreshTokenService(simpleServerClient); 69 | 70 | let onTokenRefreshedFunction = (token) => { 71 | console.log("Refresh token service:", token); 72 | } 73 | 74 | //Start the renewal service 75 | refreshService.start({ 76 | intervalSeconds: 10, 77 | refreshToken, 78 | onRefreshTokenSuccessCallback: onTokenRefreshedFunction 79 | }); 80 | 81 | //Stop the renewal service 82 | refreshService.stop(); 83 | ``` -------------------------------------------------------------------------------- /ts-client/src/http/httpClient.ts: -------------------------------------------------------------------------------- 1 | import { HttpResponse } from "./httpResponse"; 2 | import { HttpRequest } from "./httpRequest"; 3 | import { HttpError } from "./httpError"; 4 | 5 | export abstract class HttpClient { 6 | post(url: string, options: HttpRequest): Promise { 7 | return this.send({ 8 | ...options, 9 | method: "POST", 10 | url 11 | }); 12 | } 13 | public abstract send(request: HttpRequest): Promise; 14 | } 15 | 16 | export class XMLHttpRequestClient extends HttpClient { 17 | public send(request: HttpRequest): Promise { 18 | return new Promise((resolve, reject) => { 19 | const xhr = new XMLHttpRequest(); 20 | xhr.open(request.method!, request.url!, true); 21 | 22 | xhr.setRequestHeader("X-Request-Client", "XMLHttpClient"); 23 | xhr.setRequestHeader("Content-type", request.contentType || "application/x-www-form-urlencoded"); 24 | 25 | xhr.onload = () => { 26 | if (xhr.status >= 200 && xhr.status < 300) { 27 | resolve(new HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText)); 28 | } else { 29 | reject(new HttpError(xhr.statusText, xhr.status)); 30 | } 31 | } 32 | xhr.onerror = () => { 33 | reject(new HttpError(xhr.statusText, xhr.status)); 34 | } 35 | 36 | xhr.ontimeout = () => { 37 | reject( new HttpError("Operation timeout", 500)); 38 | } 39 | xhr.send(request.content || ""); 40 | }); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /ts-client/src/http/httpError.ts: -------------------------------------------------------------------------------- 1 | export class HttpError extends Error { 2 | public statusCode: number; 3 | constructor(errorMessage: string, statusCode: number) { 4 | super(errorMessage); 5 | this.statusCode = statusCode; 6 | } 7 | } -------------------------------------------------------------------------------- /ts-client/src/http/httpRequest.ts: -------------------------------------------------------------------------------- 1 | export interface HttpRequest { 2 | method?: string; 3 | url?: string; 4 | content?: string; 5 | contentType?: string; 6 | headers?: Map; 7 | } -------------------------------------------------------------------------------- /ts-client/src/http/httpResponse.ts: -------------------------------------------------------------------------------- 1 | export class HttpResponse { 2 | constructor( 3 | public readonly statusCode: number, 4 | public readonly statusText: string, 5 | public readonly content: string) { 6 | } 7 | } -------------------------------------------------------------------------------- /ts-client/src/http/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./httpClient"; 2 | export * from "./httpError"; 3 | export * from "./httpRequest"; 4 | export * from "./httpResponse"; -------------------------------------------------------------------------------- /ts-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./serverClient"; 2 | export * from "./refreshTokenService"; -------------------------------------------------------------------------------- /ts-client/src/observable.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Creates a new observer 4 | * @param callback defines the callback to call when the observer is notified 5 | * @param scope defines the current scope used to restore the JS context 6 | */ 7 | export class Observer { 8 | constructor( 9 | public callback: (eventData: T) => void, 10 | public scope: any = null 11 | ) { } 12 | } 13 | 14 | /** 15 | * The Observable class is a simple implementation of the Observable pattern. 16 | */ 17 | export class Observable { 18 | private _observers = new Array>(); 19 | 20 | public subscribe(callback: (eventData: T) => void): Observer { 21 | if (!callback) throw Error("You should provide a callback to subscribe to an observable"); 22 | 23 | let observer = new Observer(callback); 24 | this._observers.push(observer); 25 | 26 | return observer; 27 | } 28 | 29 | public notify(eventData: T): void { 30 | 31 | if(!this.hasObservers()) return; 32 | 33 | for (let observer of this._observers) { 34 | if (observer.scope) { 35 | observer.callback.call(observer.scope, eventData); 36 | } 37 | else { 38 | observer.callback(eventData); 39 | } 40 | } 41 | } 42 | 43 | public remove(observer: Observer): boolean { 44 | if (!observer) return false; 45 | let index = this._observers.indexOf(observer); 46 | if (index !== -1) { 47 | this._observers.splice(index, 1); 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | public hasObservers(): Boolean { 54 | return this._observers.length > 0; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /ts-client/src/refreshTokenService.ts: -------------------------------------------------------------------------------- 1 | import { ServerClient, Token } from './serverClient'; 2 | import { Observer } from './observable'; 3 | 4 | export class RefreshTokenServiceOptions { 5 | constructor( 6 | public intervalSeconds: number | null = null, 7 | public refreshToken: string = "", 8 | public onRefreshTokenSuccessCallback?: (eventData: Token) => void) { } 9 | } 10 | 11 | export class RefreshTokenService { 12 | private _intervalSubscription: any; 13 | private _aborted: boolean = false; 14 | private _refreshSubscription?: Observer; 15 | 16 | constructor(private client: ServerClient) { } 17 | 18 | start(refreshTokenOptions: RefreshTokenServiceOptions) { 19 | 20 | this._aborted = false; 21 | this._ensureOptions(refreshTokenOptions); 22 | 23 | this._refreshSubscription = this.client.onRequestRefreshTokenSuccess.subscribe(token => { 24 | 25 | refreshTokenOptions.onRefreshTokenSuccessCallback && 26 | refreshTokenOptions.onRefreshTokenSuccessCallback(token); 27 | }); 28 | 29 | this._intervalSubscription = setInterval(async (): Promise => { 30 | 31 | if (this._aborted) return; 32 | 33 | let token = await this.client.refreshAccessToken({ refreshToken: refreshTokenOptions.refreshToken }) 34 | refreshTokenOptions.refreshToken = token.refresh_token; 35 | 36 | }, refreshTokenOptions.intervalSeconds! * 1000); 37 | } 38 | 39 | stop() { 40 | this._aborted = true; 41 | if (this._intervalSubscription !== 0) { 42 | clearInterval(this._intervalSubscription); 43 | this._intervalSubscription = 0; 44 | } 45 | if (this._refreshSubscription) { 46 | this.client.onRequestRefreshTokenSuccess.remove(this._refreshSubscription); 47 | this._refreshSubscription = undefined; 48 | } 49 | } 50 | 51 | private _ensureOptions(options: RefreshTokenServiceOptions): void { 52 | if (!options.onRefreshTokenSuccessCallback) { 53 | throw Error("You must provide a callback to start the RefreshTokenService"); 54 | } 55 | if (!options.intervalSeconds) { 56 | throw Error("You must provide the refresh token interval"); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /ts-client/src/serverClient.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, XMLHttpRequestClient } from "./http/httpClient"; 2 | import { HttpResponse } from "./http/httpResponse"; 3 | import { Observable } from './observable'; 4 | 5 | export interface Token { 6 | access_token: string; 7 | refresh_token: string; 8 | expires_in: string; 9 | } 10 | 11 | export interface PasswordGrandTypeCredentials { 12 | userName: string; 13 | password: string; 14 | } 15 | export interface RefreshTokenGrandTypeCredentials { 16 | refreshToken: string; 17 | } 18 | 19 | export class ClientOptions { 20 | public tokenEndpoint: string = "/token"; 21 | public host: string = window.location.origin; 22 | public httpClient?: HttpClient; 23 | } 24 | 25 | export class ServerClient { 26 | private _httpClient?: HttpClient; 27 | 28 | public onBeforeRequestAccessToken : Observable = new Observable(); 29 | public onRequestAccessTokenSuccess : Observable = new Observable(); 30 | 31 | public onBeforeRequestRefreshToken : Observable = new Observable(); 32 | public onRequestRefreshTokenSuccess : Observable = new Observable(); 33 | 34 | public constructor(private options: ClientOptions) { 35 | this._httpClient = options.httpClient || new XMLHttpRequestClient(); 36 | } 37 | public async requestAccessToken(credentials: PasswordGrandTypeCredentials): Promise { 38 | 39 | this.onBeforeRequestAccessToken.notify(undefined); 40 | let requestContent = `grant_type=password&username=${credentials.userName}&password=${credentials.password}`; 41 | 42 | let token = await this._postTokenRequest(requestContent); 43 | this.onRequestAccessTokenSuccess.notify(token); 44 | 45 | return token; 46 | } 47 | public async refreshAccessToken(credentials: RefreshTokenGrandTypeCredentials): Promise { 48 | this.onBeforeRequestRefreshToken.notify(undefined); 49 | let content = `grant_type=refresh_token&refresh_token=${credentials.refreshToken}`; 50 | 51 | let token = await this._postTokenRequest(content); 52 | this.onRequestRefreshTokenSuccess.notify(token); 53 | return token; 54 | } 55 | 56 | private async _postTokenRequest(content: string): Promise { 57 | let { host, tokenEndpoint } = this.options; 58 | let response = await this._httpClient!.post(`${host}${tokenEndpoint}`, { 59 | content 60 | }); 61 | return this._buildTokenFromResponse(response); 62 | } 63 | 64 | private _buildTokenFromResponse(response: HttpResponse): Token { 65 | return (JSON.parse(response.content)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ts-client/test/jwtSimpleServerClient.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ServerClient, 3 | ClientOptions, 4 | Token 5 | } from "../src/serverClient"; 6 | import { HttpResponse } from "../src/http"; 7 | import { Observable, Observer} from "../src/observable"; 8 | import createMockHttpClient from "./mocks/httpClientMock"; 9 | 10 | let clientOptions : ClientOptions = new ClientOptions(); 11 | let mockToken = { 12 | refreshToken: "foo", 13 | accessToken: "bar", 14 | expiresAt: "5000" 15 | }; 16 | 17 | describe("JwtSimpleServerClient should", () => { 18 | it("retrieve a token with configured http client", async () => { 19 | let mockResponse = createSuccessResponseFrom(mockToken); 20 | clientOptions.httpClient = createMockHttpClient(() => mockResponse); 21 | let simpleServerClient: ServerClient = new ServerClient(clientOptions); 22 | 23 | let token: Token = await simpleServerClient.requestAccessToken({ userName: "scott", password: "tiger" }); 24 | 25 | expect(token).toEqual(mockToken); 26 | }); 27 | 28 | it("notify observers before and after token request success", async () => { 29 | let mockResponse = createSuccessResponseFrom(mockToken); 30 | clientOptions.httpClient = createMockHttpClient(() => mockResponse); 31 | let simpleServerClient: ServerClient = new ServerClient(clientOptions); 32 | let beforeTokenRequestObserver = jest.fn(); 33 | simpleServerClient.onBeforeRequestAccessToken.subscribe(beforeTokenRequestObserver); 34 | simpleServerClient.onRequestAccessTokenSuccess.subscribe(token => { 35 | expect(token).toEqual(mockToken); 36 | }); 37 | 38 | let token = await simpleServerClient.requestAccessToken({userName: "foo", password:"bar"}); 39 | 40 | expect(beforeTokenRequestObserver).toHaveBeenCalled(); 41 | }); 42 | }); 43 | 44 | let createSuccessResponseFrom = (data: any) => { 45 | return new HttpResponse(200, "", JSON.stringify(data)); 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /ts-client/test/mocks/httpClientMock.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpResponse, HttpRequest } from "../../src/http"; 2 | 3 | class HttpClientMock extends HttpClient { 4 | public mockResponseFunction?: () => HttpResponse; 5 | public send(request: HttpRequest): Promise { 6 | return new Promise((res, rej) => { 7 | res(this.mockResponseFunction!()); 8 | }); 9 | } 10 | } 11 | export default function createMockClient(mockResponseFunction: any): HttpClient { 12 | let mockHttpClient = new HttpClientMock(); 13 | mockHttpClient.mockResponseFunction = mockResponseFunction; 14 | return mockHttpClient; 15 | } 16 | -------------------------------------------------------------------------------- /ts-client/test/observable.test.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "../src/observable"; 2 | 3 | describe("Observable should", () => { 4 | it("Not have observers when first created", () => { 5 | let observable = new Observable(); 6 | expect(observable.hasObservers()).toBe(false); 7 | }); 8 | 9 | it("should have observers when subscribed", () => { 10 | let observable = new Observable(); 11 | observable.subscribe((data: string) => { }); 12 | expect(observable.hasObservers()).toBe(true); 13 | }); 14 | 15 | it("should notify observers when published", () => { 16 | let observable = new Observable(); 17 | 18 | let fistMockObserver = jest.fn(); 19 | let secondMockObserver = jest.fn(); 20 | 21 | let date = new Date(); 22 | observable.subscribe(fistMockObserver); 23 | observable.subscribe(secondMockObserver); 24 | observable.notify(date); 25 | 26 | expect(fistMockObserver).toHaveBeenCalledWith(date); 27 | expect(secondMockObserver).toHaveBeenCalledWith(date); 28 | }); 29 | 30 | it('should remove an observer', () => { 31 | let observable = new Observable(); 32 | let observer = observable.subscribe((str: string) => {}); 33 | 34 | expect(observable.hasObservers()).toBe(true); 35 | 36 | let removed = observable.remove(observer); 37 | expect(removed).toBe(true); 38 | expect(observable.hasObservers()).toBe(false); 39 | }) 40 | 41 | }); -------------------------------------------------------------------------------- /ts-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", 5 | "module": "commonjs", 6 | "lib": [ "es2015", "dom" ], 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "strict": true 10 | }, 11 | "include" : ["./src/**/*"] 12 | } -------------------------------------------------------------------------------- /ts-client/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | devtool: "sourcemap", 5 | entry: "./index.ts", 6 | context: path.resolve(__dirname, "src"), 7 | output: { 8 | path: path.resolve(__dirname, './dist/browser'), 9 | filename: "simple-server-client.js", 10 | library : 'JwtSimpleServer', 11 | libraryTarget: 'window' 12 | }, 13 | resolve: { 14 | extensions: ['.js', '.ts'] 15 | }, 16 | module: { 17 | loaders: [ 18 | { 19 | loader: 'ts-loader', 20 | test: /\.ts$/, 21 | exclude: /node_modules/ 22 | } 23 | ] 24 | } 25 | } --------------------------------------------------------------------------------