├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .paket └── Paket.Restore.targets ├── .vscode └── settings.json ├── CodeMaid.config ├── Directory.Build.props ├── LICENSE ├── README.md ├── global.json ├── omnisharp.json ├── paket.dependencies ├── paket.lock ├── scripts └── build.ps1 ├── src ├── SuperTokens.AspNetCore │ ├── AccessToken.cs │ ├── AccessTokenSigningKey.cs │ ├── AccessTokenUtilities.cs │ ├── ApiVersionContainer.cs │ ├── DependencyInjection │ │ └── SuperTokensExtensions.cs │ ├── Events │ │ ├── MessageReceivedContext.cs │ │ └── SuperTokensEvents.cs │ ├── Handshake.cs │ ├── HandshakeContainer.cs │ ├── IApiVersionContainer.cs │ ├── IHandshakeContainer.cs │ ├── ISessionAccessor.cs │ ├── ISessionRecipe.cs │ ├── JwtUtilities.cs │ ├── SessionAccessor.cs │ ├── SessionRecipe.cs │ ├── SuperTokens.AspNetCore.csproj │ ├── SuperTokensAntiCsrfMode.cs │ ├── SuperTokensDefaults.cs │ ├── SuperTokensHandler.cs │ ├── SuperTokensOptions.cs │ ├── SuperTokensPostConfigureOptions.cs │ ├── SuperTokensSession.cs │ ├── TaskUtilities.cs │ └── paket.references └── SuperTokens.Net │ ├── Core │ ├── ApiVersionResponse.cs │ ├── CookieInfo.cs │ ├── Session.cs │ ├── ThirdPartyUser.cs │ └── User.cs │ ├── CoreApiClient.cs │ ├── CoreApiResponseException.cs │ ├── ICoreApiClient.cs │ ├── SessionRecipe │ ├── CreateSessionRequest.cs │ ├── CreateSessionResponse.cs │ ├── DeleteSessionRequest.cs │ ├── DeleteSessionResponse.cs │ ├── Handshake.cs │ ├── KeyInfo.cs │ ├── RefreshSessionRequest.cs │ ├── RefreshSessionResponse.cs │ ├── RegenerateSessionRequest.cs │ ├── RegenerateSessionResponse.cs │ ├── SessionHandlesResponse.cs │ ├── SessionResponse.cs │ ├── VerifySessionRequest.cs │ └── VerifySessionResponse.cs │ └── SuperTokens.Net.csproj ├── supertokens-dotnet.sln └── test ├── SuperTokens.AspNetCore.Tests ├── IntegrationTests.cs ├── LoginController.cs ├── Startup.cs ├── SuperTokens.AspNetCore.Tests.csproj ├── ValuesController.cs └── paket.references ├── SuperTokens.Net.Tests ├── FunctionalTests.cs ├── SuperTokens.Net.Tests.csproj └── paket.references └── SuperTokens.TestServer ├── Controllers └── SuperTokensController.cs ├── Counters.cs ├── CountersMiddleware.cs ├── Models ├── LoginRequest.cs └── SetAntiCsrfRequest.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Startup.cs ├── SuperTokens.TestServer.csproj ├── UpdateableSuperTokensOptions.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot └── index.html /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-format": { 6 | "version": "5.1.225507", 7 | "commands": [ 8 | "dotnet-format" 9 | ] 10 | }, 11 | "paket": { 12 | "version": "6.1.2", 13 | "commands": [ 14 | "paket" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | ##### .NET Framework/.NET Core Projects ##### 11 | 12 | [*.sln] 13 | charset = utf-8-bom 14 | indent_style = tab 15 | 16 | [*.{csproj,dcproj,vbproj}] 17 | charset = utf-8-bom 18 | indent_size = 2 19 | 20 | [*.{cs,csx,vb,vbx}] 21 | charset = utf-8-bom 22 | max_line_length = 120 23 | 24 | dotnet_style_qualification_for_field = false:warning 25 | dotnet_style_qualification_for_property = true:warning 26 | dotnet_style_qualification_for_method = true:warning 27 | dotnet_style_qualification_for_event = true:warning 28 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 29 | dotnet_style_predefined_type_for_member_access = true:suggestion 30 | dotnet_style_require_accessibility_modifiers = always:warning 31 | dotnet_style_readonly_field = true:warning 32 | dotnet_style_object_initializer = true:suggestion 33 | dotnet_style_collection_initializer = true:suggestion 34 | dotnet_style_explicit_tuple_names = true:suggestion 35 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 36 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 37 | dotnet_style_prefer_auto_properties = true:suggestion 38 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 39 | dotnet_style_coalesce_expression = true:suggestion 40 | dotnet_style_null_propagation = true:suggestion 41 | 42 | csharp_style_var_for_built_in_types = true:suggestion 43 | csharp_style_var_when_type_is_apparent = true:suggestion 44 | csharp_style_var_elsewhere = true:suggestion 45 | csharp_style_expression_bodied_methods = true:suggestion 46 | csharp_style_expression_bodied_constructors = true:suggestion 47 | csharp_style_expression_bodied_operators = true:suggestion 48 | csharp_style_expression_bodied_properties = true:suggestion 49 | csharp_style_expression_bodied_indexers = true:suggestion 50 | csharp_style_expression_bodied_accessors = true:suggestion 51 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 52 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 53 | csharp_style_inlined_variable_declaration = true:suggestion 54 | csharp_style_deconstructed_variable_declaration = true:suggestion 55 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 56 | csharp_style_throw_expression = true:suggestion 57 | csharp_style_conditional_delegate_call = true:suggestion 58 | 59 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 60 | csharp_prefer_simple_default_expression = true:suggestion 61 | csharp_prefer_braces = true:suggestion 62 | 63 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 64 | 65 | dotnet_sort_system_directives_first = true 66 | 67 | csharp_new_line_before_open_brace = all 68 | csharp_new_line_before_else = true 69 | csharp_new_line_before_catch = true 70 | csharp_new_line_before_finally = true 71 | csharp_new_line_before_members_in_object_initializers = true 72 | csharp_new_line_before_members_in_anonymous_types = true 73 | csharp_new_line_between_query_expression_clauses = true 74 | 75 | csharp_indent_case_contents = true 76 | csharp_indent_switch_labels = true 77 | csharp_indent_labels = flush_left 78 | 79 | csharp_space_after_cast = false 80 | csharp_space_after_keywords_in_control_flow_statements = true 81 | csharp_space_between_method_declaration_parameter_list_parentheses = false 82 | csharp_space_between_method_call_parameter_list_parentheses = false 83 | csharp_space_between_parentheses = false 84 | csharp_space_before_colon_in_inheritance_clause = true 85 | csharp_space_after_colon_in_inheritance_clause = true 86 | csharp_space_around_binary_operators = before_and_after 87 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 88 | csharp_space_between_method_call_name_and_opening_parenthesis = false 89 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 90 | 91 | csharp_preserve_single_line_statements = false 92 | csharp_preserve_single_line_blocks = true 93 | 94 | [*.cs] 95 | dotnet_diagnostic.CA1031.severity = suggestion 96 | dotnet_diagnostic.CA1054.severity = suggestion 97 | dotnet_diagnostic.CA1055.severity = suggestion 98 | dotnet_diagnostic.CA1056.severity = suggestion 99 | dotnet_diagnostic.CA1303.severity = none 100 | dotnet_diagnostic.CA2007.severity = none 101 | dotnet_diagnostic.S112.severity = suggestion 102 | 103 | [paket.references] 104 | insert_final_newline = false 105 | 106 | ##### Documents ##### 107 | 108 | [*.code-workspace] 109 | indent_size = 2 110 | 111 | [*.{css,less,sass,scss,styl}] 112 | indent_size = 2 113 | max_line_length = 120 114 | 115 | [*.{html,htm,xhtml}] 116 | indent_size = 2 117 | max_line_length = 120 118 | 119 | [*.{js,jsx,ts,tsx}] 120 | indent_size = 2 121 | max_line_length = 120 122 | 123 | [*.json] 124 | indent_size = 2 125 | max_line_length = 120 126 | 127 | [*.md] 128 | indent_size = 2 129 | 130 | [*.svg] 131 | indent_size = 2 132 | max_line_length = 120 133 | 134 | [*.{xml,xslt}] 135 | indent_size = 2 136 | max_line_length = 120 137 | 138 | [*.{yml,yaml}] 139 | indent_size = 2 140 | max_line_length = 120 141 | 142 | [.nvmrc] 143 | insert_final_newline = false 144 | 145 | ##### Shell Scripts ##### 146 | 147 | [*.{bat,cmd}] 148 | end_of_line = crlf 149 | indent_size = 2 150 | max_line_length = 120 151 | 152 | [*.ps1] 153 | end_of_line = lf 154 | max_line_length = 120 155 | 156 | [*.sh] 157 | end_of_line = lf 158 | indent_size = 2 159 | max_line_length = 120 160 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.7z binary 3 | *.ada text diff=ada 4 | *.ai binary 5 | *.bat text eol=crlf 6 | *.bib text diff=bibtex 7 | *.bmp binary 8 | *.c text diff=cpp 9 | *.class binary 10 | *.cmd text eol=crlf 11 | *.code-workspace text 12 | *.cpp text diff=cpp 13 | *.cs text diff=csharp 14 | *.csproj text 15 | *.css text diff=css 16 | *.csv text 17 | *.dcproj text 18 | *.dll binary 19 | *.doc binary diff=astextplain 20 | *.Dockerfile text 21 | *.docm binary diff=astextplain 22 | *.docx binary diff=astextplain 23 | *.dot binary diff=astextplain 24 | *.dotm binary diff=astextplain 25 | *.dotx binary diff=astextplain 26 | *.env text 27 | *.eot binary 28 | *.eps binary 29 | *.exe binary 30 | *.f text diff=fortran 31 | *.f90 text diff=fortran 32 | *.for text diff=fortran 33 | *.gif binary 34 | *.go text diff=golang 35 | *.gz binary 36 | *.h text diff=cpp 37 | *.htm text diff=html 38 | *.html text diff=html 39 | *.ico binary 40 | *.ini text 41 | *.jar binary 42 | *.java text diff=java 43 | *.jpeg binary 44 | *.jpg binary 45 | *.js text 46 | *.json text 47 | *.jsp text 48 | *.jsx text 49 | *.less text 50 | *.m text diff=matlab 51 | *.m4a binary 52 | *.m4v binary 53 | *.md text 54 | *.mid binary 55 | *.midi binary 56 | *.mov binary 57 | *.mp3 binary 58 | *.mp4 binary 59 | *.mpeg binary 60 | *.mpg binary 61 | *.o binary 62 | *.odf binary diff=astextplain 63 | *.odp binary 64 | *.ods binary diff=astextplain 65 | *.odt binary diff=astextplain 66 | *.ogg binary 67 | *.otf binary 68 | *.pas text diff=pascal 69 | *.pbxproj binary 70 | *.pdb binary 71 | *.pdf binary diff=astextplain 72 | *.php text diff=php 73 | *.pl text diff=perl 74 | *.pm text diff=perl 75 | *.png binary 76 | *.pot binary 77 | *.potx binary 78 | *.pp text diff=pascal 79 | *.ppt binary 80 | *.pptx binary 81 | *.properties text 82 | *.props text 83 | *.proto text 84 | *.ps1 text eol=lf 85 | *.psd binary 86 | *.pxd text 87 | *.py text diff=python 88 | *.py3 text diff=python 89 | *.pyc binary 90 | *.pyd binary 91 | *.pyo binary 92 | *.pyw text diff=python 93 | *.pywz binary 94 | *.pyx text 95 | *.pyz binary 96 | *.rake text diff=ruby 97 | *.rar binary 98 | *.rb text diff=ruby 99 | *.rtf text diff=astextplain 100 | *.ruleset text 101 | *.sass text 102 | *.scss text 103 | *.sh text eol=lf 104 | *.sln text 105 | *.so binary 106 | *.sql text 107 | *.styl text 108 | *.svg text 109 | *.svgz binary 110 | *.tar binary 111 | *.targets text 112 | *.tex text diff=tex 113 | *.tif binary 114 | *.tiff binary 115 | *.ts text 116 | *.tsv text 117 | *.tsx text 118 | *.ttf binary 119 | *.txt text 120 | *.vbproj text 121 | *.war binary 122 | *.wav binary 123 | *.wbmp binary 124 | *.webm binary 125 | *.webp binary 126 | *.woff binary 127 | *.woff2 binary 128 | *.xhtml text diff=html 129 | *.xls binary 130 | *.xlsx binary 131 | *.xlt binary 132 | *.xltx binary 133 | *.xml text 134 | *.xslt text 135 | *.xz binary 136 | *.yaml text 137 | *.yml text 138 | *.zip binary 139 | .babelrc text 140 | .dockerignore text 141 | .editorconfig text 142 | .eslintrc text 143 | .gitattributes text 144 | .gitconfig text 145 | .gitignore text 146 | .helmignore text 147 | .htaccess text 148 | .nvmrc text eol=lf 149 | .prettierignore text 150 | .prettierrc text 151 | .yarnclean text 152 | .yarnrc text 153 | ACKNOWLEDGMENTS* text 154 | AUTHORS* text 155 | CHANGELOG* text 156 | CHANGES* text 157 | CODE_OF_CONDUCT* text 158 | CodeMaid.config text 159 | CODEOWNERS* text 160 | CONTRIBUTING* text 161 | CONTRIBUTORS* text 162 | COPYING* text 163 | COPYRIGHT* text 164 | Dockerfile* text 165 | HISTORY* text 166 | INSTALL* text 167 | ISSUE_TEMPLATE* text 168 | LICENSE* text 169 | Makefile text 170 | makefile text 171 | NEWS* text 172 | paket.dependencies text 173 | paket.references text 174 | PULL_REQUEST_TEMPLATE* text 175 | README* text 176 | RELEASES* text 177 | SUPPORT* text 178 | TODO* text 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Linux.gitignore 2 | ## https://github.com/github/gitignore 3 | 4 | *~ 5 | 6 | # temporary files which can be created if a process still has a handle open of a deleted file 7 | .fuse_hidden* 8 | 9 | # KDE directory preferences 10 | .directory 11 | 12 | # Linux trash folder which might appear on any partition or disk 13 | .Trash-* 14 | 15 | # .nfs files are created when an open file is removed but is still being accessed 16 | .nfs* 17 | 18 | ## macOS.gitignore 19 | ## https://github.com/github/gitignore 20 | 21 | # General 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ## Windows.gitignore 49 | ## https://github.com/github/gitignore 50 | 51 | # Windows thumbnail cache files 52 | Thumbs.db 53 | Thumbs.db:encryptable 54 | ehthumbs.db 55 | ehthumbs_vista.db 56 | 57 | # Dump file 58 | *.stackdump 59 | 60 | # Folder config file 61 | [Dd]esktop.ini 62 | 63 | # Recycle Bin used on file shares 64 | $RECYCLE.BIN/ 65 | 66 | # Windows Installer files 67 | *.cab 68 | *.msi 69 | *.msix 70 | *.msm 71 | *.msp 72 | 73 | # Windows shortcuts 74 | *.lnk 75 | 76 | ## VisualStudio.gitignore 77 | ## https://github.com/github/gitignore 78 | 79 | ## Ignore Visual Studio temporary files, build results, and 80 | ## files generated by popular Visual Studio add-ons. 81 | ## 82 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 83 | 84 | # User-specific files 85 | *.rsuser 86 | *.suo 87 | *.user 88 | *.userosscache 89 | *.sln.docstates 90 | 91 | # User-specific files (MonoDevelop/Xamarin Studio) 92 | *.userprefs 93 | 94 | # Mono auto generated files 95 | mono_crash.* 96 | 97 | # Build results 98 | [Dd]ebug/ 99 | [Dd]ebugPublic/ 100 | [Rr]elease/ 101 | [Rr]eleases/ 102 | x64/ 103 | x86/ 104 | [Ww][Ii][Nn]32/ 105 | [Aa][Rr][Mm]/ 106 | [Aa][Rr][Mm]64/ 107 | bld/ 108 | [Bb]in/ 109 | [Oo]bj/ 110 | [Ll]og/ 111 | [Ll]ogs/ 112 | 113 | # Visual Studio 2015/2017 cache/options directory 114 | .vs/ 115 | # Uncomment if you have tasks that create the project's static files in wwwroot 116 | #wwwroot/ 117 | 118 | # Visual Studio 2017 auto generated files 119 | Generated\ Files/ 120 | 121 | # MSTest test Results 122 | [Tt]est[Rr]esult*/ 123 | [Bb]uild[Ll]og.* 124 | 125 | # NUnit 126 | *.VisualState.xml 127 | TestResult.xml 128 | nunit-*.xml 129 | 130 | # Build Results of an ATL Project 131 | [Dd]ebugPS/ 132 | [Rr]eleasePS/ 133 | dlldata.c 134 | 135 | # Benchmark Results 136 | BenchmarkDotNet.Artifacts/ 137 | 138 | # .NET Core 139 | project.lock.json 140 | project.fragment.lock.json 141 | artifacts/ 142 | 143 | # ASP.NET Scaffolding 144 | ScaffoldingReadMe.txt 145 | 146 | # StyleCop 147 | StyleCopReport.xml 148 | 149 | # Files built by Visual Studio 150 | *_i.c 151 | *_p.c 152 | *_h.h 153 | *.ilk 154 | *.meta 155 | *.obj 156 | *.iobj 157 | *.pch 158 | *.pdb 159 | *.ipdb 160 | *.pgc 161 | *.pgd 162 | *.rsp 163 | *.sbr 164 | *.tlb 165 | *.tli 166 | *.tlh 167 | *.tmp 168 | *.tmp_proj 169 | *_wpftmp.csproj 170 | *.log 171 | *.tlog 172 | *.vspscc 173 | *.vssscc 174 | .builds 175 | *.pidb 176 | *.svclog 177 | *.scc 178 | 179 | # Chutzpah Test files 180 | _Chutzpah* 181 | 182 | # Visual C++ cache files 183 | ipch/ 184 | *.aps 185 | *.ncb 186 | *.opendb 187 | *.opensdf 188 | *.sdf 189 | *.cachefile 190 | *.VC.db 191 | *.VC.VC.opendb 192 | 193 | # Visual Studio profiler 194 | *.psess 195 | *.vsp 196 | *.vspx 197 | *.sap 198 | 199 | # Visual Studio Trace Files 200 | *.e2e 201 | 202 | # TFS 2012 Local Workspace 203 | $tf/ 204 | 205 | # Guidance Automation Toolkit 206 | *.gpState 207 | 208 | # ReSharper is a .NET coding add-in 209 | _ReSharper*/ 210 | *.[Rr]e[Ss]harper 211 | *.DotSettings.user 212 | 213 | # TeamCity is a build add-in 214 | _TeamCity* 215 | 216 | # DotCover is a Code Coverage Tool 217 | *.dotCover 218 | 219 | # AxoCover is a Code Coverage Tool 220 | .axoCover/* 221 | !.axoCover/settings.json 222 | 223 | # Coverlet is a free, cross platform Code Coverage Tool 224 | coverage*.json 225 | coverage*.xml 226 | coverage*.info 227 | 228 | # Visual Studio code coverage results 229 | *.coverage 230 | *.coveragexml 231 | 232 | # NCrunch 233 | _NCrunch_* 234 | .*crunch*.local.xml 235 | nCrunchTemp_* 236 | 237 | # MightyMoose 238 | *.mm.* 239 | AutoTest.Net/ 240 | 241 | # Web workbench (sass) 242 | .sass-cache/ 243 | 244 | # Installshield output folder 245 | [Ee]xpress/ 246 | 247 | # DocProject is a documentation generator add-in 248 | DocProject/buildhelp/ 249 | DocProject/Help/*.HxT 250 | DocProject/Help/*.HxC 251 | DocProject/Help/*.hhc 252 | DocProject/Help/*.hhk 253 | DocProject/Help/*.hhp 254 | DocProject/Help/Html2 255 | DocProject/Help/html 256 | 257 | # Click-Once directory 258 | publish/ 259 | 260 | # Publish Web Output 261 | *.[Pp]ublish.xml 262 | *.azurePubxml 263 | # Note: Comment the next line if you want to checkin your web deploy settings, 264 | # but database connection strings (with potential passwords) will be unencrypted 265 | *.pubxml 266 | *.publishproj 267 | 268 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 269 | # checkin your Azure Web App publish settings, but sensitive information contained 270 | # in these scripts will be unencrypted 271 | PublishScripts/ 272 | 273 | # NuGet Packages 274 | *.nupkg 275 | # NuGet Symbol Packages 276 | *.snupkg 277 | # The packages folder can be ignored because of Package Restore 278 | **/[Pp]ackages/* 279 | # except build/, which is used as an MSBuild target. 280 | !**/[Pp]ackages/build/ 281 | # Uncomment if necessary however generally it will be regenerated when needed 282 | #!**/[Pp]ackages/repositories.config 283 | # NuGet v3's project.json files produces more ignorable files 284 | *.nuget.props 285 | *.nuget.targets 286 | 287 | # Nuget personal access tokens and Credentials 288 | nuget.config 289 | 290 | # Microsoft Azure Build Output 291 | csx/ 292 | *.build.csdef 293 | 294 | # Microsoft Azure Emulator 295 | ecf/ 296 | rcf/ 297 | 298 | # Windows Store app package directories and files 299 | AppPackages/ 300 | BundleArtifacts/ 301 | Package.StoreAssociation.xml 302 | _pkginfo.txt 303 | *.appx 304 | *.appxbundle 305 | *.appxupload 306 | 307 | # Visual Studio cache files 308 | # files ending in .cache can be ignored 309 | *.[Cc]ache 310 | # but keep track of directories ending in .cache 311 | !?*.[Cc]ache/ 312 | 313 | # Others 314 | ClientBin/ 315 | ~$* 316 | *~ 317 | *.dbmdl 318 | *.dbproj.schemaview 319 | *.jfm 320 | *.pfx 321 | *.publishsettings 322 | orleans.codegen.cs 323 | 324 | # Including strong name files can present a security risk 325 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 326 | #*.snk 327 | 328 | # Since there are multiple workflows, uncomment next line to ignore bower_components 329 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 330 | #bower_components/ 331 | 332 | # RIA/Silverlight projects 333 | Generated_Code/ 334 | 335 | # Backup & report files from converting an old project file 336 | # to a newer Visual Studio version. Backup files are not needed, 337 | # because we have git ;-) 338 | _UpgradeReport_Files/ 339 | Backup*/ 340 | UpgradeLog*.XML 341 | UpgradeLog*.htm 342 | ServiceFabricBackup/ 343 | *.rptproj.bak 344 | 345 | # SQL Server files 346 | *.mdf 347 | *.ldf 348 | *.ndf 349 | 350 | # Business Intelligence projects 351 | *.rdl.data 352 | *.bim.layout 353 | *.bim_*.settings 354 | *.rptproj.rsuser 355 | *- [Bb]ackup.rdl 356 | *- [Bb]ackup ([0-9]).rdl 357 | *- [Bb]ackup ([0-9][0-9]).rdl 358 | 359 | # Microsoft Fakes 360 | FakesAssemblies/ 361 | 362 | # GhostDoc plugin setting file 363 | *.GhostDoc.xml 364 | 365 | # Node.js Tools for Visual Studio 366 | .ntvs_analysis.dat 367 | node_modules/ 368 | 369 | # Visual Studio 6 build log 370 | *.plg 371 | 372 | # Visual Studio 6 workspace options file 373 | *.opt 374 | 375 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 376 | *.vbw 377 | 378 | # Visual Studio LightSwitch build output 379 | **/*.HTMLClient/GeneratedArtifacts 380 | **/*.DesktopClient/GeneratedArtifacts 381 | **/*.DesktopClient/ModelManifest.xml 382 | **/*.Server/GeneratedArtifacts 383 | **/*.Server/ModelManifest.xml 384 | _Pvt_Extensions 385 | 386 | # Paket dependency manager 387 | .paket/paket.exe 388 | paket-files/ 389 | 390 | # FAKE - F# Make 391 | .fake/ 392 | 393 | # CodeRush personal settings 394 | .cr/personal 395 | 396 | # Python Tools for Visual Studio (PTVS) 397 | __pycache__/ 398 | *.pyc 399 | 400 | # Cake - Uncomment if you are using it 401 | # tools/** 402 | # !tools/packages.config 403 | 404 | # Tabs Studio 405 | *.tss 406 | 407 | # Telerik's JustMock configuration file 408 | *.jmconfig 409 | 410 | # BizTalk build output 411 | *.btp.cs 412 | *.btm.cs 413 | *.odx.cs 414 | *.xsd.cs 415 | 416 | # OpenCover UI analysis results 417 | OpenCover/ 418 | 419 | # Azure Stream Analytics local run output 420 | ASALocalRun/ 421 | 422 | # MSBuild Binary and Structured Log 423 | *.binlog 424 | 425 | # NVidia Nsight GPU debugger configuration file 426 | *.nvuser 427 | 428 | # MFractors (Xamarin productivity tool) working folder 429 | .mfractor/ 430 | 431 | # Local History for Visual Studio 432 | .localhistory/ 433 | 434 | # BeatPulse healthcheck temp database 435 | healthchecksdb 436 | 437 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 438 | MigrationBackup/ 439 | 440 | # Ionide (cross platform F# VS Code tools) working folder 441 | .ionide/ 442 | 443 | # Fody - auto-generated XML schema 444 | FodyWeavers.xsd 445 | 446 | # VS Code files for those working on multiple tools 447 | .vscode/* 448 | !.vscode/settings.json 449 | !.vscode/tasks.json 450 | !.vscode/launch.json 451 | !.vscode/extensions.json 452 | *.code-workspace 453 | 454 | # Local History for Visual Studio Code 455 | .history/ 456 | 457 | # Windows Installer files from build outputs 458 | *.cab 459 | *.msi 460 | *.msix 461 | *.msm 462 | *.msp 463 | 464 | # JetBrains Rider 465 | .idea/ 466 | *.sln.iml 467 | 468 | ## VisualStudioCode.gitignore 469 | ## https://github.com/github/gitignore 470 | 471 | .vscode/* 472 | !.vscode/settings.json 473 | !.vscode/tasks.json 474 | !.vscode/launch.json 475 | !.vscode/extensions.json 476 | *.code-workspace 477 | 478 | # Local History for Visual Studio Code 479 | .history/ 480 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet-test-explorer.runInParallel": true, 3 | "dotnet-test-explorer.testProjectPath": "/test/*.Tests", 4 | "editor.formatOnSave": true, 5 | "editor.minimap.maxColumn": 120, 6 | "editor.minimap.showSlider": "always", 7 | "editor.renderWhitespace": "all", 8 | "editor.rulers": [ 9 | 120 10 | ], 11 | "files.autoSave": "afterDelay", 12 | "files.autoSaveDelay": 30000, 13 | "files.exclude": { 14 | "**/bin": true, 15 | "**/obj": true 16 | }, 17 | "omnisharp.defaultLaunchSolution": "supertokens-dotnet.sln", 18 | "omnisharp.enableEditorConfigSupport": true, 19 | "omnisharp.enableRoslynAnalyzers": true, 20 | "omnisharp.useEditorFormattingSettings": false 21 | } 22 | -------------------------------------------------------------------------------- /CodeMaid.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | True 12 | 13 | 14 | True 15 | 16 | 17 | True 18 | 19 | 20 | True 21 | 22 | 23 | True 24 | 25 | 26 | False 27 | 28 | 29 | 2 30 | 31 | 32 | True 33 | 34 | 35 | True 36 | 37 | 38 | False 39 | 40 | 41 | 120 42 | 43 | 44 | False 45 | 46 | 47 | True 48 | 49 | 50 | False 51 | 52 | 53 | False 54 | 55 | 56 | False 57 | 58 | 59 | False 60 | 61 | 62 | 4 63 | 64 | 65 | True 66 | 67 | 68 | True 69 | 70 | 71 | True 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {9A19103F-16F7-4668-BE54-9A1E7A4F7556} 6 | 7 | 8 | 9 | 10 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 | Copyright 2021 Salad Technologies, Inc. (https://salad.com) 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SuperTokens for ASP.NET Core 2 | 3 | **NOTE**: _This repository is no longer supported or updated by Salad Technologies. If you wish to continue to develop this code yourself, we recommend you fork it._ 4 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "allowPrerelease": true, 4 | "rollForward": "latestFeature", 5 | "version": "5.0.400" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /omnisharp.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/omnisharp", 3 | "FormattingOptions": { 4 | "enableEditorConfigSupport": true 5 | }, 6 | "RoslynExtensionsOptions": { 7 | "enableAnalyzersSupport": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | framework: net5.0 2 | storage: none 3 | 4 | source https://api.nuget.org/v3/index.json 5 | 6 | nuget coverlet.collector copy_local: true 7 | nuget Microsoft.AspNetCore.Mvc.Testing 8 | nuget Microsoft.CodeAnalysis.NetAnalyzers copy_local: true 9 | nuget Microsoft.NET.Test.Sdk copy_local: true 10 | nuget Microsoft.SourceLink.GitHub copy_local: true 11 | nuget Newtonsoft.Json 12 | nuget SonarAnalyzer.CSharp copy_local: true 13 | nuget xunit 14 | nuget xunit.runner.visualstudio copy_local: true 15 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | STORAGE: NONE 2 | RESTRICTION: == net5.0 3 | NUGET 4 | remote: https://api.nuget.org/v3/index.json 5 | coverlet.collector (3.1) - copy_local: true 6 | Microsoft.AspNetCore.Mvc.Testing (5.0.10) 7 | Microsoft.AspNetCore.TestHost (>= 5.0.10) 8 | Microsoft.Extensions.DependencyModel (>= 5.0) 9 | Microsoft.Extensions.Hosting (>= 5.0) 10 | Microsoft.AspNetCore.TestHost (5.0.10) 11 | System.IO.Pipelines (>= 5.0.1) 12 | Microsoft.Build.Tasks.Git (1.0) - copy_local: true 13 | Microsoft.CodeAnalysis.NetAnalyzers (5.0.3) - copy_local: true 14 | Microsoft.CodeCoverage (16.11) - copy_local: true 15 | Microsoft.Extensions.Configuration (5.0) 16 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 17 | Microsoft.Extensions.Primitives (>= 5.0) 18 | Microsoft.Extensions.Configuration.Abstractions (5.0) 19 | Microsoft.Extensions.Primitives (>= 5.0) 20 | Microsoft.Extensions.Configuration.Binder (5.0) 21 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 22 | Microsoft.Extensions.Configuration.CommandLine (5.0) 23 | Microsoft.Extensions.Configuration (>= 5.0) 24 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 25 | Microsoft.Extensions.Configuration.EnvironmentVariables (5.0) 26 | Microsoft.Extensions.Configuration (>= 5.0) 27 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 28 | Microsoft.Extensions.Configuration.FileExtensions (5.0) 29 | Microsoft.Extensions.Configuration (>= 5.0) 30 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 31 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 32 | Microsoft.Extensions.FileProviders.Physical (>= 5.0) 33 | Microsoft.Extensions.Primitives (>= 5.0) 34 | Microsoft.Extensions.Configuration.Json (5.0) 35 | Microsoft.Extensions.Configuration (>= 5.0) 36 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 37 | Microsoft.Extensions.Configuration.FileExtensions (>= 5.0) 38 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 39 | Microsoft.Extensions.Configuration.UserSecrets (5.0) 40 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 41 | Microsoft.Extensions.Configuration.Json (>= 5.0) 42 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 43 | Microsoft.Extensions.FileProviders.Physical (>= 5.0) 44 | Microsoft.Extensions.DependencyInjection (5.0.2) 45 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 46 | Microsoft.Extensions.DependencyInjection.Abstractions (5.0) 47 | Microsoft.Extensions.DependencyModel (5.0) 48 | Microsoft.Extensions.FileProviders.Abstractions (5.0) 49 | Microsoft.Extensions.Primitives (>= 5.0) 50 | Microsoft.Extensions.FileProviders.Physical (5.0) 51 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 52 | Microsoft.Extensions.FileSystemGlobbing (>= 5.0) 53 | Microsoft.Extensions.Primitives (>= 5.0) 54 | Microsoft.Extensions.FileSystemGlobbing (5.0) 55 | Microsoft.Extensions.Hosting (5.0) 56 | Microsoft.Extensions.Configuration (>= 5.0) 57 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 58 | Microsoft.Extensions.Configuration.Binder (>= 5.0) 59 | Microsoft.Extensions.Configuration.CommandLine (>= 5.0) 60 | Microsoft.Extensions.Configuration.EnvironmentVariables (>= 5.0) 61 | Microsoft.Extensions.Configuration.FileExtensions (>= 5.0) 62 | Microsoft.Extensions.Configuration.Json (>= 5.0) 63 | Microsoft.Extensions.Configuration.UserSecrets (>= 5.0) 64 | Microsoft.Extensions.DependencyInjection (>= 5.0) 65 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 66 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 67 | Microsoft.Extensions.FileProviders.Physical (>= 5.0) 68 | Microsoft.Extensions.Hosting.Abstractions (>= 5.0) 69 | Microsoft.Extensions.Logging (>= 5.0) 70 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 71 | Microsoft.Extensions.Logging.Configuration (>= 5.0) 72 | Microsoft.Extensions.Logging.Console (>= 5.0) 73 | Microsoft.Extensions.Logging.Debug (>= 5.0) 74 | Microsoft.Extensions.Logging.EventLog (>= 5.0) 75 | Microsoft.Extensions.Logging.EventSource (>= 5.0) 76 | Microsoft.Extensions.Options (>= 5.0) 77 | Microsoft.Extensions.Hosting.Abstractions (5.0) 78 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 79 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 80 | Microsoft.Extensions.FileProviders.Abstractions (>= 5.0) 81 | Microsoft.Extensions.Logging (5.0) 82 | Microsoft.Extensions.DependencyInjection (>= 5.0) 83 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 84 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 85 | Microsoft.Extensions.Options (>= 5.0) 86 | Microsoft.Extensions.Logging.Abstractions (5.0) 87 | Microsoft.Extensions.Logging.Configuration (5.0) 88 | Microsoft.Extensions.Configuration (>= 5.0) 89 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 90 | Microsoft.Extensions.Configuration.Binder (>= 5.0) 91 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 92 | Microsoft.Extensions.Logging (>= 5.0) 93 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 94 | Microsoft.Extensions.Options (>= 5.0) 95 | Microsoft.Extensions.Options.ConfigurationExtensions (>= 5.0) 96 | Microsoft.Extensions.Logging.Console (5.0) 97 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 98 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 99 | Microsoft.Extensions.Logging (>= 5.0) 100 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 101 | Microsoft.Extensions.Logging.Configuration (>= 5.0) 102 | Microsoft.Extensions.Options (>= 5.0) 103 | Microsoft.Extensions.Options.ConfigurationExtensions (>= 5.0) 104 | Microsoft.Extensions.Logging.Debug (5.0) 105 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 106 | Microsoft.Extensions.Logging (>= 5.0) 107 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 108 | Microsoft.Extensions.Logging.EventLog (5.0) 109 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 110 | Microsoft.Extensions.Logging (>= 5.0) 111 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 112 | Microsoft.Extensions.Options (>= 5.0) 113 | System.Diagnostics.EventLog (>= 5.0) 114 | Microsoft.Extensions.Logging.EventSource (5.0.1) 115 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 116 | Microsoft.Extensions.Logging (>= 5.0) 117 | Microsoft.Extensions.Logging.Abstractions (>= 5.0) 118 | Microsoft.Extensions.Options (>= 5.0) 119 | Microsoft.Extensions.Primitives (>= 5.0.1) 120 | Microsoft.Extensions.Options (5.0) 121 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 122 | Microsoft.Extensions.Primitives (>= 5.0) 123 | Microsoft.Extensions.Options.ConfigurationExtensions (5.0) 124 | Microsoft.Extensions.Configuration.Abstractions (>= 5.0) 125 | Microsoft.Extensions.Configuration.Binder (>= 5.0) 126 | Microsoft.Extensions.DependencyInjection.Abstractions (>= 5.0) 127 | Microsoft.Extensions.Options (>= 5.0) 128 | Microsoft.Extensions.Primitives (>= 5.0) 129 | Microsoft.Extensions.Primitives (5.0.1) 130 | Microsoft.NET.Test.Sdk (16.11) - copy_local: true 131 | Microsoft.CodeCoverage (>= 16.11) 132 | Microsoft.TestPlatform.TestHost (>= 16.11) 133 | Microsoft.NETCore.Platforms (5.0.3) 134 | Microsoft.SourceLink.Common (1.0) - copy_local: true 135 | Microsoft.SourceLink.GitHub (1.0) - copy_local: true 136 | Microsoft.Build.Tasks.Git (>= 1.0) 137 | Microsoft.SourceLink.Common (>= 1.0) 138 | Microsoft.TestPlatform.ObjectModel (16.11) - copy_local: true 139 | NuGet.Frameworks (>= 5.0) 140 | System.Reflection.Metadata (>= 1.6) 141 | Microsoft.TestPlatform.TestHost (16.11) - copy_local: true 142 | Microsoft.TestPlatform.ObjectModel (>= 16.11) 143 | Newtonsoft.Json (>= 9.0.1) 144 | Microsoft.Win32.Registry (5.0) 145 | System.Security.AccessControl (>= 5.0) 146 | System.Security.Principal.Windows (>= 5.0) 147 | NETStandard.Library (2.0.3) 148 | Microsoft.NETCore.Platforms (>= 1.1) 149 | Newtonsoft.Json (13.0.1) 150 | NuGet.Frameworks (5.11) - copy_local: true 151 | SonarAnalyzer.CSharp (8.29.0.36737) - copy_local: true 152 | System.Diagnostics.EventLog (5.0.1) 153 | Microsoft.NETCore.Platforms (>= 5.0.1) 154 | Microsoft.Win32.Registry (>= 5.0) 155 | System.Security.Principal.Windows (>= 5.0) 156 | System.IO.Pipelines (5.0.1) 157 | System.Reflection.Metadata (5.0) - copy_local: true 158 | System.Security.AccessControl (5.0) 159 | Microsoft.NETCore.Platforms (>= 5.0) 160 | System.Security.Principal.Windows (>= 5.0) 161 | System.Security.Principal.Windows (5.0) 162 | xunit (2.4.1) 163 | xunit.analyzers (>= 0.10) 164 | xunit.assert (2.4.1) 165 | xunit.core (2.4.1) 166 | xunit.abstractions (2.0.3) 167 | xunit.analyzers (0.10) 168 | xunit.assert (2.4.1) 169 | NETStandard.Library (>= 1.6.1) 170 | xunit.core (2.4.1) 171 | xunit.extensibility.core (2.4.1) 172 | xunit.extensibility.execution (2.4.1) 173 | xunit.extensibility.core (2.4.1) 174 | NETStandard.Library (>= 1.6.1) 175 | xunit.abstractions (>= 2.0.3) 176 | xunit.extensibility.execution (2.4.1) 177 | NETStandard.Library (>= 1.6.1) 178 | xunit.extensibility.core (2.4.1) 179 | xunit.runner.visualstudio (2.4.3) - copy_local: true 180 | -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | [CmdletBinding()] 3 | param() 4 | 5 | Set-StrictMode -Version Latest 6 | $ErrorActionPreference = "Stop" 7 | 8 | # Build Docker images 9 | $ProjectRoot = Split-Path -Path $PSScriptRoot -Parent 10 | Push-Location -Path $ProjectRoot 11 | try { 12 | & dotnet tool restore 13 | & dotnet paket restore 14 | & dotnet restore .\supertokens-dotnet.sln 15 | & dotnet build --configuration Release --no-dependencies --no-restore .\src\SuperTokens.Net\SuperTokens.Net.csproj 16 | & dotnet build --configuration Release --no-dependencies --no-restore .\src\SuperTokens.AspNetCore\SuperTokens.AspNetCore.csproj 17 | & dotnet pack --configuration Release --no-build --no-dependencies --no-restore --output "$(Join-Path -Path $ProjectRoot -ChildPath artifacts)" .\src\SuperTokens.Net\SuperTokens.Net.csproj 18 | & dotnet pack --configuration Release --no-build --no-dependencies --no-restore --output "$(Join-Path -Path $ProjectRoot -ChildPath artifacts)" .\src\SuperTokens.AspNetCore\SuperTokens.AspNetCore.csproj 19 | } 20 | finally { 21 | Pop-Location 22 | } 23 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/AccessToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SuperTokens.AspNetCore 4 | { 5 | internal sealed class AccessToken 6 | { 7 | public AccessToken( 8 | string? antiCsrfToken, 9 | DateTimeOffset expiryTime, 10 | string? parentRefreshTokenHash1, 11 | string refreshTokenHash1, 12 | string sessionHandle, 13 | DateTimeOffset timeCreated, 14 | string userData, 15 | string userId) 16 | { 17 | this.AntiCsrfToken = antiCsrfToken; 18 | this.ExpiryTime = expiryTime; 19 | this.ParentRefreshTokenHash1 = parentRefreshTokenHash1; 20 | this.RefreshTokenHash1 = refreshTokenHash1; 21 | this.SessionHandle = sessionHandle; 22 | this.TimeCreated = timeCreated; 23 | this.UserData = userData; 24 | this.UserId = userId; 25 | } 26 | 27 | public string? AntiCsrfToken { get; } 28 | 29 | public DateTimeOffset ExpiryTime { get; } 30 | 31 | public string? ParentRefreshTokenHash1 { get; } 32 | 33 | public string RefreshTokenHash1 { get; } 34 | 35 | public string SessionHandle { get; } 36 | 37 | public DateTimeOffset TimeCreated { get; } 38 | 39 | public string UserData { get; } 40 | 41 | public string UserId { get; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/AccessTokenSigningKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SuperTokens.AspNetCore 4 | { 5 | public sealed class AccessTokenSigningKey : IEquatable 6 | { 7 | public AccessTokenSigningKey( 8 | string publicKey, 9 | DateTimeOffset expiration, 10 | DateTimeOffset creation) 11 | { 12 | this.PublicKey = publicKey; 13 | this.Expiration = expiration; 14 | this.Creation = creation; 15 | } 16 | 17 | public DateTimeOffset Creation { get; } 18 | 19 | public DateTimeOffset Expiration { get; } 20 | 21 | public string PublicKey { get; } 22 | 23 | public override bool Equals(object? obj) => this.Equals(obj as AccessTokenSigningKey); 24 | 25 | // We do not consider the creation time during equality check: this information is not available on old APIs. 26 | public bool Equals(AccessTokenSigningKey? other) => other != null && this.PublicKey == other.PublicKey && this.Expiration.Equals(other.Expiration); 27 | 28 | public override int GetHashCode() => HashCode.Combine(this.PublicKey, this.Expiration, this.Creation); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/AccessTokenUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Text.Json; 4 | 5 | namespace SuperTokens.AspNetCore 6 | { 7 | internal static class AccessTokenUtilities 8 | { 9 | public static bool TryParse(string jwtPayload, [NotNullWhen(true)] out AccessToken? accessToken) 10 | { 11 | using var document = JsonDocument.Parse(jwtPayload); 12 | if (!document.RootElement.TryGetProperty("sessionHandle", out var sessionHandleProperty) || sessionHandleProperty.ValueKind != JsonValueKind.String || 13 | !document.RootElement.TryGetProperty("userId", out var userIdProperty) || userIdProperty.ValueKind != JsonValueKind.String || 14 | !document.RootElement.TryGetProperty("refreshTokenHash1", out var refreshTokenHash1Property) || refreshTokenHash1Property.ValueKind != JsonValueKind.String || 15 | (document.RootElement.TryGetProperty("parentRefreshTokenHash1", out var parentRefreshTokenHash1Property) && parentRefreshTokenHash1Property.ValueKind != JsonValueKind.String) || 16 | !document.RootElement.TryGetProperty("userData", out var userDataProperty) || userDataProperty.ValueKind != JsonValueKind.Object || 17 | (document.RootElement.TryGetProperty("antiCsrfToken", out var antiCsrfTokenProperty) && antiCsrfTokenProperty.ValueKind != JsonValueKind.String) || 18 | !document.RootElement.TryGetProperty("expiryTime", out var expiryTimeProperty) || expiryTimeProperty.ValueKind != JsonValueKind.Number || 19 | !document.RootElement.TryGetProperty("timeCreated", out var timeCreatedProperty) || timeCreatedProperty.ValueKind != JsonValueKind.Number) 20 | { 21 | accessToken = null; 22 | return false; 23 | } 24 | 25 | try 26 | { 27 | accessToken = new AccessToken( 28 | antiCsrfTokenProperty.ValueKind == JsonValueKind.Undefined ? null : antiCsrfTokenProperty.GetString()!.Trim(), 29 | DateTimeOffset.FromUnixTimeMilliseconds(expiryTimeProperty.GetInt64()), 30 | parentRefreshTokenHash1Property.ValueKind == JsonValueKind.Undefined ? null : parentRefreshTokenHash1Property.GetString()!.Trim(), 31 | refreshTokenHash1Property.GetString()!.Trim(), 32 | sessionHandleProperty.GetString()!.Trim(), 33 | DateTimeOffset.FromUnixTimeMilliseconds(timeCreatedProperty.GetInt64()), 34 | userDataProperty.GetRawText().Trim(), 35 | userIdProperty.GetString()!.Trim()); 36 | return true; 37 | } 38 | catch (ArgumentOutOfRangeException) 39 | { 40 | accessToken = null; 41 | return false; 42 | } 43 | catch (FormatException) 44 | { 45 | accessToken = null; 46 | return false; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/ApiVersionContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.Extensions.Options; 10 | using SuperTokens.Net; 11 | 12 | namespace SuperTokens.AspNetCore 13 | { 14 | internal sealed class ApiVersionContainer : BackgroundService, IApiVersionContainer 15 | { 16 | private static readonly TimeSpan AutomaticRefreshInterval = new(0, 12, 0, 0, 0); 17 | 18 | private static readonly TimeSpan MinAutomaticRefreshInterval = new(0, 0, 1, 0, 0); 19 | 20 | private static readonly TimeSpan RetryRefreshInterval = new(0, 0, 5, 0, 0); 21 | 22 | private static readonly string[] SupportedApiVersions = new[] { "2.9", "2.8", "2.7" }.OrderByDescending(str => new Version(str)).ToArray(); 23 | 24 | private readonly ISystemClock _clock; 25 | 26 | private readonly ILogger _logger; 27 | 28 | private readonly IOptionsMonitor _options; 29 | 30 | private readonly SemaphoreSlim _refreshLock = new(1); 31 | 32 | private readonly IServiceProvider _services; 33 | 34 | private string? _apiVersion; 35 | 36 | private DateTimeOffset _refreshAfter = DateTimeOffset.MinValue; 37 | 38 | public ApiVersionContainer(IServiceProvider services, ISystemClock clock, IOptionsMonitor options, ILogger logger) 39 | { 40 | _services = services ?? throw new ArgumentNullException(nameof(services)); 41 | _clock = clock ?? throw new ArgumentNullException(nameof(clock)); 42 | _options = options ?? throw new ArgumentNullException(nameof(options)); 43 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 44 | } 45 | 46 | public ValueTask GetApiVersionAsync(string? apiKey) => 47 | this.GetApiVersionAsync(apiKey, CancellationToken.None); 48 | 49 | public ValueTask GetApiVersionAsync(string? apiKey, CancellationToken cancellationToken) 50 | { 51 | var apiVersion = _apiVersion; 52 | return apiVersion != null 53 | ? ValueTask.FromResult(apiVersion) 54 | : new ValueTask(this.RefreshApiVersionAsync(apiKey, cancellationToken)); 55 | } 56 | 57 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 58 | { 59 | while (true) 60 | { 61 | stoppingToken.ThrowIfCancellationRequested(); 62 | 63 | try 64 | { 65 | var options = _options.Get(SuperTokensDefaults.AuthenticationScheme); 66 | await this.RefreshApiVersionAsync(options.CoreApiKey, stoppingToken); 67 | } 68 | catch (Exception e) 69 | { 70 | _logger.LogError(e, "Failed to obtain API versions from SuperTokens Core."); 71 | } 72 | 73 | var delay = _refreshAfter - _clock.UtcNow; 74 | if (delay < MinAutomaticRefreshInterval) 75 | { 76 | delay = MinAutomaticRefreshInterval; 77 | } 78 | 79 | await Task.Delay(delay, stoppingToken); 80 | } 81 | } 82 | 83 | private static NotSupportedException NewNotSupportedException(Exception? innerException = null) => 84 | new("This version of the SuperTokens SDK does not support the specified SuperTokens Core.", innerException); 85 | 86 | private async Task RefreshApiVersionAsync(string? apiKey, CancellationToken cancellationToken) 87 | { 88 | var now = _clock.UtcNow; 89 | 90 | await _refreshLock.WaitAsync(cancellationToken).ConfigureAwait(false); 91 | try 92 | { 93 | if (_refreshAfter <= now) 94 | { 95 | var coreApiClient = _services.GetRequiredService(); 96 | try 97 | { 98 | var result = await coreApiClient.GetApiVersionAsync(apiKey, CancellationToken.None); 99 | string? matchedApiVersion = null; 100 | foreach (var supportedVersion in SupportedApiVersions) 101 | { 102 | foreach (var version in result.Versions) 103 | { 104 | if (supportedVersion.Equals(version, StringComparison.Ordinal)) 105 | { 106 | matchedApiVersion = version; 107 | break; 108 | } 109 | } 110 | 111 | if (matchedApiVersion != null) 112 | { 113 | break; 114 | } 115 | } 116 | 117 | _apiVersion = matchedApiVersion; 118 | _refreshAfter = now.Add(AutomaticRefreshInterval); 119 | } 120 | catch (Exception e) 121 | { 122 | _refreshAfter = now.Add(RetryRefreshInterval); 123 | if (_apiVersion == null) 124 | { 125 | throw NewNotSupportedException(e); 126 | } 127 | else 128 | { 129 | _logger.LogError(e, "Failed to obtain updated API versions from SuperTokens Core."); 130 | } 131 | } 132 | } 133 | 134 | if (_apiVersion == null) 135 | { 136 | throw NewNotSupportedException(); 137 | } 138 | 139 | return _apiVersion; 140 | } 141 | finally 142 | { 143 | _refreshLock.Release(); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/DependencyInjection/SuperTokensExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authentication; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using Microsoft.Extensions.Options; 5 | using SuperTokens.AspNetCore; 6 | using SuperTokens.Net; 7 | 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | public static class SuperTokensExtensions 11 | { 12 | /// 13 | /// Adds SuperTokens authentication to using the default scheme ( 14 | /// ). 15 | /// 16 | /// The . 17 | /// A reference to after the operation has completed. 18 | public static AuthenticationBuilder AddSuperTokens(this AuthenticationBuilder builder) => 19 | builder.AddSuperTokens(SuperTokensDefaults.AuthenticationScheme); 20 | 21 | /// 22 | /// Adds SuperTokens authentication to using the specified scheme. 23 | /// 24 | /// The . 25 | /// The authentication scheme. 26 | /// A reference to after the operation has completed. 27 | public static AuthenticationBuilder AddSuperTokens(this AuthenticationBuilder builder, string authenticationScheme) => 28 | builder.AddSuperTokens(authenticationScheme, null, null!); 29 | 30 | /// 31 | /// Adds SuperTokens authentication to using the default scheme ( 32 | /// ). 33 | /// 34 | /// The . 35 | /// A delegate to configure . 36 | /// A reference to after the operation has completed. 37 | public static AuthenticationBuilder AddSuperTokens(this AuthenticationBuilder builder, Action configureOptions) => 38 | builder.AddSuperTokens(SuperTokensDefaults.AuthenticationScheme, null, configureOptions); 39 | 40 | /// 41 | /// Adds SuperTokens authentication to using the specified scheme. 42 | /// 43 | /// The . 44 | /// The authentication scheme. 45 | /// A delegate to configure . 46 | /// A reference to after the operation has completed. 47 | public static AuthenticationBuilder AddSuperTokens(this AuthenticationBuilder builder, string authenticationScheme, Action configureOptions) => 48 | builder.AddSuperTokens(authenticationScheme, null, configureOptions); 49 | 50 | /// 51 | /// Adds SuperTokens authentication to using the specified scheme. 52 | /// 53 | /// The . 54 | /// The authentication scheme. 55 | /// A display name for the authentication handler. 56 | /// A delegate to configure . 57 | /// A reference to after the operation has completed. 58 | public static AuthenticationBuilder AddSuperTokens(this AuthenticationBuilder builder, string authenticationScheme, string? displayName, Action configureOptions) 59 | { 60 | if (builder == null) 61 | { 62 | throw new ArgumentNullException(nameof(builder)); 63 | } 64 | 65 | builder.Services.AddSingleton(); 66 | builder.Services.AddSingleton(services => services.GetRequiredService()); 67 | builder.Services.AddHostedService(services => services.GetRequiredService()); 68 | builder.Services.AddSingleton(); 69 | builder.Services.AddScoped(); 70 | builder.Services.AddScoped(); 71 | builder.Services.AddHttpClient((services, httpClient) => 72 | { 73 | var options = services.GetRequiredService>().Get(authenticationScheme); 74 | httpClient.BaseAddress = new Uri(options.CoreAddress, UriKind.Absolute); 75 | }); 76 | builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, SuperTokensPostConfigureOptions>()); 77 | return builder.AddScheme(authenticationScheme, displayName, configureOptions); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/Events/MessageReceivedContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace SuperTokens.AspNetCore.Events 5 | { 6 | public class MessageReceivedContext : ResultContext 7 | { 8 | public MessageReceivedContext(HttpContext context, AuthenticationScheme scheme, SuperTokensOptions options) : 9 | base(context, scheme, options) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/Events/SuperTokensEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SuperTokens.AspNetCore.Events 5 | { 6 | public class SuperTokensEvents 7 | { 8 | public Func OnMessageReceived { get; set; } = context => Task.CompletedTask; 9 | 10 | public virtual Task MessageReceived(MessageReceivedContext context) => 11 | this.OnMessageReceived(context); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/Handshake.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | 6 | namespace SuperTokens.AspNetCore 7 | { 8 | public sealed class Handshake 9 | { 10 | private ImmutableArray _accessTokenSigningPublicKeyList; 11 | 12 | public Handshake( 13 | IEnumerable jwtSigningPublicKeyList, 14 | bool accessTokenBlacklistingEnabled, 15 | TimeSpan accessTokenLifetime, 16 | TimeSpan refreshTokenLifetime) 17 | { 18 | _accessTokenSigningPublicKeyList = ImmutableArray.ToImmutableArray(jwtSigningPublicKeyList); 19 | 20 | this.AccessTokenBlacklistingEnabled = accessTokenBlacklistingEnabled; 21 | this.AccessTokenLifetime = accessTokenLifetime; 22 | this.RefreshTokenLifetime = refreshTokenLifetime; 23 | } 24 | 25 | public bool AccessTokenBlacklistingEnabled { get; } 26 | 27 | public TimeSpan AccessTokenLifetime { get; } 28 | 29 | public TimeSpan RefreshTokenLifetime { get; } 30 | 31 | public ImmutableArray GetAccessTokenSigningPublicKeyList(DateTimeOffset now) => 32 | _accessTokenSigningPublicKeyList.Where(keyInfo => keyInfo.Expiration > now).ToImmutableArray(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/HandshakeContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text.Json; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Authentication; 9 | using Microsoft.Extensions.Logging; 10 | using SuperTokens.Net; 11 | 12 | namespace SuperTokens.AspNetCore 13 | { 14 | internal class HandshakeContainer : IHandshakeContainer 15 | { 16 | private readonly ISystemClock _clock; 17 | 18 | private readonly ICoreApiClient _coreApiClient; 19 | 20 | private readonly ILogger _logger; 21 | private readonly SemaphoreSlim _refreshLock = new(1); 22 | 23 | private Handshake? _handshake; 24 | 25 | public HandshakeContainer(ICoreApiClient coreApiClient, ILogger logger, ISystemClock clock) 26 | { 27 | _coreApiClient = coreApiClient ?? throw new ArgumentNullException(nameof(coreApiClient)); 28 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 29 | _clock = clock ?? throw new ArgumentNullException(nameof(clock)); 30 | } 31 | 32 | public Task GetHandshakeAsync(string? apiKey, string? cdiVersion) => 33 | this.GetHandshakeAsync(apiKey, cdiVersion, CancellationToken.None); 34 | 35 | public async Task GetHandshakeAsync(string? apiKey, string? cdiVersion, CancellationToken cancellationToken) 36 | { 37 | var now = _clock.UtcNow; 38 | 39 | var handshake = _handshake; 40 | if (handshake != null && handshake.GetAccessTokenSigningPublicKeyList(now).Length > 0) 41 | { 42 | return handshake; 43 | } 44 | 45 | await _refreshLock.WaitAsync(cancellationToken).ConfigureAwait(false); 46 | try 47 | { 48 | if (_handshake == null || _handshake.GetAccessTokenSigningPublicKeyList(now).Length == 0) 49 | { 50 | try 51 | { 52 | var handshakeResponse = await _coreApiClient.GetHandshakeAsync(apiKey, cdiVersion, CancellationToken.None).ConfigureAwait(false); 53 | if (handshakeResponse.Status.Equals("OK", StringComparison.OrdinalIgnoreCase)) 54 | { 55 | var jwtSigningPublicKeyList = handshakeResponse.JwtSigningPublicKeyList != null ? 56 | handshakeResponse.JwtSigningPublicKeyList.Select(keyInfo => 57 | new AccessTokenSigningKey(keyInfo.publicKey, 58 | DateTimeOffset.FromUnixTimeMilliseconds(keyInfo.expirationTime), 59 | DateTimeOffset.FromUnixTimeMilliseconds(keyInfo.createdAt) 60 | ) 61 | ) : new[] { new AccessTokenSigningKey(handshakeResponse.JwtSigningPublicKey, 62 | DateTimeOffset.FromUnixTimeMilliseconds(handshakeResponse.JwtSigningPublicKeyExpiryTime), 63 | now 64 | )}; 65 | 66 | _handshake = new Handshake( 67 | jwtSigningPublicKeyList, 68 | handshakeResponse.AccessTokenBlacklistingEnabled, 69 | TimeSpan.FromMilliseconds(handshakeResponse.AccessTokenValidity), 70 | TimeSpan.FromMilliseconds(handshakeResponse.RefreshTokenValidity)); 71 | } 72 | } 73 | catch (CoreApiResponseException e) 74 | { 75 | _logger.LogError(e, "Failed to get SuperTokens handshake"); 76 | } 77 | catch (HttpRequestException e) 78 | { 79 | _logger.LogError(e, "Failed to get SuperTokens handshake"); 80 | } 81 | catch (JsonException e) 82 | { 83 | _logger.LogError(e, "Failed to parse SuperTokens handshake"); 84 | } 85 | } 86 | 87 | if (_handshake != null) 88 | { 89 | return _handshake; 90 | } 91 | else 92 | { 93 | throw new InvalidOperationException("No SuperTokens handshake is available"); 94 | } 95 | } 96 | finally 97 | { 98 | _refreshLock.Release(); 99 | } 100 | } 101 | 102 | public async Task OnHandshakeChanged(IEnumerable? jwtSigningPublicKeyList, string jwtSigningPublicKey, DateTimeOffset jwtSigningPublicKeyExpiration) 103 | { 104 | var handshake = _handshake; 105 | if (handshake == null) 106 | { 107 | return; 108 | } 109 | 110 | var now = _clock.UtcNow; 111 | var updatedSigningPublicKeyList = jwtSigningPublicKeyList != null 112 | ? jwtSigningPublicKeyList.Select(keyInfo => 113 | new AccessTokenSigningKey(keyInfo.publicKey, 114 | DateTimeOffset.FromUnixTimeMilliseconds(keyInfo.expirationTime), 115 | DateTimeOffset.FromUnixTimeMilliseconds(keyInfo.createdAt) 116 | )) 117 | : new[] { 118 | new AccessTokenSigningKey(jwtSigningPublicKey, 119 | jwtSigningPublicKeyExpiration, 120 | now 121 | )}; 122 | 123 | if (handshake.GetAccessTokenSigningPublicKeyList(now).SequenceEqual(updatedSigningPublicKeyList)) 124 | { 125 | return; 126 | } 127 | 128 | await _refreshLock.WaitAsync().ConfigureAwait(false); 129 | try 130 | { 131 | if (_handshake != null && !handshake.GetAccessTokenSigningPublicKeyList(now).SequenceEqual(updatedSigningPublicKeyList)) 132 | { 133 | _handshake = new Handshake( 134 | updatedSigningPublicKeyList, 135 | _handshake.AccessTokenBlacklistingEnabled, 136 | _handshake.AccessTokenLifetime, 137 | _handshake.RefreshTokenLifetime); 138 | } 139 | } 140 | finally 141 | { 142 | _refreshLock.Release(); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/IApiVersionContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace SuperTokens.AspNetCore 5 | { 6 | public interface IApiVersionContainer 7 | { 8 | ValueTask GetApiVersionAsync(string? apiKey); 9 | 10 | ValueTask GetApiVersionAsync(string? apiKey, CancellationToken cancellationToken); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/IHandshakeContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace SuperTokens.AspNetCore 7 | { 8 | public interface IHandshakeContainer 9 | { 10 | Task GetHandshakeAsync(string? apiKey, string? cdiVersion); 11 | 12 | Task GetHandshakeAsync(string? apiKey, string? cdiVersion, CancellationToken cancellationToken); 13 | 14 | Task OnHandshakeChanged(IEnumerable? jwtSigningPublicKeyList, string jwtSigningPublicKey, DateTimeOffset jwtSigningPublicKeyExpiration); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/ISessionAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace SuperTokens.AspNetCore 2 | { 3 | /// Provides access to the current , if one is available. 4 | public interface ISessionAccessor 5 | { 6 | /// 7 | /// Gets or sets the current . Returns null if there is no active 8 | /// . 9 | /// 10 | public SuperTokensSession? Session { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/ISessionRecipe.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SuperTokens.AspNetCore 4 | { 5 | public interface ISessionRecipe 6 | { 7 | Task AuthenticateAsync(string userId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/JwtUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Security.Cryptography; 4 | using System.Text; 5 | 6 | namespace SuperTokens.AspNetCore 7 | { 8 | internal static class JwtUtilities 9 | { 10 | /// 11 | /// A base64 encoded representation of the following JWT header: 12 | /// 13 | ///{"alg":"RS256","typ":"JWT","version":"1"} 14 | /// 15 | /// 16 | private const string VersionOneHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIxIn0="; 17 | 18 | /// 19 | /// A base64 encoded representation of the following JWT header: 20 | /// 21 | ///{"alg":"RS256","typ":"JWT","version":"2"} 22 | /// 23 | /// 24 | private const string VersionTwoHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIyIn0="; 25 | 26 | public static bool TryParse(string jwt, [NotNullWhen(true)] out string? jwtPayload, out string[]? components) 27 | { 28 | // Parse JWT. 29 | components = jwt.Split('.'); 30 | if (components.Length != 3 || components[1].Length == 0) 31 | { 32 | jwtPayload = null; 33 | components = null; 34 | return false; 35 | } 36 | 37 | // Verify JWT header. 38 | if (!VersionOneHeader.Equals(components[0], StringComparison.Ordinal) && !VersionTwoHeader.Equals(components[0], StringComparison.Ordinal)) 39 | { 40 | jwtPayload = null; 41 | components = null; 42 | return false; 43 | } 44 | 45 | // Verify JWT payload. 46 | var buffer = new Span(new byte[components[1].Length]); 47 | if (!Convert.TryFromBase64String(components[1], buffer, out var bufferLength)) 48 | { 49 | jwtPayload = null; 50 | components = null; 51 | return false; 52 | } 53 | 54 | jwtPayload = Encoding.UTF8.GetString(buffer.Slice(0, bufferLength)); 55 | return true; 56 | } 57 | 58 | public static bool TryParseAndValidate(string jwt, string jwtSigningPublicKey, [NotNullWhen(true)] out string? jwtPayload, out bool isSignatureValid) 59 | { 60 | // Parse JWT. 61 | var components = jwt.Split('.'); 62 | if (components.Length != 3 || components[1].Length == 0) 63 | { 64 | jwtPayload = null; 65 | isSignatureValid = false; 66 | return false; 67 | } 68 | 69 | // Verify JWT header. 70 | if (!VersionOneHeader.Equals(components[0], StringComparison.Ordinal) && !VersionTwoHeader.Equals(components[0], StringComparison.Ordinal)) 71 | { 72 | jwtPayload = null; 73 | isSignatureValid = false; 74 | return false; 75 | } 76 | 77 | // Verify JWT payload. 78 | var buffer = new Span(new byte[components[1].Length]); 79 | if (!Convert.TryFromBase64String(components[1], buffer, out var bufferLength)) 80 | { 81 | jwtPayload = null; 82 | isSignatureValid = false; 83 | return false; 84 | } 85 | 86 | jwtPayload = Encoding.UTF8.GetString(buffer.Slice(0, bufferLength)); 87 | isSignatureValid = Validate(components, jwtSigningPublicKey); 88 | return true; 89 | } 90 | 91 | public static bool Validate(string[] components, string jwtSigningPublicKey) 92 | { 93 | // Verify JWT signature. 94 | var rsa = RSA.Create(); 95 | var key = Convert.FromBase64String(jwtSigningPublicKey); 96 | rsa.ImportSubjectPublicKeyInfo(key, out _); 97 | 98 | var data = Encoding.UTF8.GetBytes($"{components[0]}.{components[1]}"); 99 | var signature = Convert.FromBase64String(components[2]); 100 | return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SessionAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace SuperTokens.AspNetCore 2 | { 3 | /// Provides an implementation of . 4 | internal class SessionAccessor : ISessionAccessor 5 | { 6 | /// 7 | public SuperTokensSession? Session { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SessionRecipe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.Extensions.Primitives; 8 | using Microsoft.Net.Http.Headers; 9 | using SuperTokens.Net; 10 | using SuperTokens.Net.SessionRecipe; 11 | 12 | namespace SuperTokens.AspNetCore 13 | { 14 | public class SessionRecipe : ISessionRecipe 15 | { 16 | private readonly IApiVersionContainer _apiVersionContainer; 17 | 18 | private readonly ICoreApiClient _coreApiClient; 19 | 20 | private readonly IHandshakeContainer _handshakeContainer; 21 | 22 | private readonly IHttpContextAccessor _httpContextAccessor; 23 | 24 | private readonly IOptionsMonitor _options; 25 | 26 | private readonly ISessionAccessor _sessionAccessor; 27 | 28 | public SessionRecipe(ICoreApiClient coreApiClient, IHandshakeContainer handshakeContainer, ISessionAccessor sessionAccessor, IHttpContextAccessor httpContextAccessor, IOptionsMonitor options, IApiVersionContainer apiVersionContainer) 29 | { 30 | _coreApiClient = coreApiClient ?? throw new ArgumentNullException(nameof(coreApiClient)); 31 | _handshakeContainer = handshakeContainer ?? throw new ArgumentNullException(nameof(handshakeContainer)); 32 | _sessionAccessor = sessionAccessor ?? throw new ArgumentNullException(nameof(sessionAccessor)); 33 | _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); 34 | _options = options ?? throw new ArgumentNullException(nameof(options)); 35 | _apiVersionContainer = apiVersionContainer ?? throw new ArgumentNullException(nameof(apiVersionContainer)); 36 | } 37 | 38 | public async Task AuthenticateAsync(string userId) 39 | { 40 | var options = _options.Get(SuperTokensDefaults.AuthenticationScheme); 41 | 42 | var document = JsonDocument.Parse("{}"); 43 | var emptyObject = document.RootElement.Clone(); 44 | document.Dispose(); 45 | 46 | var cdiVersion = await _apiVersionContainer.GetApiVersionAsync(options.CoreApiKey); 47 | var result = await _coreApiClient.CreateSessionAsync(options.CoreApiKey, cdiVersion, new CreateSessionRequest 48 | { 49 | EnableAntiCsrf = options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaToken, 50 | UserDataInDatabase = emptyObject, 51 | UserDataInJwt = emptyObject, 52 | UserId = userId, 53 | }); 54 | if (!"OK".Equals(result.Status, StringComparison.Ordinal)) 55 | { 56 | throw new InvalidOperationException("Failed to login the user."); 57 | } 58 | 59 | await _handshakeContainer.OnHandshakeChanged(result.JwtSigningPublicKeyList, result.JwtSigningPublicKey, DateTimeOffset.FromUnixTimeMilliseconds(result.JwtSigningPublicKeyExpiryTime)); 60 | 61 | var context = _httpContextAccessor.HttpContext ?? throw new InvalidOperationException("The session recipe only works in a HTTP context."); 62 | if (context.Response.HasStarted) 63 | { 64 | throw new InvalidOperationException("The session recipe cannot send cookies after the response has started."); 65 | } 66 | 67 | context.Response.Headers[HeaderNames.SetCookie] = StringValues.Concat( 68 | context.Response.Headers[HeaderNames.SetCookie], 69 | new StringValues(new[] 70 | { 71 | new SetCookieHeaderValue(SuperTokensDefaults.AccessTokenCookieName, result.AccessToken.Token) 72 | { 73 | Domain = options.CookieDomain, 74 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(result.AccessToken.Expiry), 75 | HttpOnly = true, 76 | Path = "/", 77 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)options.CookieSameSite, 78 | Secure = options.CookieSecure == CookieSecurePolicy.Always || (options.CookieSecure == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps), 79 | }.ToString(), 80 | new SetCookieHeaderValue(SuperTokensDefaults.RefreshTokenCookieName, result.RefreshToken.Token) 81 | { 82 | Domain = options.CookieDomain, 83 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(result.RefreshToken.Expiry), 84 | HttpOnly = true, 85 | Path = new StringSegment(options.RefreshPath ?? "/"), 86 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)options.CookieSameSite, 87 | Secure = options.CookieSecure == CookieSecurePolicy.Always || (options.CookieSecure == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps), 88 | }.ToString(), 89 | new SetCookieHeaderValue(SuperTokensDefaults.IdRefreshTokenCookieName, result.IdRefreshToken.Token) 90 | { 91 | Domain = options.CookieDomain, 92 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(result.IdRefreshToken.Expiry), 93 | HttpOnly = true, 94 | Path = "/", 95 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)options.CookieSameSite, 96 | Secure = options.CookieSecure == CookieSecurePolicy.Always || (options.CookieSecure == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps), 97 | }.ToString(), 98 | })); 99 | 100 | context.Response.Headers[SuperTokensDefaults.FrontTokenHeaderKey] = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new 101 | { 102 | uid = result.Session.UserId, 103 | ate = result.AccessToken.Expiry, 104 | up = result.Session.UserDataInJwt, 105 | }))); 106 | context.Response.Headers[SuperTokensDefaults.IdRefreshTokenHeaderKey] = $"{result.IdRefreshToken.Token};{result.IdRefreshToken.Expiry:D}"; 107 | context.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat( 108 | context.Response.Headers[HeaderNames.AccessControlExposeHeaders], 109 | new StringValues(new[] 110 | { 111 | SuperTokensDefaults.FrontTokenHeaderKey, 112 | SuperTokensDefaults.IdRefreshTokenHeaderKey 113 | })); 114 | 115 | if (result.AntiCsrfToken != null) 116 | { 117 | context.Response.Headers[SuperTokensDefaults.AntiCsrfHeaderKey] = result.AntiCsrfToken; 118 | context.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat( 119 | context.Response.Headers[HeaderNames.AccessControlExposeHeaders], 120 | SuperTokensDefaults.AntiCsrfHeaderKey); 121 | } 122 | 123 | var session = new SuperTokensSession(result.Session.Handle, result.Session.UserId, result.Session.UserDataInJwt.ToString()!); 124 | _sessionAccessor.Session = session; 125 | return session; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokens.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | seniorquico 6 | Copyright © 2021 Kyle Dodson 7 | true 8 | true 9 | Provides ASP.NET Core authentication services for SuperTokens. 10 | MIT 11 | $(SourceRoot)/artifacts/$(Configuration) 12 | https://github.com/seniorquico/SuperTokens.AspNetCore 13 | 14 | 15 | SuperTokens 16 | true 17 | snupkg 18 | 1.0.5 19 | 20 | 21 | 22 | AllEnabledByDefault 23 | true 24 | strict 25 | true 26 | latest 27 | true 28 | $(NoWarn);CS1591 29 | enable 30 | Library 31 | net5.0 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensAntiCsrfMode.cs: -------------------------------------------------------------------------------- 1 | namespace SuperTokens.AspNetCore 2 | { 3 | public enum SuperTokensAntiCsrfMode 4 | { 5 | None = 0, 6 | 7 | ViaCustomHeader, 8 | 9 | ViaToken, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace SuperTokens.AspNetCore 2 | { 3 | public static class SuperTokensDefaults 4 | { 5 | public const string AccessTokenCookieName = "sAccessToken"; 6 | 7 | public const string AntiCsrfHeaderKey = "anti-csrf"; 8 | 9 | public const string AuthenticationScheme = "SuperTokens"; 10 | 11 | public const string FrontendDriverInterfaceHeaderKey = "fdi-version"; 12 | 13 | public const string FrontTokenHeaderKey = "front-token"; 14 | 15 | public const string IdRefreshTokenCookieName = "sIdRefreshToken"; 16 | 17 | public const string IdRefreshTokenHeaderKey = "id-refresh-token"; 18 | 19 | public const string RecipeIdHeaderKey = "rid"; 20 | 21 | public const string RefreshTokenCookieName = "sRefreshToken"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Net.Mime; 5 | using System.Security.Claims; 6 | using System.Security.Principal; 7 | using System.Text; 8 | using System.Text.Encodings.Web; 9 | using System.Text.Json; 10 | using System.Text.Json.Serialization; 11 | using System.Threading.Tasks; 12 | using Microsoft.AspNetCore.Authentication; 13 | using Microsoft.AspNetCore.Http; 14 | using Microsoft.Extensions.Logging; 15 | using Microsoft.Extensions.Options; 16 | using Microsoft.Extensions.Primitives; 17 | using Microsoft.Net.Http.Headers; 18 | using SuperTokens.AspNetCore.Events; 19 | using SuperTokens.Net; 20 | using SuperTokens.Net.Core; 21 | using SuperTokens.Net.SessionRecipe; 22 | 23 | namespace SuperTokens.AspNetCore 24 | { 25 | public class SuperTokensHandler : SignOutAuthenticationHandler, IAuthenticationRequestHandler 26 | { 27 | private readonly IApiVersionContainer _apiVersionContainer; 28 | 29 | private readonly ICoreApiClient _coreApiClient; 30 | 31 | private readonly IHandshakeContainer _handshakeContainer; 32 | 33 | private readonly ISessionAccessor _sessionAccessor; 34 | 35 | public SuperTokensHandler( 36 | ICoreApiClient coreApiClient, 37 | IHandshakeContainer handshakeContainer, 38 | IApiVersionContainer apiVersionContainer, 39 | ISessionAccessor sessionAccessor, 40 | IOptionsMonitor options, 41 | ILoggerFactory logger, 42 | UrlEncoder encoder, 43 | ISystemClock clock) : 44 | base(options, logger, encoder, clock) 45 | { 46 | _coreApiClient = coreApiClient ?? throw new ArgumentNullException(nameof(coreApiClient)); 47 | _apiVersionContainer = apiVersionContainer ?? throw new ArgumentNullException(nameof(apiVersionContainer)); 48 | _handshakeContainer = handshakeContainer ?? throw new ArgumentNullException(nameof(handshakeContainer)); 49 | _sessionAccessor = sessionAccessor ?? throw new ArgumentNullException(nameof(sessionAccessor)); 50 | } 51 | 52 | protected new SuperTokensEvents Events 53 | { 54 | get => (SuperTokensEvents)base.Events!; 55 | set => base.Events = value; 56 | } 57 | 58 | public Task HandleRequestAsync() 59 | { 60 | if (this.Options.RefreshPath.HasValue && this.Options.RefreshPath == this.Request.Path && HttpMethods.IsPost(this.Request.Method)) 61 | { 62 | return this.HandleRefreshRequestAsync(); 63 | } 64 | else if (this.Options.SignOutPath.HasValue && this.Options.SignOutPath == this.Request.Path && HttpMethods.IsPost(this.Request.Method)) 65 | { 66 | return this.HandleSignOutRequestAsync(); 67 | } 68 | 69 | return TaskUtilities.FalseTask; 70 | } 71 | 72 | protected override Task CreateEventsAsync() => 73 | Task.FromResult(new SuperTokensEvents()); 74 | 75 | protected virtual string? GetAccessTokenFromCookie() => 76 | !this.Request.Cookies.TryGetValue(SuperTokensDefaults.AccessTokenCookieName, out var token) || string.IsNullOrEmpty(token) 77 | ? null 78 | : token; 79 | 80 | protected virtual string? GetAntiCsrfTokenFromHeader() => 81 | !this.Request.Headers.TryGetValue(SuperTokensDefaults.AntiCsrfHeaderKey, out var values) || values.Count != 1 || string.IsNullOrEmpty(values[0]) 82 | ? null 83 | : values[0]; 84 | 85 | protected virtual string? GetIdRefreshTokenFromCookie() => 86 | !this.Request.Cookies.TryGetValue(SuperTokensDefaults.IdRefreshTokenCookieName, out var token) || string.IsNullOrEmpty(token) 87 | ? null 88 | : token; 89 | 90 | protected virtual string? GetRecipeIdFromHeader() => 91 | !this.Request.Headers.TryGetValue(SuperTokensDefaults.RecipeIdHeaderKey, out var values) || values.Count != 1 || string.IsNullOrEmpty(values[0]) 92 | ? null 93 | : values[0]; 94 | 95 | protected virtual string? GetRefreshTokenFromCookie() => 96 | !this.Request.Cookies.TryGetValue(SuperTokensDefaults.RefreshTokenCookieName, out var token) || string.IsNullOrEmpty(token) 97 | ? null 98 | : token; 99 | 100 | protected override async Task HandleAuthenticateAsync() 101 | { 102 | if (_sessionAccessor.Session != null) 103 | { 104 | return AuthenticateResult.Success(new AuthenticationTicket(this.CreatePrincipal(_sessionAccessor.Session.UserId), this.Scheme.Name)); 105 | } 106 | 107 | var now = this.Clock.UtcNow; 108 | 109 | var messageReceivedContext = new MessageReceivedContext(this.Context, this.Scheme, this.Options); 110 | await this.Events.MessageReceived(messageReceivedContext); 111 | if (messageReceivedContext.Result != null) 112 | { 113 | return messageReceivedContext.Result; 114 | } 115 | 116 | var idRefreshToken = this.GetIdRefreshTokenFromCookie(); 117 | if (idRefreshToken == null) 118 | { 119 | return AuthenticateResult.NoResult(); 120 | } 121 | 122 | var accessToken = this.GetAccessTokenFromCookie(); 123 | if (accessToken == null) 124 | { 125 | await this.SendTryRefreshTokenResponse(); 126 | return AuthenticateResult.Fail("The access token expired."); 127 | } 128 | 129 | string? antiCsrfToken = null; 130 | if (!HttpMethods.IsGet(this.Request.Method)) 131 | { 132 | if (this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaCustomHeader) 133 | { 134 | var recipeId = this.GetRecipeIdFromHeader(); 135 | if (recipeId == null) 136 | { 137 | await this.SendTryRefreshTokenResponse(); 138 | return AuthenticateResult.Fail("The anti-CSRF header is missing."); 139 | } 140 | } 141 | else if (this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaToken) 142 | { 143 | antiCsrfToken = this.GetAntiCsrfTokenFromHeader(); 144 | if (antiCsrfToken == null) 145 | { 146 | await this.SendTryRefreshTokenResponse(); 147 | return AuthenticateResult.Fail("The anti-CSRF header is missing."); 148 | } 149 | 150 | // We will check the token value after parsing & validating the access token. 151 | } 152 | } 153 | 154 | var cdiVersion = await _apiVersionContainer.GetApiVersionAsync(this.Options.CoreApiKey, this.Context.RequestAborted); 155 | var handshake = await _handshakeContainer.GetHandshakeAsync(this.Options.CoreApiKey, cdiVersion, this.Context.RequestAborted); 156 | 157 | AccessToken? parsedAccessToken; 158 | 159 | // If we have no key old enough to verify this access token we should reject it without calling the core 160 | 161 | if (!JwtUtilities.TryParse(accessToken, out var jwtPayload, out var components) || 162 | !AccessTokenUtilities.TryParse(jwtPayload, out parsedAccessToken)) 163 | { 164 | await this.SendTryRefreshTokenResponse(); 165 | return AuthenticateResult.Fail("The access token is invalid."); 166 | } 167 | 168 | if (parsedAccessToken.ExpiryTime < now) 169 | { 170 | await this.SendTryRefreshTokenResponse(); 171 | return AuthenticateResult.Fail("The access token expired."); 172 | } 173 | 174 | var foundSigningKeyOlderThanToken = false; 175 | var isSignatureValid = false; 176 | foreach (var keyInfo in handshake.GetAccessTokenSigningPublicKeyList(now)) 177 | { 178 | if (JwtUtilities.Validate(components, keyInfo.PublicKey)) 179 | { 180 | // If we reached a key older than the token then we don't need to try older keys since the keys are 181 | // always signed with the latest available key The keylist in the handshake is ordered from newest 182 | // to oldest. 183 | isSignatureValid = true; 184 | } 185 | if (keyInfo.Creation < parsedAccessToken.TimeCreated) 186 | { 187 | foundSigningKeyOlderThanToken = true; 188 | break; 189 | } 190 | } 191 | 192 | // If the token was created before the oldest key in the cache but hasn't expired, then a config value 193 | // must've changed. E.g., the access_token_signing_key_update_interval was reduced, or 194 | // access_token_signing_key_dynamic was turned on. Either way, the user needs to refresh the access token as 195 | // validating by the server is likely to do nothing. 196 | if (!foundSigningKeyOlderThanToken) 197 | { 198 | await this.SendTryRefreshTokenResponse(); 199 | return AuthenticateResult.Fail("The access token expired."); 200 | } 201 | 202 | if (antiCsrfToken != null && !antiCsrfToken.Equals(parsedAccessToken.AntiCsrfToken, StringComparison.Ordinal)) 203 | { 204 | await this.SendTryRefreshTokenResponse(); 205 | return AuthenticateResult.Fail("The anti-CSRF header is invalid."); 206 | } 207 | 208 | if (isSignatureValid && !handshake.AccessTokenBlacklistingEnabled && parsedAccessToken.ParentRefreshTokenHash1 == null) 209 | { 210 | _sessionAccessor.Session = new SuperTokensSession(parsedAccessToken.SessionHandle, parsedAccessToken.UserId, parsedAccessToken.UserData); 211 | return AuthenticateResult.Success(new AuthenticationTicket(this.CreatePrincipal(_sessionAccessor.Session.UserId), this.Scheme.Name)); 212 | } 213 | 214 | try 215 | { 216 | var result = await _coreApiClient.VerifySessionAsync( 217 | this.Options.CoreApiKey, 218 | cdiVersion, 219 | new VerifySessionRequest 220 | { 221 | AccessToken = accessToken, 222 | AntiCsrfToken = antiCsrfToken, 223 | DoAntiCsrfCheck = this.Options.AntiCsrfMode != SuperTokensAntiCsrfMode.ViaToken, 224 | EnableAntiCsrf = this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaToken, 225 | }, 226 | this.Context.RequestAborted); 227 | 228 | if (!string.IsNullOrEmpty(result.JwtSigningPublicKey)) 229 | { 230 | await _handshakeContainer.OnHandshakeChanged(result.JwtSigningPublicKeyList, result.JwtSigningPublicKey, DateTimeOffset.FromUnixTimeMilliseconds(result.JwtSigningPublicKeyExpiryTime)); 231 | } 232 | 233 | if ("TRY_REFRESH_TOKEN".Equals(result.Status, StringComparison.Ordinal)) 234 | { 235 | await this.SendTryRefreshTokenResponse(); 236 | return AuthenticateResult.Fail("The access token expired."); 237 | } 238 | else if ("UNAUTHORISED".Equals(result.Status, StringComparison.Ordinal)) 239 | { 240 | await this.SendUnauthorisedResponse(); 241 | return AuthenticateResult.Fail("The access token expired."); 242 | } 243 | 244 | if (result.AccessToken != null) 245 | { 246 | this.RefreshSessionCookies(result.Session, result.AccessToken); 247 | } 248 | 249 | _sessionAccessor.Session = new(result.Session.Handle, result.Session.UserId, result.Session.UserDataInJwt.GetRawText()); 250 | return AuthenticateResult.Success(new AuthenticationTicket(this.CreatePrincipal(result.Session.UserId), this.Scheme.Name)); 251 | } 252 | catch (CoreApiResponseException e) 253 | { 254 | this.Logger.LogError(e, "Failed to verify SuperTokens session."); 255 | await this.WriteEmptyResponse(StatusCodes.Status500InternalServerError); 256 | return AuthenticateResult.NoResult(); 257 | } 258 | catch (HttpRequestException e) 259 | { 260 | this.Logger.LogError(e, "Failed to verify SuperTokens session."); 261 | await this.WriteEmptyResponse(StatusCodes.Status500InternalServerError); 262 | return AuthenticateResult.NoResult(); 263 | } 264 | } 265 | 266 | protected override Task HandleChallengeAsync(AuthenticationProperties properties) 267 | { 268 | if (this.Response.HasStarted) 269 | { 270 | return Task.CompletedTask; 271 | } 272 | 273 | return base.HandleChallengeAsync(properties); 274 | } 275 | 276 | protected override async Task HandleSignOutAsync(AuthenticationProperties? properties) 277 | { 278 | var result = await this.AuthenticateAsync(); 279 | if (result.Succeeded) 280 | { 281 | var cdiVersion = await _apiVersionContainer.GetApiVersionAsync(this.Options.CoreApiKey, this.Context.RequestAborted); 282 | await _coreApiClient.DeleteSessionAsync(this.Options.CoreApiKey, cdiVersion, new DeleteSessionRequest 283 | { 284 | SessionHandles = new() { _sessionAccessor.Session!.Handle }, 285 | }); 286 | this.DeleteSessionCookies(); 287 | } 288 | else if (result.None) 289 | { 290 | await this.SendTryRefreshTokenResponse(); 291 | } 292 | } 293 | 294 | protected override Task InitializeHandlerAsync() 295 | { 296 | this.Context.Response.OnStarting(this.FinishResponseAsync); 297 | return base.InitializeHandlerAsync(); 298 | } 299 | 300 | private void AddSessionCookies(Session session, CookieInfo accessToken, CookieInfo refreshToken, CookieInfo idRefreshToken, string? antiCsrfToken) 301 | { 302 | if (this.Response.HasStarted) 303 | { 304 | throw new InvalidOperationException("Response headers have already been sent to the client"); 305 | } 306 | 307 | this.Response.Headers[HeaderNames.SetCookie] = StringValues.Concat( 308 | this.Response.Headers[HeaderNames.SetCookie], 309 | new StringValues(new[] 310 | { 311 | new SetCookieHeaderValue(SuperTokensDefaults.AccessTokenCookieName, accessToken.Token) 312 | { 313 | Domain = this.Options.CookieDomain, 314 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(accessToken.Expiry), 315 | HttpOnly = true, 316 | Path = "/", 317 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)this.Options.CookieSameSite, 318 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 319 | }.ToString(), 320 | new SetCookieHeaderValue(SuperTokensDefaults.RefreshTokenCookieName, refreshToken.Token) 321 | { 322 | Domain = this.Options.CookieDomain, 323 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(refreshToken.Expiry), 324 | HttpOnly = true, 325 | Path = (this.Options.RefreshPath ?? "/").Value, 326 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)this.Options.CookieSameSite, 327 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 328 | }.ToString(), 329 | new SetCookieHeaderValue(SuperTokensDefaults.IdRefreshTokenCookieName, idRefreshToken.Token) 330 | { 331 | Domain = this.Options.CookieDomain, 332 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(idRefreshToken.Expiry), 333 | HttpOnly = true, 334 | Path = "/", 335 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)this.Options.CookieSameSite, 336 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 337 | }.ToString(), 338 | })); 339 | 340 | this.Response.Headers[SuperTokensDefaults.FrontTokenHeaderKey] = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new 341 | { 342 | uid = session.UserId, 343 | ate = accessToken.Expiry, 344 | up = session.UserDataInJwt, 345 | }))); 346 | this.Response.Headers[SuperTokensDefaults.IdRefreshTokenHeaderKey] = $"{idRefreshToken.Token};{idRefreshToken.Expiry:D}"; 347 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat( 348 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders], 349 | new StringValues(new[] 350 | { 351 | SuperTokensDefaults.FrontTokenHeaderKey, 352 | SuperTokensDefaults.IdRefreshTokenHeaderKey 353 | })); 354 | 355 | if (antiCsrfToken != null) 356 | { 357 | this.Response.Headers[SuperTokensDefaults.AntiCsrfHeaderKey] = antiCsrfToken; 358 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat( 359 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders], 360 | SuperTokensDefaults.AntiCsrfHeaderKey); 361 | } 362 | } 363 | 364 | private ClaimsPrincipal CreatePrincipal(string userId) => 365 | new GenericPrincipal( 366 | new ClaimsIdentity( 367 | new[] 368 | { 369 | new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String), 370 | }, 371 | this.Scheme.Name), 372 | null); 373 | 374 | private void DeleteSessionCookies() 375 | { 376 | if (this.Response.HasStarted) 377 | { 378 | throw new InvalidOperationException("Response headers have already been sent to the client"); 379 | } 380 | 381 | this.Response.Cookies.Delete(SuperTokensDefaults.AccessTokenCookieName, new CookieOptions 382 | { 383 | Domain = this.Options.CookieDomain, 384 | HttpOnly = true, 385 | IsEssential = true, 386 | Path = "/", 387 | SameSite = this.Options.CookieSameSite, 388 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 389 | }); 390 | this.Response.Cookies.Delete(SuperTokensDefaults.RefreshTokenCookieName, new CookieOptions 391 | { 392 | Domain = this.Options.CookieDomain, 393 | HttpOnly = true, 394 | IsEssential = true, 395 | Path = this.Options.RefreshPath ?? "/", 396 | SameSite = this.Options.CookieSameSite, 397 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 398 | }); 399 | this.Response.Cookies.Delete(SuperTokensDefaults.IdRefreshTokenCookieName, new CookieOptions 400 | { 401 | Domain = this.Options.CookieDomain, 402 | HttpOnly = true, 403 | IsEssential = true, 404 | Path = "/", 405 | SameSite = this.Options.CookieSameSite, 406 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 407 | }); 408 | this.Response.Headers[SuperTokensDefaults.IdRefreshTokenHeaderKey] = "remove"; 409 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat(this.Response.Headers[HeaderNames.AccessControlExposeHeaders], SuperTokensDefaults.IdRefreshTokenHeaderKey); 410 | } 411 | 412 | private Task FinishResponseAsync() => 413 | Task.CompletedTask; 414 | 415 | private async Task HandleRefreshRequestAsync() 416 | { 417 | var idRefreshToken = this.GetIdRefreshTokenFromCookie(); 418 | if (idRefreshToken == null) 419 | { 420 | // Do not delete cookies because of a possible race condition 421 | // (https://github.com/supertokens/supertokens-node/issues/17). 422 | await this.SendUnauthorisedResponse(); 423 | return true; 424 | } 425 | 426 | var refreshToken = this.GetRefreshTokenFromCookie(); 427 | if (refreshToken == null) 428 | { 429 | this.DeleteSessionCookies(); 430 | await this.SendUnauthorisedResponse(); 431 | return true; 432 | } 433 | 434 | string? antiCsrfToken = null; 435 | if (this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaCustomHeader) 436 | { 437 | var recipeId = this.GetRecipeIdFromHeader(); 438 | if (recipeId == null) 439 | { 440 | this.DeleteSessionCookies(); 441 | await this.SendUnauthorisedResponse(); 442 | return true; 443 | } 444 | } 445 | else if (this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaToken) 446 | { 447 | antiCsrfToken = this.GetAntiCsrfTokenFromHeader(); 448 | if (antiCsrfToken == null) 449 | { 450 | this.DeleteSessionCookies(); 451 | await this.SendUnauthorisedResponse(); 452 | return true; 453 | } 454 | 455 | // We will check the token value on the server. 456 | } 457 | 458 | try 459 | { 460 | var cdiVersion = await _apiVersionContainer.GetApiVersionAsync(this.Options.CoreApiKey, this.Context.RequestAborted); 461 | var result = await _coreApiClient.RefreshSessionAsync( 462 | this.Options.CoreApiKey, 463 | cdiVersion, 464 | new RefreshSessionRequest 465 | { 466 | AntiCsrfToken = antiCsrfToken, 467 | EnableAntiCsrf = this.Options.AntiCsrfMode == SuperTokensAntiCsrfMode.ViaToken, 468 | RefreshToken = refreshToken, 469 | }, 470 | this.Context.RequestAborted); 471 | 472 | if ("TOKEN_THEFT_DETECTED".Equals(result.Status, StringComparison.Ordinal)) 473 | { 474 | this.DeleteSessionCookies(); 475 | await this.SendTokenTheftDetectedResponse(); 476 | return true; 477 | } 478 | else if ("UNAUTHORISED".Equals(result.Status, StringComparison.Ordinal)) 479 | { 480 | this.DeleteSessionCookies(); 481 | await this.SendUnauthorisedResponse(); 482 | return true; 483 | } 484 | 485 | _sessionAccessor.Session = new(result.Session.Handle, result.Session.UserId, result.Session.UserDataInJwt.GetRawText()); 486 | 487 | this.AddSessionCookies(result.Session, result.AccessToken, result.RefreshToken, result.IdRefreshToken, result.AntiCsrfToken); 488 | await this.SendOkResponse(); 489 | return true; 490 | } 491 | catch (CoreApiResponseException e) 492 | { 493 | this.Logger.LogError(e, "Failed to refresh SuperTokens session."); 494 | await this.WriteEmptyResponse(StatusCodes.Status500InternalServerError); 495 | return true; 496 | } 497 | catch (HttpRequestException e) 498 | { 499 | this.Logger.LogError(e, "Failed to refresh SuperTokens session."); 500 | await this.WriteEmptyResponse(StatusCodes.Status500InternalServerError); 501 | return true; 502 | } 503 | } 504 | 505 | private async Task HandleSignOutRequestAsync() 506 | { 507 | var result = await this.AuthenticateAsync(); 508 | if (result.Succeeded) 509 | { 510 | var cdiVersion = await _apiVersionContainer.GetApiVersionAsync(this.Options.CoreApiKey, this.Context.RequestAborted); 511 | await _coreApiClient.DeleteSessionAsync(this.Options.CoreApiKey, cdiVersion, new DeleteSessionRequest 512 | { 513 | SessionHandles = new() { _sessionAccessor.Session!.Handle }, 514 | }); 515 | this.DeleteSessionCookies(); 516 | await this.SendOkResponse(); 517 | } 518 | else if (result.None) 519 | { 520 | await this.SendTryRefreshTokenResponse(); 521 | } 522 | else 523 | { 524 | await this.SendUnauthorisedResponse(); 525 | } 526 | 527 | return true; 528 | } 529 | 530 | private void RefreshSessionCookies(Session session, CookieInfo accessToken) 531 | { 532 | if (this.Response.HasStarted) 533 | { 534 | throw new InvalidOperationException("Response headers have already been sent to the client"); 535 | } 536 | 537 | this.Response.Headers[HeaderNames.SetCookie] = StringValues.Concat( 538 | this.Response.Headers[HeaderNames.SetCookie], 539 | new SetCookieHeaderValue(SuperTokensDefaults.AccessTokenCookieName, accessToken.Token) 540 | { 541 | Domain = this.Options.CookieDomain, 542 | Expires = DateTimeOffset.FromUnixTimeMilliseconds(accessToken.Expiry), 543 | HttpOnly = true, 544 | Path = "/", 545 | SameSite = (Microsoft.Net.Http.Headers.SameSiteMode)this.Options.CookieSameSite, 546 | Secure = this.Options.CookieSecure == CookieSecurePolicy.Always || (this.Options.CookieSecure == CookieSecurePolicy.SameAsRequest && this.Request.IsHttps), 547 | }.ToString()); 548 | this.Response.Headers[SuperTokensDefaults.FrontTokenHeaderKey] = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new 549 | { 550 | uid = session.UserId, 551 | ate = accessToken.Expiry, 552 | up = session.UserDataInJwt, 553 | }))); 554 | this.Response.Headers[HeaderNames.AccessControlExposeHeaders] = StringValues.Concat(this.Response.Headers[HeaderNames.AccessControlExposeHeaders], SuperTokensDefaults.FrontTokenHeaderKey); 555 | } 556 | 557 | private Task SendOkResponse() => 558 | this.WriteJsonResponse(StatusCodes.Status200OK, "{}"); 559 | 560 | private Task SendTokenTheftDetectedResponse() => 561 | this.WriteErrorResponse("token theft detected"); 562 | 563 | private Task SendTryRefreshTokenResponse() => 564 | this.WriteErrorResponse("try refresh token"); 565 | 566 | private Task SendUnauthorisedResponse() => 567 | this.WriteErrorResponse("unauthorised"); 568 | 569 | private Task WriteEmptyResponse(int statusCode) 570 | { 571 | if (this.Response.HasStarted) 572 | { 573 | throw new InvalidOperationException("Response headers have already been sent to the client"); 574 | } 575 | 576 | this.Response.StatusCode = statusCode; 577 | 578 | return this.Response.CompleteAsync(); 579 | } 580 | 581 | private Task WriteErrorResponse(string message) => 582 | this.WriteJsonResponse( 583 | StatusCodes.Status401Unauthorized, 584 | JsonSerializer.Serialize(new ErrorResponse 585 | { 586 | Message = message, 587 | })); 588 | 589 | private async Task WriteJsonResponse(int statusCode, string json) 590 | { 591 | if (this.Response.HasStarted) 592 | { 593 | throw new InvalidOperationException("Response headers have already been sent to the client"); 594 | } 595 | 596 | this.Response.StatusCode = statusCode; 597 | 598 | using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json); 599 | foreach (var header in content.Headers) 600 | { 601 | this.Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray())); 602 | } 603 | 604 | await content.CopyToAsync(this.Response.Body, this.Context.RequestAborted).ConfigureAwait(false); 605 | await this.Response.CompleteAsync(); 606 | } 607 | 608 | private class ErrorResponse 609 | { 610 | [JsonPropertyName("message")] 611 | public string? Message { get; set; } 612 | } 613 | } 614 | } 615 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace SuperTokens.AspNetCore 5 | { 6 | public class SuperTokensOptions : AuthenticationSchemeOptions 7 | { 8 | public SuperTokensAntiCsrfMode AntiCsrfMode { get; set; } 9 | 10 | public string? ApiBasePath { get; set; } 11 | 12 | public string ApiDomain { get; set; } = null!; 13 | 14 | public string? ApiGatewayPath { get; set; } 15 | 16 | public string AppName { get; set; } = null!; 17 | 18 | public string? CookieDomain { get; set; } 19 | 20 | public SameSiteMode CookieSameSite { get; set; } = SameSiteMode.Unspecified; 21 | 22 | public CookieSecurePolicy CookieSecure { get; set; } = CookieSecurePolicy.SameAsRequest; 23 | 24 | public string CoreAddress { get; set; } = null!; 25 | 26 | public string? CoreApiKey { get; set; } 27 | 28 | public PathString? RefreshPath { get; set; } = "/auth/session/refresh"; 29 | 30 | public PathString? SignOutPath { get; set; } = "/auth/signout"; 31 | 32 | public string? WebsiteBasePath { get; set; } 33 | 34 | public string WebsiteDomain { get; set; } = null!; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensPostConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace SuperTokens.AspNetCore 5 | { 6 | public class SuperTokensPostConfigureOptions : IPostConfigureOptions 7 | { 8 | public void PostConfigure(string name, SuperTokensOptions options) 9 | { 10 | if (name == null) 11 | { 12 | throw new ArgumentNullException(nameof(name)); 13 | } 14 | 15 | if (options == null) 16 | { 17 | throw new ArgumentNullException(nameof(options)); 18 | } 19 | 20 | if (options.CookieDomain == null) 21 | { 22 | if (Uri.TryCreate(options.ApiDomain, UriKind.Absolute, out var apiUri)) 23 | { 24 | options.CookieDomain = apiUri.Host; 25 | } 26 | else 27 | { 28 | throw new InvalidOperationException("The API domain must have a valid host name."); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/SuperTokensSession.cs: -------------------------------------------------------------------------------- 1 | namespace SuperTokens.AspNetCore 2 | { 3 | public sealed class SuperTokensSession 4 | { 5 | public SuperTokensSession(string handle, string userId, string userDataInJwt) 6 | { 7 | this.Handle = handle; 8 | this.UserId = userId; 9 | this.UserDataInJwt = userDataInJwt; 10 | } 11 | 12 | public string Handle { get; } 13 | 14 | public string UserDataInJwt { get; } 15 | 16 | public string UserId { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/TaskUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SuperTokens.AspNetCore 4 | { 5 | internal static class TaskUtilities 6 | { 7 | public static readonly Task FalseTask = Task.FromResult(false); 8 | 9 | public static readonly Task TrueTask = Task.FromResult(true); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SuperTokens.AspNetCore/paket.references: -------------------------------------------------------------------------------- 1 | Newtonsoft.Json -------------------------------------------------------------------------------- /src/SuperTokens.Net/Core/ApiVersionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.Core 5 | { 6 | public class ApiVersionResponse 7 | { 8 | [JsonPropertyName("versions")] 9 | public List Versions { get; set; } = null!; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/Core/CookieInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.Core 4 | { 5 | public class CookieInfo 6 | { 7 | [JsonPropertyName("createdTime")] 8 | public long CreatedTime { get; set; } 9 | 10 | [JsonPropertyName("expiry")] 11 | public long Expiry { get; set; } 12 | 13 | [JsonPropertyName("token")] 14 | public string Token { get; set; } = null!; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/Core/Session.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.Core 5 | { 6 | public class Session 7 | { 8 | [JsonPropertyName("handle")] 9 | public string Handle { get; set; } = null!; 10 | 11 | [JsonPropertyName("userDataInJWT")] 12 | public JsonElement UserDataInJwt { get; set; } 13 | 14 | [JsonPropertyName("userId")] 15 | public string UserId { get; set; } = null!; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/Core/ThirdPartyUser.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.Core 4 | { 5 | public class ThirdPartyUser 6 | { 7 | [JsonPropertyName("email")] 8 | public string Email { get; set; } = null!; 9 | 10 | [JsonPropertyName("id")] 11 | public string Id { get; set; } = null!; 12 | 13 | [JsonPropertyName("thirdParty")] 14 | public ThirdPartyUserThirdParty ThirdParty { get; set; } = null!; 15 | 16 | [JsonPropertyName("timeJoined")] 17 | public long TimeJoined { get; set; } 18 | 19 | public class ThirdPartyUserThirdParty 20 | { 21 | [JsonPropertyName("id")] 22 | public string Id { get; set; } = null!; 23 | 24 | [JsonPropertyName("userId")] 25 | public string UserId { get; set; } = null!; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/Core/User.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.Core 4 | { 5 | public class User 6 | { 7 | [JsonPropertyName("email")] 8 | public string Email { get; set; } = null!; 9 | 10 | [JsonPropertyName("id")] 11 | public string Id { get; set; } = null!; 12 | 13 | [JsonPropertyName("timeJoined")] 14 | public long TimeJoined { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/CoreApiClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.Mime; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using SuperTokens.Net.Core; 11 | using SuperTokens.Net.SessionRecipe; 12 | 13 | namespace SuperTokens.Net 14 | { 15 | public sealed class CoreApiClient : ICoreApiClient 16 | { 17 | private static readonly JsonSerializerOptions _jsonSerializerOptions = new() 18 | { 19 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 20 | }; 21 | 22 | private readonly HttpClient _httpClient; 23 | 24 | public CoreApiClient(HttpClient httpClient) => 25 | _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); 26 | 27 | public Task CreateSessionAsync(string? apiKey, string? cdiVersion, CreateSessionRequest body) => 28 | this.CreateSessionAsync(apiKey, cdiVersion, body, CancellationToken.None); 29 | 30 | public async Task CreateSessionAsync(string? apiKey, string? cdiVersion, CreateSessionRequest body, CancellationToken cancellationToken) 31 | { 32 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/session", UriKind.Relative), apiKey, cdiVersion, body); 33 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 34 | } 35 | 36 | public Task DeleteSessionAsync(string? apiKey, string? cdiVersion, DeleteSessionRequest body) => 37 | this.DeleteSessionAsync(apiKey, cdiVersion, body, CancellationToken.None); 38 | 39 | public async Task DeleteSessionAsync(string? apiKey, string? cdiVersion, DeleteSessionRequest body, CancellationToken cancellationToken) 40 | { 41 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/session/remove", UriKind.Relative), apiKey, cdiVersion, body); 42 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 43 | } 44 | 45 | public Task GetApiVersionAsync(string? apiKey) => 46 | this.GetApiVersionAsync(apiKey, CancellationToken.None); 47 | 48 | public async Task GetApiVersionAsync(string? apiKey, CancellationToken cancellationToken) 49 | { 50 | using var request = CreateRequest(HttpMethod.Get, new Uri("/apiversion", UriKind.Relative), apiKey, null); 51 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 52 | } 53 | 54 | public Task GetHandshakeAsync(string? apiKey, string? cdiVersion) => 55 | this.GetHandshakeAsync(apiKey, cdiVersion, CancellationToken.None); 56 | 57 | public async Task GetHandshakeAsync(string? apiKey, string? cdiVersion, CancellationToken cancellationToken) 58 | { 59 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/handshake", UriKind.Relative), apiKey, cdiVersion); 60 | request.Content = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json); 61 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 62 | } 63 | 64 | public Task GetSessionAsync(string? apiKey, string? cdiVersion, string sessionHandle) => 65 | this.GetSessionAsync(apiKey, cdiVersion, sessionHandle, CancellationToken.None); 66 | 67 | public async Task GetSessionAsync(string? apiKey, string? cdiVersion, string sessionHandle, CancellationToken cancellationToken) 68 | { 69 | using var request = CreateRequest(HttpMethod.Get, new Uri($"/recipe/session?sessionHandle={sessionHandle}", UriKind.Relative), apiKey, cdiVersion); 70 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 71 | } 72 | 73 | public Task GetSessionHandlesAsync(string? apiKey, string? cdiVersion, string userId) => 74 | this.GetSessionHandlesAsync(apiKey, cdiVersion, userId, CancellationToken.None); 75 | 76 | public async Task GetSessionHandlesAsync(string? apiKey, string? cdiVersion, string userId, CancellationToken cancellationToken) 77 | { 78 | using var request = CreateRequest(HttpMethod.Get, new Uri($"/recipe/session/user?userId={userId}", UriKind.Relative), apiKey, cdiVersion); 79 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 80 | } 81 | 82 | public Task RefreshSessionAsync(string? apiKey, string? cdiVersion, RefreshSessionRequest body) => 83 | this.RefreshSessionAsync(apiKey, cdiVersion, body, CancellationToken.None); 84 | 85 | public async Task RefreshSessionAsync(string? apiKey, string? cdiVersion, RefreshSessionRequest body, CancellationToken cancellationToken) 86 | { 87 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/session/refresh", UriKind.Relative), apiKey, cdiVersion, body); 88 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 89 | } 90 | 91 | public Task RegenerateSessionAsync(string? apiKey, string? cdiVersion, RegenerateSessionRequest body) => 92 | this.RegenerateSessionAsync(apiKey, cdiVersion, body, CancellationToken.None); 93 | 94 | public async Task RegenerateSessionAsync(string? apiKey, string? cdiVersion, RegenerateSessionRequest body, CancellationToken cancellationToken) 95 | { 96 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/session/regenerate", UriKind.Relative), apiKey, cdiVersion, body); 97 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 98 | } 99 | 100 | public Task VerifySessionAsync(string? apiKey, string? cdiVersion, VerifySessionRequest body) => 101 | this.VerifySessionAsync(apiKey, cdiVersion, body, CancellationToken.None); 102 | 103 | public async Task VerifySessionAsync(string? apiKey, string? cdiVersion, VerifySessionRequest body, CancellationToken cancellationToken) 104 | { 105 | using var request = CreateRequest(HttpMethod.Post, new Uri("/recipe/session/verify", UriKind.Relative), apiKey, cdiVersion, body); 106 | return await SendRequestAsync(_httpClient, request, cancellationToken).ConfigureAwait(false); 107 | } 108 | 109 | private static HttpRequestMessage CreateRequest(HttpMethod method, Uri uri, string? apiKey, string? cdiVersion) => 110 | CreateRequest(method, uri, apiKey, cdiVersion, null); 111 | 112 | private static HttpRequestMessage CreateRequest(HttpMethod method, Uri uri, string? apiKey, string? cdiVersion, T? body) 113 | where T : class 114 | { 115 | var request = new HttpRequestMessage(method, uri); 116 | if (apiKey != null) 117 | { 118 | request.Headers.TryAddWithoutValidation("api-key", apiKey); 119 | } 120 | 121 | if (cdiVersion != null) 122 | { 123 | request.Headers.TryAddWithoutValidation("cdi-version", cdiVersion); 124 | } 125 | 126 | if (body != null) 127 | { 128 | string requestBody; 129 | try 130 | { 131 | requestBody = JsonSerializer.Serialize(body, _jsonSerializerOptions); 132 | } 133 | catch 134 | { 135 | request.Dispose(); 136 | throw; 137 | } 138 | 139 | request.Content = new StringContent(requestBody, Encoding.UTF8, MediaTypeNames.Application.Json); 140 | } 141 | 142 | return request; 143 | } 144 | 145 | private static async Task SendRequestAsync(HttpClient httpClient, HttpRequestMessage request, CancellationToken cancellationToken) 146 | where T : class 147 | { 148 | using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); 149 | if (response.IsSuccessStatusCode) 150 | { 151 | var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); 152 | var responseBody = JsonSerializer.Deserialize(responseContent, _jsonSerializerOptions); 153 | if (responseBody == null) 154 | { 155 | throw new CoreApiResponseException(request.Method.Method, request.RequestUri?.ToString() ?? "", (int)response.StatusCode, responseContent); 156 | } 157 | 158 | return responseBody; 159 | } 160 | else if (response.StatusCode == HttpStatusCode.BadRequest) 161 | { 162 | var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); 163 | throw new CoreApiResponseException(request.Method.Method, request.RequestUri?.ToString() ?? "", (int)response.StatusCode, responseContent); 164 | } 165 | else 166 | { 167 | throw new CoreApiResponseException(request.Method.Method, request.RequestUri?.ToString() ?? "", (int)response.StatusCode, null); 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/CoreApiResponseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace SuperTokens.Net 5 | { 6 | [Serializable] 7 | public sealed class CoreApiResponseException : Exception 8 | { 9 | private const string DefaultMessage = "Failed to call the SuperTokens Core API."; 10 | 11 | public CoreApiResponseException() : 12 | base(DefaultMessage) 13 | { 14 | } 15 | 16 | public CoreApiResponseException(string message) : 17 | base(message) 18 | { 19 | } 20 | 21 | public CoreApiResponseException(string message, Exception innerException) : 22 | base(message, innerException) 23 | { 24 | } 25 | 26 | public CoreApiResponseException(string requestMethod, string requestUrl, int responseStatusCode, string? responseBody) : 27 | base(DefaultMessage) 28 | { 29 | this.RequestMethod = requestMethod ?? throw new ArgumentNullException(nameof(requestMethod)); 30 | this.RequestUrl = requestUrl ?? throw new ArgumentNullException(nameof(requestUrl)); 31 | this.ResponseStatusCode = responseStatusCode; 32 | this.ResponseBody = responseBody; 33 | } 34 | 35 | public CoreApiResponseException(string requestMethod, string requestUrl, int responseStatusCode, string? responseBody, Exception innerException) : 36 | base(DefaultMessage, innerException) 37 | { 38 | this.RequestMethod = requestMethod ?? throw new ArgumentNullException(nameof(requestMethod)); 39 | this.RequestUrl = requestUrl ?? throw new ArgumentNullException(nameof(requestUrl)); 40 | this.ResponseStatusCode = responseStatusCode; 41 | this.ResponseBody = responseBody; 42 | } 43 | 44 | private CoreApiResponseException(SerializationInfo info, StreamingContext context) : 45 | base(info, context) 46 | { 47 | this.RequestMethod = info.GetString(nameof(this.RequestMethod)); 48 | this.RequestUrl = info.GetString(nameof(this.RequestUrl)); 49 | this.ResponseBody = info.GetString(nameof(this.ResponseBody)); 50 | this.ResponseStatusCode = info.GetInt32(nameof(this.ResponseStatusCode)); 51 | } 52 | 53 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 54 | { 55 | if (info == null) 56 | { 57 | throw new ArgumentNullException(nameof(info)); 58 | } 59 | 60 | info.AddValue(nameof(this.RequestMethod), this.RequestMethod); 61 | info.AddValue(nameof(this.RequestUrl), this.RequestUrl); 62 | info.AddValue(nameof(this.ResponseBody), this.ResponseBody); 63 | info.AddValue(nameof(this.ResponseStatusCode), this.ResponseStatusCode); 64 | 65 | base.GetObjectData(info, context); 66 | } 67 | 68 | public string? RequestMethod { get; } 69 | 70 | public string? RequestUrl { get; } 71 | 72 | public string? ResponseBody { get; } 73 | 74 | public int ResponseStatusCode { get; } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/ICoreApiClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using SuperTokens.Net.Core; 4 | using SuperTokens.Net.SessionRecipe; 5 | 6 | namespace SuperTokens.Net 7 | { 8 | public interface ICoreApiClient 9 | { 10 | Task CreateSessionAsync(string? apiKey, string? cdiVersion, CreateSessionRequest body); 11 | 12 | Task CreateSessionAsync(string? apiKey, string? cdiVersion, CreateSessionRequest body, CancellationToken cancellationToken); 13 | 14 | Task DeleteSessionAsync(string? apiKey, string? cdiVersion, DeleteSessionRequest body); 15 | 16 | Task DeleteSessionAsync(string? apiKey, string? cdiVersion, DeleteSessionRequest body, CancellationToken cancellationToken); 17 | 18 | Task GetApiVersionAsync(string? apiKey); 19 | 20 | Task GetApiVersionAsync(string? apiKey, CancellationToken cancellationToken); 21 | 22 | Task GetHandshakeAsync(string? apiKey, string? cdiVersion); 23 | 24 | Task GetHandshakeAsync(string? apiKey, string? cdiVersion, CancellationToken cancellationToken); 25 | 26 | Task GetSessionAsync(string? apiKey, string? cdiVersion, string sessionHandle); 27 | 28 | Task GetSessionAsync(string? apiKey, string? cdiVersion, string sessionHandle, CancellationToken cancellationToken); 29 | 30 | Task GetSessionHandlesAsync(string? apiKey, string? cdiVersion, string userId); 31 | 32 | Task GetSessionHandlesAsync(string? apiKey, string? cdiVersion, string userId, CancellationToken cancellationToken); 33 | 34 | Task RefreshSessionAsync(string? apiKey, string? cdiVersion, RefreshSessionRequest body); 35 | 36 | Task RefreshSessionAsync(string? apiKey, string? cdiVersion, RefreshSessionRequest body, CancellationToken cancellationToken); 37 | 38 | Task RegenerateSessionAsync(string? apiKey, string? cdiVersion, RegenerateSessionRequest body); 39 | 40 | Task RegenerateSessionAsync(string? apiKey, string? cdiVersion, RegenerateSessionRequest body, CancellationToken cancellationToken); 41 | 42 | Task VerifySessionAsync(string? apiKey, string? cdiVersion, VerifySessionRequest body); 43 | 44 | Task VerifySessionAsync(string? apiKey, string? cdiVersion, VerifySessionRequest body, CancellationToken cancellationToken); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/CreateSessionRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class CreateSessionRequest 7 | { 8 | [JsonPropertyName("enableAntiCsrf")] 9 | public bool EnableAntiCsrf { get; set; } 10 | 11 | [JsonPropertyName("userDataInDatabase")] 12 | public JsonElement UserDataInDatabase { get; set; } 13 | 14 | [JsonPropertyName("userDataInJWT")] 15 | public JsonElement UserDataInJwt { get; set; } 16 | 17 | [JsonPropertyName("userId")] 18 | public string UserId { get; set; } = null!; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/CreateSessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class CreateSessionResponse 7 | { 8 | [JsonPropertyName("accessToken")] 9 | public Core.CookieInfo AccessToken { get; set; } = null!; 10 | 11 | [JsonPropertyName("antiCsrfToken")] 12 | public string? AntiCsrfToken { get; set; } 13 | 14 | [JsonPropertyName("idRefreshToken")] 15 | public Core.CookieInfo IdRefreshToken { get; set; } = null!; 16 | 17 | [JsonPropertyName("jwtSigningPublicKeyList")] 18 | public List? JwtSigningPublicKeyList { get; set; } = null; 19 | 20 | [JsonPropertyName("jwtSigningPublicKey")] 21 | public string JwtSigningPublicKey { get; set; } = null!; 22 | 23 | [JsonPropertyName("jwtSigningPublicKeyExpiryTime")] 24 | public long JwtSigningPublicKeyExpiryTime { get; set; } 25 | 26 | [JsonPropertyName("refreshToken")] 27 | public Core.CookieInfo RefreshToken { get; set; } = null!; 28 | 29 | [JsonPropertyName("session")] 30 | public Core.Session Session { get; set; } = null!; 31 | 32 | [JsonPropertyName("status")] 33 | public string Status { get; set; } = null!; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/DeleteSessionRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class DeleteSessionRequest 7 | { 8 | [JsonPropertyName("sessionHandles")] 9 | public List? SessionHandles { get; set; } 10 | 11 | [JsonPropertyName("userId")] 12 | public string? UserId { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/DeleteSessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class DeleteSessionResponse 7 | { 8 | [JsonPropertyName("sessionHandlesRevoked")] 9 | public List SessionHandlesRevoked { get; set; } = null!; 10 | 11 | [JsonPropertyName("status")] 12 | public string Status { get; set; } = null!; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/Handshake.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class Handshake 7 | { 8 | [JsonPropertyName("accessTokenBlacklistingEnabled")] 9 | public bool AccessTokenBlacklistingEnabled { get; set; } 10 | 11 | [JsonPropertyName("accessTokenValidity")] 12 | public long AccessTokenValidity { get; set; } 13 | 14 | [JsonPropertyName("jwtSigningPublicKeyList")] 15 | public List? JwtSigningPublicKeyList { get; set; } = null; 16 | 17 | [JsonPropertyName("jwtSigningPublicKey")] 18 | public string JwtSigningPublicKey { get; set; } = null!; 19 | 20 | [JsonPropertyName("jwtSigningPublicKeyExpiryTime")] 21 | public long JwtSigningPublicKeyExpiryTime { get; set; } 22 | 23 | [JsonPropertyName("refreshTokenValidity")] 24 | public long RefreshTokenValidity { get; set; } 25 | 26 | [JsonPropertyName("status")] 27 | public string Status { get; set; } = null!; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/KeyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.SessionRecipe 4 | { 5 | public class KeyInfo 6 | { 7 | [JsonPropertyName("publicKey")] 8 | public string publicKey { get; set; } 9 | 10 | 11 | [JsonPropertyName("expiryTime")] 12 | public long expirationTime { get; set; } 13 | 14 | [JsonPropertyName("createdAt")] 15 | public long createdAt { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/RefreshSessionRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.SessionRecipe 4 | { 5 | public class RefreshSessionRequest 6 | { 7 | [JsonPropertyName("antiCsrfToken")] 8 | public string? AntiCsrfToken { get; set; } 9 | 10 | [JsonPropertyName("enableAntiCsrf")] 11 | public bool EnableAntiCsrf { get; set; } 12 | 13 | [JsonPropertyName("refreshToken")] 14 | public string RefreshToken { get; set; } = null!; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/RefreshSessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.SessionRecipe 4 | { 5 | public class RefreshSessionResponse 6 | { 7 | [JsonPropertyName("accessToken")] 8 | public Core.CookieInfo AccessToken { get; set; } = null!; 9 | 10 | [JsonPropertyName("antiCsrfToken")] 11 | public string? AntiCsrfToken { get; set; } 12 | 13 | [JsonPropertyName("idRefreshToken")] 14 | public Core.CookieInfo IdRefreshToken { get; set; } = null!; 15 | 16 | [JsonPropertyName("refreshToken")] 17 | public Core.CookieInfo RefreshToken { get; set; } = null!; 18 | 19 | [JsonPropertyName("session")] 20 | public Core.Session Session { get; set; } = null!; 21 | 22 | [JsonPropertyName("status")] 23 | public string Status { get; set; } = null!; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/RegenerateSessionRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class RegenerateSessionRequest 7 | { 8 | [JsonPropertyName("accessToken")] 9 | public string AccessToken { get; set; } = null!; 10 | 11 | [JsonPropertyName("userDataInJWT")] 12 | public JsonElement UserDataInJwt { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/RegenerateSessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.SessionRecipe 4 | { 5 | public class RegenerateSessionResponse 6 | { 7 | [JsonPropertyName("accessToken")] 8 | public Core.CookieInfo AccessToken { get; set; } = null!; 9 | 10 | [JsonPropertyName("session")] 11 | public Core.Session Session { get; set; } = null!; 12 | 13 | [JsonPropertyName("status")] 14 | public string Status { get; set; } = null!; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/SessionHandlesResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class SessionHandlesResponse 7 | { 8 | [JsonPropertyName("sessionHandles")] 9 | public List SessionHandles { get; set; } = null!; 10 | 11 | [JsonPropertyName("status")] 12 | public string Status { get; set; } = null!; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/SessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class SessionResponse 7 | { 8 | [JsonPropertyName("expiry")] 9 | public long Expiry { get; set; } 10 | 11 | [JsonPropertyName("status")] 12 | public string Status { get; set; } = null!; 13 | 14 | [JsonPropertyName("timeCreated")] 15 | public long TimeCreated { get; set; } 16 | 17 | [JsonPropertyName("userDataInDatabase")] 18 | public JsonElement UserDataInDatabase { get; set; } 19 | 20 | [JsonPropertyName("userDataInJWT")] 21 | public JsonElement UserDataInJwt { get; set; } 22 | 23 | [JsonPropertyName("userId")] 24 | public string UserId { get; set; } = null!; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/VerifySessionRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.Net.SessionRecipe 4 | { 5 | public class VerifySessionRequest 6 | { 7 | [JsonPropertyName("accessToken")] 8 | public string AccessToken { get; set; } = null!; 9 | 10 | [JsonPropertyName("antiCsrfToken")] 11 | public string? AntiCsrfToken { get; set; } 12 | 13 | [JsonPropertyName("doAntiCsrfCheck")] 14 | public bool DoAntiCsrfCheck { get; set; } 15 | 16 | [JsonPropertyName("enableAntiCsrf")] 17 | public bool EnableAntiCsrf { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SessionRecipe/VerifySessionResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace SuperTokens.Net.SessionRecipe 5 | { 6 | public class VerifySessionResponse 7 | { 8 | [JsonPropertyName("accessToken")] 9 | public Core.CookieInfo? AccessToken { get; set; } 10 | 11 | [JsonPropertyName("jwtSigningPublicKeyList")] 12 | public List? JwtSigningPublicKeyList { get; set; } = null; 13 | 14 | [JsonPropertyName("jwtSigningPublicKey")] 15 | public string JwtSigningPublicKey { get; set; } = null!; 16 | 17 | [JsonPropertyName("jwtSigningPublicKeyExpiryTime")] 18 | public long JwtSigningPublicKeyExpiryTime { get; set; } 19 | 20 | [JsonPropertyName("session")] 21 | public Core.Session Session { get; set; } = null!; 22 | 23 | [JsonPropertyName("status")] 24 | public string Status { get; set; } = null!; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SuperTokens.Net/SuperTokens.Net.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | seniorquico 6 | Copyright © 2021 Kyle Dodson 7 | true 8 | true 9 | Provides a client for the SuperTokens Core API. 10 | MIT 11 | $(SourceRoot)/artifacts/$(Configuration) 12 | https://github.com/seniorquico/SuperTokens.AspNetCore 13 | 14 | SuperTokens 15 | true 16 | snupkg 17 | 1.0.1 18 | 19 | 20 | 21 | AllEnabledByDefault 22 | true 23 | strict 24 | latest 25 | $(NoWarn);CS1591 26 | enable 27 | net5.0 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /supertokens-dotnet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{6B798D51-750E-4988-8709-C96781D968BD}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7BC33F35-5693-4764-A044-F76505DFA836}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D8EDA4B2-174A-4F32-8307-71E256466903}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperTokens.AspNetCore", "src\SuperTokens.AspNetCore\SuperTokens.AspNetCore.csproj", "{94E2ED53-4DB5-4726-B503-AE72FDE6D4A1}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperTokens.Net", "src\SuperTokens.Net\SuperTokens.Net.csproj", "{79F64E2D-7C95-4F8C-A260-D59481CB9649}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperTokens.AspNetCore.Tests", "test\SuperTokens.AspNetCore.Tests\SuperTokens.AspNetCore.Tests.csproj", "{BE05620F-31DB-44A2-8433-DF0CD5E5C5F0}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperTokens.Net.Tests", "test\SuperTokens.Net.Tests\SuperTokens.Net.Tests.csproj", "{C80B04C4-D706-4E40-A2A5-3785483FDCE3}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuperTokens.TestServer", "test\SuperTokens.TestServer\SuperTokens.TestServer.csproj", "{FDD409A4-117F-4432-A898-2FF406231C00}" 24 | EndProject 25 | Global 26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 27 | Debug|Any CPU = Debug|Any CPU 28 | Release|Any CPU = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {94E2ED53-4DB5-4726-B503-AE72FDE6D4A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {94E2ED53-4DB5-4726-B503-AE72FDE6D4A1}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {94E2ED53-4DB5-4726-B503-AE72FDE6D4A1}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {94E2ED53-4DB5-4726-B503-AE72FDE6D4A1}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {79F64E2D-7C95-4F8C-A260-D59481CB9649}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {79F64E2D-7C95-4F8C-A260-D59481CB9649}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {79F64E2D-7C95-4F8C-A260-D59481CB9649}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {79F64E2D-7C95-4F8C-A260-D59481CB9649}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {BE05620F-31DB-44A2-8433-DF0CD5E5C5F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {BE05620F-31DB-44A2-8433-DF0CD5E5C5F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {BE05620F-31DB-44A2-8433-DF0CD5E5C5F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {BE05620F-31DB-44A2-8433-DF0CD5E5C5F0}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {C80B04C4-D706-4E40-A2A5-3785483FDCE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {C80B04C4-D706-4E40-A2A5-3785483FDCE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {C80B04C4-D706-4E40-A2A5-3785483FDCE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {C80B04C4-D706-4E40-A2A5-3785483FDCE3}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {FDD409A4-117F-4432-A898-2FF406231C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {FDD409A4-117F-4432-A898-2FF406231C00}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {FDD409A4-117F-4432-A898-2FF406231C00}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {FDD409A4-117F-4432-A898-2FF406231C00}.Release|Any CPU.Build.0 = Release|Any CPU 51 | EndGlobalSection 52 | GlobalSection(SolutionProperties) = preSolution 53 | HideSolutionNode = FALSE 54 | EndGlobalSection 55 | GlobalSection(NestedProjects) = preSolution 56 | {94E2ED53-4DB5-4726-B503-AE72FDE6D4A1} = {7BC33F35-5693-4764-A044-F76505DFA836} 57 | {79F64E2D-7C95-4F8C-A260-D59481CB9649} = {7BC33F35-5693-4764-A044-F76505DFA836} 58 | {BE05620F-31DB-44A2-8433-DF0CD5E5C5F0} = {D8EDA4B2-174A-4F32-8307-71E256466903} 59 | {C80B04C4-D706-4E40-A2A5-3785483FDCE3} = {D8EDA4B2-174A-4F32-8307-71E256466903} 60 | {FDD409A4-117F-4432-A898-2FF406231C00} = {D8EDA4B2-174A-4F32-8307-71E256466903} 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {6971CE83-5820-4F64-8E7F-1DEAA6FAB808} 64 | EndGlobalSection 65 | EndGlobal 66 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/IntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Net.Mime; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Mvc.Testing; 9 | using Microsoft.Extensions.Hosting; 10 | using Xunit; 11 | 12 | namespace SuperTokens.AspNetCore 13 | { 14 | public class IntegrationTests : IClassFixture 15 | { 16 | private readonly TestWebApplicationFactory _factory; 17 | 18 | public IntegrationTests(TestWebApplicationFactory factory) => 19 | _factory = factory ?? throw new ArgumentNullException(nameof(factory)); 20 | 21 | [Fact] 22 | public async Task FunctionalTests() 23 | { 24 | using var client = _factory.CreateClient(); 25 | 26 | { 27 | using var loginResponse = await client.GetAsync("/api/login"); 28 | Assert.True(loginResponse.IsSuccessStatusCode); 29 | } 30 | 31 | { 32 | using var valuesResponse = await client.GetAsync("/api/values"); 33 | Assert.True(valuesResponse.IsSuccessStatusCode); 34 | } 35 | 36 | { 37 | using var refreshContent = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json); 38 | using var refreshResponse = await client.PostAsync("/auth/session/refresh", refreshContent); 39 | Assert.True(refreshResponse.IsSuccessStatusCode); 40 | } 41 | 42 | { 43 | using var signOutContent = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json); 44 | using var signOutResponse = await client.PostAsync("/auth/signout", signOutContent); 45 | Assert.True(signOutResponse.IsSuccessStatusCode); 46 | } 47 | } 48 | 49 | [Fact] 50 | public async Task RefreshTest() 51 | { 52 | using var client = _factory.CreateClient(); 53 | 54 | { 55 | using var loginResponse = await client.GetAsync("/api/login"); 56 | Assert.True(loginResponse.IsSuccessStatusCode); 57 | } 58 | 59 | { 60 | using var valuesResponse = await client.GetAsync("/api/values"); 61 | Assert.True(valuesResponse.IsSuccessStatusCode); 62 | } 63 | 64 | await Task.Delay(31000); 65 | 66 | { 67 | using var valuesResponse = await client.GetAsync("/api/values"); 68 | Assert.False(valuesResponse.IsSuccessStatusCode); 69 | } 70 | 71 | { 72 | using var refreshContent = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json); 73 | using var refreshResponse = await client.PostAsync("/auth/session/refresh", refreshContent); 74 | Assert.True(refreshResponse.IsSuccessStatusCode); 75 | } 76 | 77 | { 78 | using var valuesResponse = await client.GetAsync("/api/values"); 79 | Assert.True(valuesResponse.IsSuccessStatusCode); 80 | } 81 | 82 | { 83 | using var signOutContent = new StringContent("{}", Encoding.UTF8, MediaTypeNames.Application.Json); 84 | using var signOutResponse = await client.PostAsync("/auth/signout", signOutContent); 85 | Assert.True(signOutResponse.IsSuccessStatusCode); 86 | } 87 | } 88 | 89 | public class TestWebApplicationFactory : WebApplicationFactory 90 | { 91 | protected override IHost CreateHost(IHostBuilder builder) 92 | { 93 | builder.UseContentRoot(Directory.GetCurrentDirectory()); 94 | return base.CreateHost(builder); 95 | } 96 | 97 | protected override IHostBuilder CreateHostBuilder() => 98 | Host.CreateDefaultBuilder() 99 | .ConfigureWebHostDefaults(webBuilder => 100 | { 101 | webBuilder.UseStartup(); 102 | }); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/LoginController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using SuperTokens.Net; 6 | using SuperTokens.Net.SessionRecipe; 7 | 8 | namespace SuperTokens.AspNetCore 9 | { 10 | [ApiController] 11 | [Route("api/login")] 12 | public sealed class LoginController : ControllerBase 13 | { 14 | private readonly ICoreApiClient _coreApiClient; 15 | 16 | public LoginController(ICoreApiClient coreApiClient) => 17 | _coreApiClient = coreApiClient ?? throw new ArgumentNullException(nameof(coreApiClient)); 18 | 19 | [HttpGet] 20 | public async Task Login() 21 | { 22 | var document = JsonDocument.Parse("{}"); 23 | var emptyObject = document.RootElement.Clone(); 24 | document.Dispose(); 25 | 26 | var session = await _coreApiClient.CreateSessionAsync(null, null, new CreateSessionRequest 27 | { 28 | EnableAntiCsrf = false, 29 | UserDataInDatabase = emptyObject, 30 | UserDataInJwt = emptyObject, 31 | UserId = new Guid("97d44afb-e4ea-4feb-b25c-c3bc80c55249").ToString("D"), 32 | }); 33 | 34 | // https://github.com/supertokens/supertokens-node/blob/646c44f7a86536c37fee7279a68be26f7d15ae7b/lib/ts/recipe/session/utils.ts#L215 35 | this.Response.Cookies.Append(SuperTokensDefaults.AccessTokenCookieName, session.AccessToken.Token); 36 | this.Response.Cookies.Append(SuperTokensDefaults.IdRefreshTokenCookieName, session.IdRefreshToken.Token); 37 | this.Response.Cookies.Append(SuperTokensDefaults.RefreshTokenCookieName, session.RefreshToken.Token); 38 | return this.NoContent(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace SuperTokens.AspNetCore 7 | { 8 | public class Startup 9 | { 10 | public void ConfigureServices(IServiceCollection services) 11 | { 12 | // ---------- ASP.NET Core Authentication ---------- 13 | 14 | services 15 | .AddAuthentication("SuperTokens") 16 | .AddSuperTokens(options => 17 | { 18 | options.ApiDomain = "https://login.salad.com"; 19 | options.AppName = "Salad"; 20 | options.CoreAddress = "http://127.0.0.1:3567"; 21 | options.WebsiteDomain = "https://salad.com"; 22 | }); 23 | 24 | // ---------- ASP.NET Core MVC ---------- 25 | 26 | services 27 | .AddHttpContextAccessor() 28 | .AddControllersWithViews(); 29 | } 30 | 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.Use((context, next) => 39 | { 40 | if (!context.Response.Headers.ContainsKey("Referrer-Policy")) 41 | { 42 | context.Response.Headers.Add("Referrer-Policy", "no-referrer"); 43 | } 44 | 45 | if (!context.Response.Headers.ContainsKey("X-Content-Type-Options")) 46 | { 47 | context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 48 | } 49 | 50 | if (!context.Response.Headers.ContainsKey("X-Frame-Options")) 51 | { 52 | context.Response.Headers.Add("X-Frame-Options", "DENY"); 53 | } 54 | 55 | if (!context.Response.Headers.ContainsKey("X-Xss-Protection")) 56 | { 57 | context.Response.Headers.Add("X-Xss-Protection", "1; mode=block"); 58 | } 59 | 60 | return next(); 61 | }); 62 | 63 | app.UseRouting(); 64 | app.UseAuthentication(); 65 | app.UseAuthorization(); 66 | app.UseEndpoints(endpoints => 67 | { 68 | endpoints.MapControllers(); 69 | }); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/SuperTokens.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | false 6 | latest 7 | SuperTokens.Net 8 | net5.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace SuperTokens.AspNetCore 6 | { 7 | [ApiController] 8 | [Authorize] 9 | [Route("api/values")] 10 | public sealed class ValuesController : ControllerBase 11 | { 12 | [HttpGet] 13 | public ActionResult> Get() => 14 | new List 15 | { 16 | "One", 17 | "Two", 18 | "Three", 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/SuperTokens.AspNetCore.Tests/paket.references: -------------------------------------------------------------------------------- 1 | coverlet.collector 2 | Microsoft.AspNetCore.Mvc.Testing 3 | Microsoft.NET.Test.Sdk 4 | xunit 5 | xunit.runner.visualstudio -------------------------------------------------------------------------------- /test/SuperTokens.Net.Tests/FunctionalTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net.Http; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | using SuperTokens.Net.SessionRecipe; 7 | using Xunit; 8 | 9 | namespace SuperTokens.Net 10 | { 11 | public class FunctionalTests 12 | { 13 | [Fact] 14 | public async Task Functional() 15 | { 16 | using var httpClient = new HttpClient(); 17 | httpClient.BaseAddress = new Uri("https://try.supertokens.io", UriKind.Absolute); 18 | 19 | var client = new CoreApiClient(httpClient); 20 | string apiKey = null!; 21 | 22 | var apiVersions = await client.GetApiVersionAsync(apiKey); 23 | var cdiVersion = apiVersions.Versions.First(); 24 | 25 | var handshake = await client.GetHandshakeAsync(apiKey, cdiVersion); 26 | 27 | var emptyObjectJson = JsonDocument.Parse("{}"); 28 | var emptyObject = emptyObjectJson.RootElement.Clone(); 29 | emptyObjectJson.Dispose(); 30 | 31 | var requestObject = new CreateSessionRequest 32 | { 33 | EnableAntiCsrf = true, 34 | UserDataInDatabase = emptyObject, 35 | UserDataInJwt = emptyObject, 36 | UserId = new Guid("0de15609-26ce-4652-88de-aa184f40c7ac").ToString("D"), 37 | }; 38 | var requestContent = JsonSerializer.Serialize(requestObject); 39 | 40 | var session = await client.CreateSessionAsync(apiKey, cdiVersion, requestObject); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/SuperTokens.Net.Tests/SuperTokens.Net.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | false 6 | latest 7 | SuperTokens.Net 8 | net5.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/SuperTokens.Net.Tests/paket.references: -------------------------------------------------------------------------------- 1 | coverlet.collector 2 | Microsoft.NET.Test.Sdk 3 | xunit 4 | xunit.runner.visualstudio -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Controllers/SuperTokensController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Mime; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authentication; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Net.Http.Headers; 9 | using SuperTokens.AspNetCore; 10 | using SuperTokens.TestServer.Models; 11 | 12 | namespace SuperTokens.TestServer.Controllers 13 | { 14 | [ApiController] 15 | public class SuperTokensController : ControllerBase 16 | { 17 | private readonly Counters _counters; 18 | 19 | private readonly UpdateableSuperTokensOptions _optionsSource; 20 | 21 | private readonly ISessionAccessor _sessionAccessor; 22 | 23 | private readonly ISessionRecipe _sessionRecipe; 24 | 25 | public SuperTokensController(UpdateableSuperTokensOptions optionsSource, ISessionAccessor sessionAccessor, ISessionRecipe sessionRecipe, Counters counters) 26 | { 27 | _optionsSource = optionsSource ?? throw new ArgumentNullException(nameof(optionsSource)); 28 | _sessionAccessor = sessionAccessor ?? throw new ArgumentNullException(nameof(sessionAccessor)); 29 | _sessionRecipe = sessionRecipe ?? throw new ArgumentNullException(nameof(sessionRecipe)); 30 | _counters = counters ?? throw new ArgumentNullException(nameof(counters)); 31 | } 32 | 33 | [AllowAnonymous] 34 | [HttpPost("/beforeeach")] 35 | public ActionResult BeforeEach() => 36 | this.StatusCode(StatusCodes.Status200OK); 37 | 38 | [AllowAnonymous] 39 | [HttpPost("/checkAllowCredentials")] 40 | public ActionResult CheckAllowCredentials() => 41 | this.Content(!string.IsNullOrEmpty(this.Request.Headers["allow-credentials"]) ? "true" : "false", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 42 | 43 | [Authorize] 44 | [HttpGet("/check-rid")] 45 | public ActionResult CheckRid() => 46 | this.Content(string.IsNullOrEmpty(this.Request.Headers["rid"]) ? "fail" : "success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 47 | 48 | [Authorize] 49 | [HttpGet("/")] 50 | public ActionResult Get() => 51 | this.Content(_sessionAccessor.Session?.UserId ?? "", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 52 | 53 | [Authorize] 54 | [HttpGet("/update-jwt")] 55 | public ActionResult GetJwt() => 56 | this.Content(_sessionAccessor.Session?.UserDataInJwt ?? "", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 57 | 58 | [AllowAnonymous] 59 | [HttpGet("/getSessionCalledTime")] 60 | public ActionResult GetSessionCalledTime() => 61 | this.Content($"{_counters.NoOfTimesGetSessionCalledDuringTest:D}", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 62 | 63 | [AllowAnonymous] 64 | [HttpPost("/login")] 65 | public async Task> Login([FromBody] LoginRequest body) 66 | { 67 | var session = await _sessionRecipe.AuthenticateAsync(body.UserId); 68 | return this.Content(session.UserId, new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 69 | } 70 | 71 | [Authorize] 72 | [HttpPost("/logout")] 73 | public async Task> Logout() 74 | { 75 | await this.HttpContext.SignOutAsync(SuperTokensDefaults.AuthenticationScheme); 76 | return this.Content("success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 77 | } 78 | 79 | [AllowAnonymous] 80 | [HttpPost("/multipleInterceptors")] 81 | public ActionResult MultipleInterceptors() => 82 | this.Content(string.IsNullOrEmpty(this.Request.Headers["interceptorheader1"]) && string.IsNullOrEmpty(this.Request.Headers["interceptorheader2"]) ? "failure" : "success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 83 | 84 | [AllowAnonymous] 85 | [HttpGet("/ping")] 86 | public ActionResult Ping() => 87 | this.Content("success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 88 | 89 | [AllowAnonymous] 90 | [HttpGet("/refreshAttemptedTime")] 91 | public ActionResult RefreshAttemptedTime() => 92 | this.Content($"{_counters.NoOfTimesRefreshAttemptedDuringTest:D}", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 93 | 94 | [AllowAnonymous] 95 | [HttpGet("/refreshCalledTime")] 96 | public ActionResult RefreshCalledTime() => 97 | this.Content($"{_counters.NoOfTimesRefreshCalledDuringTest:D}", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 98 | 99 | [Authorize] 100 | [HttpPost("/revokeAll")] 101 | public ActionResult RevokeAll() 102 | { 103 | // TODO: Implement. 104 | throw new NotImplementedException(); 105 | } 106 | 107 | [AllowAnonymous] 108 | [HttpPost("/setAntiCsrf")] 109 | public ActionResult SetAntiCsrf([FromBody] SetAntiCsrfRequest body) 110 | { 111 | if (body.EnableAntiCsrf.HasValue) 112 | { 113 | if (body.EnableAntiCsrf.Value) 114 | { 115 | _optionsSource.AntiCsrfMode = SuperTokensAntiCsrfMode.ViaToken; 116 | } 117 | else 118 | { 119 | _optionsSource.AntiCsrfMode = SuperTokensAntiCsrfMode.None; 120 | } 121 | 122 | _optionsSource.FireChangeToken(); 123 | } 124 | 125 | return this.Content("success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 126 | } 127 | 128 | [AllowAnonymous] 129 | [HttpGet("/testError")] 130 | public ActionResult TestError() 131 | { 132 | var content = this.Content("test error message", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 133 | content.StatusCode = StatusCodes.Status500InternalServerError; 134 | return content; 135 | } 136 | 137 | [AllowAnonymous] 138 | [HttpGet("/testHeader")] 139 | public ActionResult TestHeader() => 140 | this.Ok(new 141 | { 142 | success = !string.IsNullOrEmpty(this.Request.Headers["st-custom-header"]), 143 | }); 144 | 145 | [AllowAnonymous] 146 | [HttpDelete("/testing")] 147 | [HttpGet("/testing")] 148 | [HttpHead("/testing")] 149 | [HttpPatch("/testing")] 150 | [HttpPost("/testing")] 151 | [HttpPut("/testing")] 152 | public ActionResult Testing() 153 | { 154 | var testHeader = this.Request.Headers["testing"]; 155 | if (!string.IsNullOrEmpty(testHeader)) 156 | { 157 | this.Response.Headers["testing"] = testHeader; 158 | } 159 | 160 | return this.Content("success", new MediaTypeHeaderValue(MediaTypeNames.Text.Plain)); 161 | } 162 | 163 | [AllowAnonymous] 164 | [HttpPost("/testUserConfig")] 165 | public ActionResult TestUserConfig() => 166 | this.StatusCode(StatusCodes.Status200OK); 167 | 168 | [Authorize] 169 | [HttpPost("/update-jwt")] 170 | public ActionResult UpdateJwt() 171 | { 172 | // TODO: Implement. 173 | throw new NotImplementedException(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Counters.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace SuperTokens.TestServer 4 | { 5 | public sealed class Counters 6 | { 7 | private int _noOfTimesGetSessionCalledDuringTest; 8 | private int _noOfTimesRefreshAttemptedDuringTest; 9 | private int _noOfTimesRefreshCalledDuringTest; 10 | 11 | public int NoOfTimesGetSessionCalledDuringTest => 12 | _noOfTimesGetSessionCalledDuringTest; 13 | 14 | public int NoOfTimesRefreshAttemptedDuringTest => 15 | _noOfTimesRefreshAttemptedDuringTest; 16 | 17 | public int NoOfTimesRefreshCalledDuringTest => 18 | _noOfTimesRefreshCalledDuringTest; 19 | 20 | public void IncrementNoOfTimesGetSessionCalledDuringTest() => 21 | Interlocked.Increment(ref _noOfTimesGetSessionCalledDuringTest); 22 | 23 | public void IncrementNoOfTimesRefreshAttemptedDuringTest() => 24 | Interlocked.Increment(ref _noOfTimesRefreshAttemptedDuringTest); 25 | 26 | public void IncrementNoOfTimesRefreshCalledDuringTest() => 27 | Interlocked.Increment(ref _noOfTimesRefreshCalledDuringTest); 28 | 29 | public void Reset() 30 | { 31 | Interlocked.Exchange(ref _noOfTimesGetSessionCalledDuringTest, 0); 32 | Interlocked.Exchange(ref _noOfTimesRefreshAttemptedDuringTest, 0); 33 | Interlocked.Exchange(ref _noOfTimesRefreshCalledDuringTest, 0); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/CountersMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace SuperTokens.TestServer 6 | { 7 | public class CountersMiddleware 8 | { 9 | private readonly Counters _counters; 10 | 11 | private readonly RequestDelegate _next; 12 | 13 | public CountersMiddleware(RequestDelegate next, Counters counters) 14 | { 15 | _next = next ?? throw new ArgumentNullException(nameof(next)); 16 | _counters = counters ?? throw new ArgumentNullException(nameof(counters)); 17 | } 18 | 19 | public async Task InvokeAsync(HttpContext context) 20 | { 21 | if (HttpMethods.IsPost(context.Request.Method) && context.Request.Path.Equals("/beforeeach")) 22 | { 23 | _counters.Reset(); 24 | } 25 | else if (HttpMethods.IsPost(context.Request.Method) && context.Request.Path.Equals("/auth/session/refresh")) 26 | { 27 | _counters.IncrementNoOfTimesRefreshAttemptedDuringTest(); 28 | } 29 | 30 | await _next(context); 31 | 32 | if (HttpMethods.IsGet(context.Request.Method) && context.Request.Path.Equals("/") && context.Response.StatusCode == StatusCodes.Status200OK) 33 | { 34 | _counters.IncrementNoOfTimesGetSessionCalledDuringTest(); 35 | } 36 | else if (HttpMethods.IsPost(context.Request.Method) && context.Request.Path.Equals("/auth/session/refresh") && context.Response.StatusCode == StatusCodes.Status200OK) 37 | { 38 | _counters.IncrementNoOfTimesRefreshCalledDuringTest(); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Models/LoginRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.TestServer.Models 4 | { 5 | public sealed class LoginRequest 6 | { 7 | [JsonPropertyName("userId")] 8 | public string UserId { get; set; } = null!; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Models/SetAntiCsrfRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace SuperTokens.TestServer.Models 4 | { 5 | public sealed class SetAntiCsrfRequest 6 | { 7 | [JsonPropertyName("enableAntiCsrf")] 8 | public bool? EnableAntiCsrf { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace SuperTokens.TestServer 5 | { 6 | public class Program 7 | { 8 | public static IHostBuilder CreateHostBuilder(string[] args) => 9 | Host.CreateDefaultBuilder(args) 10 | .ConfigureWebHostDefaults(webBuilder => 11 | { 12 | webBuilder.UseStartup(); 13 | }); 14 | 15 | public static void Main(string[] args) 16 | { 17 | CreateHostBuilder(args).Build().Run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Host : 8080": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": "true", 6 | "launchBrowser": false, 7 | "applicationUrl": "http://localhost:8080", 8 | "environmentVariables": { 9 | "ASPNETCORE_URLS": "http://*:8080", 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "Host : 8082": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": "true", 16 | "launchBrowser": false, 17 | "applicationUrl": "http://localhost:8082", 18 | "environmentVariables": { 19 | "ASPNETCORE_URLS": "http://*:8082", 20 | "ASPNETCORE_ENVIRONMENT": "Development", 21 | "SuperTokens__ApiDomain": "http://localhost.org:8082" 22 | } 23 | }, 24 | "WSL2 : 8080": { 25 | "commandName": "WSL2", 26 | "launchBrowser": false, 27 | "launchUrl": "http://localhost:8080", 28 | "environmentVariables": { 29 | "ASPNETCORE_URLS": "http://*:8080", 30 | "ASPNETCORE_ENVIRONMENT": "Development" 31 | }, 32 | "distributionName": "" 33 | }, 34 | "WSL2 : 8082": { 35 | "commandName": "WSL2", 36 | "launchBrowser": false, 37 | "launchUrl": "http://localhost:8082", 38 | "environmentVariables": { 39 | "ASPNETCORE_URLS": "http://*:8082", 40 | "ASPNETCORE_ENVIRONMENT": "Development", 41 | "SuperTokens__ApiDomain": "http://localhost.org:8082" 42 | }, 43 | "distributionName": "" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authorization; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Options; 10 | using Microsoft.Net.Http.Headers; 11 | using SuperTokens.AspNetCore; 12 | 13 | namespace SuperTokens.TestServer 14 | { 15 | public class Startup 16 | { 17 | private readonly IConfiguration _configuration; 18 | 19 | public Startup(IConfiguration configuration) => 20 | _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); 21 | 22 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 23 | { 24 | if (env.IsDevelopment()) 25 | { 26 | app.UseDeveloperExceptionPage(); 27 | } 28 | 29 | app.UseStaticFiles(); 30 | app.UseMiddleware(); 31 | app.UseRouting(); 32 | app.UseCors(); 33 | app.UseAuthentication(); 34 | app.UseAuthorization(); 35 | app.UseEndpoints(endpoints => 36 | { 37 | endpoints.MapControllers(); 38 | }); 39 | } 40 | 41 | public void ConfigureServices(IServiceCollection services) 42 | { 43 | // ---------- Options ---------- 44 | 45 | var updateableOptions = new UpdateableSuperTokensOptions(); 46 | _configuration.GetSection("SuperTokens").Bind(updateableOptions); 47 | services.AddSingleton(updateableOptions); 48 | services.AddSingleton>(updateableOptions); 49 | 50 | // ---------- ASP.NET Core Authentication ---------- 51 | 52 | services 53 | .AddAuthentication() 54 | .AddSuperTokens(updateableOptions.Configure); 55 | 56 | // ---------- ASP.NET Core Authorization ---------- 57 | 58 | services 59 | .AddAuthorization(options => 60 | { 61 | options.DefaultPolicy = new AuthorizationPolicyBuilder(SuperTokensDefaults.AuthenticationScheme) 62 | .RequireAuthenticatedUser() 63 | .Build(); 64 | }); 65 | 66 | // ---------- ASP.NET Core CORS ---------- 67 | 68 | if (!updateableOptions.ApiDomain.Equals(updateableOptions.WebsiteDomain)) 69 | { 70 | services.AddCors(options => 71 | { 72 | options.AddDefaultPolicy(policy => 73 | { 74 | policy.WithOrigins(updateableOptions.WebsiteDomain) 75 | .WithMethods( 76 | HttpMethods.Delete, 77 | HttpMethods.Get, 78 | HttpMethods.Post, 79 | HttpMethods.Put) 80 | .WithHeaders( 81 | HeaderNames.ContentType, 82 | SuperTokensDefaults.AntiCsrfHeaderKey, 83 | SuperTokensDefaults.FrontendDriverInterfaceHeaderKey, 84 | SuperTokensDefaults.RecipeIdHeaderKey) 85 | .AllowCredentials(); 86 | }); 87 | }); 88 | } 89 | 90 | // ---------- ASP.NET Core MVC ---------- 91 | 92 | services 93 | .AddHttpContextAccessor() 94 | .AddControllersWithViews(); 95 | 96 | // ---------- Custom Services ---------- 97 | 98 | services.AddSingleton(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/SuperTokens.TestServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | strict 6 | latest 7 | enable 8 | net5.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/UpdateableSuperTokensOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.Extensions.Primitives; 4 | using SuperTokens.AspNetCore; 5 | 6 | namespace SuperTokens.TestServer 7 | { 8 | public class UpdateableSuperTokensOptions : SuperTokensOptions, IOptionsChangeTokenSource 9 | { 10 | private CancellationTokenSource? _cancellationTokenSource; 11 | 12 | public string Name { get; } = SuperTokensDefaults.AuthenticationScheme; 13 | 14 | public void Configure(SuperTokensOptions options) 15 | { 16 | options.AntiCsrfMode = this.AntiCsrfMode; 17 | options.ApiBasePath = this.ApiBasePath; 18 | options.ApiDomain = this.ApiDomain; 19 | options.ApiGatewayPath = this.ApiGatewayPath; 20 | options.AppName = this.AppName; 21 | options.ClaimsIssuer = this.ClaimsIssuer; 22 | options.CookieDomain = this.CookieDomain; 23 | options.CookieSameSite = this.CookieSameSite; 24 | options.CookieSecure = this.CookieSecure; 25 | options.CoreAddress = this.CoreAddress; 26 | options.CoreApiKey = this.CoreApiKey; 27 | options.Events = this.Events; 28 | options.EventsType = this.EventsType; 29 | options.ForwardAuthenticate = this.ForwardAuthenticate; 30 | options.ForwardChallenge = this.ForwardChallenge; 31 | options.ForwardDefault = this.ForwardDefault; 32 | options.ForwardDefaultSelector = this.ForwardDefaultSelector; 33 | options.ForwardForbid = this.ForwardForbid; 34 | options.ForwardSignIn = this.ForwardSignIn; 35 | options.ForwardSignOut = this.ForwardSignOut; 36 | options.RefreshPath = this.RefreshPath; 37 | options.SignOutPath = this.SignOutPath; 38 | options.WebsiteBasePath = this.WebsiteBasePath; 39 | options.WebsiteDomain = this.WebsiteDomain; 40 | } 41 | 42 | public void FireChangeToken() 43 | { 44 | var cts = Interlocked.Exchange(ref _cancellationTokenSource, null); 45 | cts?.Cancel(); 46 | } 47 | 48 | public IChangeToken GetChangeToken() 49 | { 50 | var cts = LazyInitializer.EnsureInitialized(ref _cancellationTokenSource, () => new CancellationTokenSource()); 51 | return new CancellationChangeToken(cts.Token); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "SuperTokens": { 11 | "ApiDomain": "http://localhost.org:8080", 12 | "AppName": "SuperTokens", 13 | "CoreAddress": "http://localhost:9000", 14 | "WebsiteDomain": "http://localhost.org:8080" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/SuperTokens.TestServer/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | 47 | 48 | 49 | --------------------------------------------------------------------------------