diff --git a/.editorconfig b/.editorconfig index 770629e..8c6307f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ indent_style = space tab_width = 4 # New line preferences -end_of_line = crlf +end_of_line = lf insert_final_newline = false diff --git a/AspNetCore.JwtAuthentication.sln b/AspNetCore.JwtAuthentication.sln index e1ce8a8..b6af777 100644 --- a/AspNetCore.JwtAuthentication.sln +++ b/AspNetCore.JwtAuthentication.sln @@ -18,10 +18,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore5.0.Permissi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore5.0.WithCredentialsValidator", "Examples\Example.NetCore5.0.WithCredentialsValidator\Example.NetCore5.0.WithCredentialsValidator.csproj", "{E2BC2C97-161E-41E4-B224-DFC62AD56768}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.0.BaseAuthentication", "Examples\Example.NetCore3.0.BaseAuthentication\Example.NetCore3.0.BaseAuthentication.csproj", "{6AB1E6C4-563A-4447-8024-6C31C9A45141}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.1.BaseAuthentication", "Examples\Example.NetCore3.1.BaseAuthentication\Example.NetCore3.1.BaseAuthentication.csproj", "{43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore5.0.CookiesBasedAuthentication", "Examples\Example.NetCore5.0.CookiesBasedAuthentication\Example.NetCore5.0.CookiesBasedAuthentication.csproj", "{32E74378-AAF5-4CA6-8633-30997453E58A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthentication.Identity", "JwtAuthentication.Identity\JwtAuthentication.Identity.csproj", "{60A8C4FC-D6DC-4C5B-9FA9-6F81F55F3667}" @@ -38,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore6.0.BaseAuth EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetCore5.0", "NetCore5.0", "{7A925031-2656-4CEB-821A-D76739E0E5A2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetCore3.", "NetCore3.", "{614DB4C6-A4BE-4BBF-B385-C34985CA1FC3}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetCore6.0", "NetCore6.0", "{4FF83CB3-A634-4D07-BEE0-B5391B86F071}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore6.0.AuthenticationUsingIdentityUser", "Examples\Example.NetCore6.0.AuthenticationUsingIdentityUser\Example.NetCore6.0.AuthenticationUsingIdentityUser.csproj", "{28707296-5273-471B-89FB-FA3D6C62CA67}" @@ -60,6 +54,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore6.0.RefreshT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore5.0.RefreshTokenWithConfidenceInterval", "Examples\Example.NetCore5.0.RefreshTokenWithConfidenceInterval\Example.NetCore5.0.RefreshTokenWithConfidenceInterval.csproj", "{F0E28B79-7957-444F-B437-D2EEC072A6DE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JwtAuthentication.Shared", "JwtAuthentication.Shared\JwtAuthentication.Shared.csproj", "{E1952561-A7CD-45C3-B9D0-AC9B92A194CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{D7304D43-BEFB-418C-866B-BCDBF4944DAE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetCore3.1", "NetCore3.1", "{89FFB48D-FF56-4FC0-BB01-47855CD3A1A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.1.WithCredentialsValidator", "Examples\Core\NetCore3.1\Example.NetCore.3.1.WithCredentialsValidator\Example.NetCore3.1.WithCredentialsValidator.csproj", "{A092BCEE-7B25-4DFB-9DB7-D29399750095}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.1.AuthenticationWithRefreshToken", "Examples\Core\NetCore3.1\Example.NetCore3.1.AuthenticationWithRefreshToken\Example.NetCore3.1.AuthenticationWithRefreshToken.csproj", "{2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.1.BaseAuthentication", "Examples\Core\NetCore3.1\Example.NetCore3.1.BaseAuthentication\Example.NetCore3.1.BaseAuthentication.csproj", "{996BF68D-03EB-466D-8E79-CFEBD13D255D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.NetCore3.1.PermissionBasedAuthentication", "Examples\Core\NetCore3.1\Example.NetCore3.1.PermissionBasedAuthentication\Example.NetCore3.1.PermissionBasedAuthentication.csproj", "{47427766-B40C-4C8C-80F4-4EFD57AABBE0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -82,14 +90,6 @@ Global {E2BC2C97-161E-41E4-B224-DFC62AD56768}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2BC2C97-161E-41E4-B224-DFC62AD56768}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2BC2C97-161E-41E4-B224-DFC62AD56768}.Release|Any CPU.Build.0 = Release|Any CPU - {6AB1E6C4-563A-4447-8024-6C31C9A45141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AB1E6C4-563A-4447-8024-6C31C9A45141}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AB1E6C4-563A-4447-8024-6C31C9A45141}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AB1E6C4-563A-4447-8024-6C31C9A45141}.Release|Any CPU.Build.0 = Release|Any CPU - {43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC}.Release|Any CPU.Build.0 = Release|Any CPU {32E74378-AAF5-4CA6-8633-30997453E58A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32E74378-AAF5-4CA6-8633-30997453E58A}.Debug|Any CPU.Build.0 = Debug|Any CPU {32E74378-AAF5-4CA6-8633-30997453E58A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -154,6 +154,26 @@ Global {F0E28B79-7957-444F-B437-D2EEC072A6DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {F0E28B79-7957-444F-B437-D2EEC072A6DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0E28B79-7957-444F-B437-D2EEC072A6DE}.Release|Any CPU.Build.0 = Release|Any CPU + {E1952561-A7CD-45C3-B9D0-AC9B92A194CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1952561-A7CD-45C3-B9D0-AC9B92A194CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1952561-A7CD-45C3-B9D0-AC9B92A194CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1952561-A7CD-45C3-B9D0-AC9B92A194CC}.Release|Any CPU.Build.0 = Release|Any CPU + {A092BCEE-7B25-4DFB-9DB7-D29399750095}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A092BCEE-7B25-4DFB-9DB7-D29399750095}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A092BCEE-7B25-4DFB-9DB7-D29399750095}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A092BCEE-7B25-4DFB-9DB7-D29399750095}.Release|Any CPU.Build.0 = Release|Any CPU + {2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C}.Release|Any CPU.Build.0 = Release|Any CPU + {996BF68D-03EB-466D-8E79-CFEBD13D255D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {996BF68D-03EB-466D-8E79-CFEBD13D255D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {996BF68D-03EB-466D-8E79-CFEBD13D255D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {996BF68D-03EB-466D-8E79-CFEBD13D255D}.Release|Any CPU.Build.0 = Release|Any CPU + {47427766-B40C-4C8C-80F4-4EFD57AABBE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47427766-B40C-4C8C-80F4-4EFD57AABBE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47427766-B40C-4C8C-80F4-4EFD57AABBE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47427766-B40C-4C8C-80F4-4EFD57AABBE0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -162,8 +182,6 @@ Global {36F676C3-2C5F-4727-8BE2-8F759C2A3FBE} = {7A925031-2656-4CEB-821A-D76739E0E5A2} {51E0C282-0942-4367-95EB-F248E80A5D6F} = {7A925031-2656-4CEB-821A-D76739E0E5A2} {E2BC2C97-161E-41E4-B224-DFC62AD56768} = {7A925031-2656-4CEB-821A-D76739E0E5A2} - {6AB1E6C4-563A-4447-8024-6C31C9A45141} = {614DB4C6-A4BE-4BBF-B385-C34985CA1FC3} - {43D3813D-CD36-46A7-86EF-CD6CF0A9A9BC} = {614DB4C6-A4BE-4BBF-B385-C34985CA1FC3} {32E74378-AAF5-4CA6-8633-30997453E58A} = {7A925031-2656-4CEB-821A-D76739E0E5A2} {1DF1D001-0194-4CD8-82DD-40AF57FC1432} = {7A925031-2656-4CEB-821A-D76739E0E5A2} {12915347-991A-4EA0-983D-03121E1C527B} = {7A925031-2656-4CEB-821A-D76739E0E5A2} @@ -171,7 +189,6 @@ Global {EF8EF2D5-9483-400E-B938-ADDB467BF300} = {ADBF0A06-FF69-40A6-8F32-2F188AE1B6A4} {DDA26737-950C-4B23-B66A-1733FB482A29} = {4FF83CB3-A634-4D07-BEE0-B5391B86F071} {7A925031-2656-4CEB-821A-D76739E0E5A2} = {ADBF0A06-FF69-40A6-8F32-2F188AE1B6A4} - {614DB4C6-A4BE-4BBF-B385-C34985CA1FC3} = {ADBF0A06-FF69-40A6-8F32-2F188AE1B6A4} {4FF83CB3-A634-4D07-BEE0-B5391B86F071} = {ADBF0A06-FF69-40A6-8F32-2F188AE1B6A4} {28707296-5273-471B-89FB-FA3D6C62CA67} = {4FF83CB3-A634-4D07-BEE0-B5391B86F071} {5AE1CC8C-80A2-46AF-A02E-BE129080F489} = {4FF83CB3-A634-4D07-BEE0-B5391B86F071} @@ -182,6 +199,12 @@ Global {C617F7DD-6E7E-4B5D-8766-C82C37EFFF6E} = {4FF83CB3-A634-4D07-BEE0-B5391B86F071} {ABDD94F3-DF9E-47B2-813F-935C54C8DEA7} = {4FF83CB3-A634-4D07-BEE0-B5391B86F071} {F0E28B79-7957-444F-B437-D2EEC072A6DE} = {7A925031-2656-4CEB-821A-D76739E0E5A2} + {D7304D43-BEFB-418C-866B-BCDBF4944DAE} = {ADBF0A06-FF69-40A6-8F32-2F188AE1B6A4} + {89FFB48D-FF56-4FC0-BB01-47855CD3A1A0} = {D7304D43-BEFB-418C-866B-BCDBF4944DAE} + {A092BCEE-7B25-4DFB-9DB7-D29399750095} = {89FFB48D-FF56-4FC0-BB01-47855CD3A1A0} + {2B5AEDED-B2D5-45C1-865E-FEC5C62E7B6C} = {89FFB48D-FF56-4FC0-BB01-47855CD3A1A0} + {996BF68D-03EB-466D-8E79-CFEBD13D255D} = {89FFB48D-FF56-4FC0-BB01-47855CD3A1A0} + {47427766-B40C-4C8C-80F4-4EFD57AABBE0} = {89FFB48D-FF56-4FC0-BB01-47855CD3A1A0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C2026BF9-DACC-4EA4-AF04-B8A590EA38BF} diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/Controllers/ExampleController.cs b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Controllers/ExampleController.cs similarity index 90% rename from Examples/Example.NetCore3.0.BaseAuthentication/Controllers/ExampleController.cs rename to Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Controllers/ExampleController.cs index 3f6181a..bf8c3f1 100644 --- a/Examples/Example.NetCore3.0.BaseAuthentication/Controllers/ExampleController.cs +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Controllers/ExampleController.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Example.NetCore3._0.BaseAuthentication.Controllers +namespace Example.NetCore3._1.WithCredentialsValidator.Controllers { [ApiController] [Route("[controller]")] diff --git a/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/CustomUserCredentialsValidator.cs b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/CustomUserCredentialsValidator.cs new file mode 100644 index 0000000..93eda3c --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/CustomUserCredentialsValidator.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contracts; + +namespace Example.NetCore3._1.WithCredentialsValidator +{ + public class CustomUserCredentialsValidator : UserCredentialsValidator + { + private const string Login = "User"; + private const string Password = "User"; + + public override Task ValidateUserCredentials(string login, string password) + { + return Task.FromResult(login == Login && password == Password); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Example.NetCore3.1.WithCredentialsValidator.csproj b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Example.NetCore3.1.WithCredentialsValidator.csproj new file mode 100644 index 0000000..0b08dc0 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Example.NetCore3.1.WithCredentialsValidator.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + Example.NetCore._3._1.WithCredentialsValidator + + + + + + + + diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/Program.cs b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Program.cs similarity index 89% rename from Examples/Example.NetCore3.0.BaseAuthentication/Program.cs rename to Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Program.cs index a9c46c4..8095828 100644 --- a/Examples/Example.NetCore3.0.BaseAuthentication/Program.cs +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Program.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Example.NetCore3._0.BaseAuthentication +namespace Example.NetCore3._1.WithCredentialsValidator { public class Program { @@ -16,4 +16,4 @@ public static IHostBuilder CreateHostBuilder(string[] args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } } -} \ No newline at end of file +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Properties/launchSettings.json b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Properties/launchSettings.json new file mode 100644 index 0000000..9eba533 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:36212", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "example", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Example.NetCore3._1.WithCredentialsValidator": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "example", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/Startup.cs b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Startup.cs similarity index 79% rename from Examples/Example.NetCore3.0.BaseAuthentication/Startup.cs rename to Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Startup.cs index 810e4fa..4b60d25 100644 --- a/Examples/Example.NetCore3.0.BaseAuthentication/Startup.cs +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/Startup.cs @@ -6,7 +6,7 @@ using TourmalineCore.AspNetCore.JwtAuthentication.Core; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -namespace Example.NetCore3._0.BaseAuthentication +namespace Example.NetCore3._1.WithCredentialsValidator { public class Startup { @@ -19,7 +19,9 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { - services.AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()); + services + .AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()) + .AddUserCredentialValidator(); services.AddControllers(); } @@ -40,4 +42,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } -} \ No newline at end of file +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/appsettings.json b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/appsettings.json new file mode 100644 index 0000000..8e261c5 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore.3.1.WithCredentialsValidator/appsettings.json @@ -0,0 +1,6 @@ +{ + "AuthenticationOptions": { + "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", + "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU" + } +} \ No newline at end of file diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Controllers/ExampleController.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Controllers/ExampleController.cs new file mode 100644 index 0000000..b0c214b --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Controllers/ExampleController.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Example.NetCore3._1.AuthenticationWithRefreshToken.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ExampleController : ControllerBase + { + private static readonly string[] Summaries = + { + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching", + }; + + [Authorize] + [HttpGet] + public IEnumerable Get() + { + return Summaries; + } + } +} \ No newline at end of file diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Example.NetCore3.1.AuthenticationWithRefreshToken.csproj b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Example.NetCore3.1.AuthenticationWithRefreshToken.csproj new file mode 100644 index 0000000..030e1c5 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Example.NetCore3.1.AuthenticationWithRefreshToken.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + Example.NetCore3._1.AuthenticationWithRefreshToken + + + + + + + + diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Program.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Program.cs new file mode 100644 index 0000000..4876d06 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Example.NetCore3._1.AuthenticationWithRefreshToken +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + } +} diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/Properties/launchSettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Properties/launchSettings.json similarity index 66% rename from Examples/Example.NetCore3.0.BaseAuthentication/Properties/launchSettings.json rename to Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Properties/launchSettings.json index 20acd9c..da1a230 100644 --- a/Examples/Example.NetCore3.0.BaseAuthentication/Properties/launchSettings.json +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Properties/launchSettings.json @@ -1,21 +1,23 @@ -{ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:55870/", - "sslPort": 44324 + "applicationUrl": "http://localhost:36255", + "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "launchUrl": "weatherforecast", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Example.NetCore3._0.BaseAuthentication": { + "Example.NetCore3._1.AuthenticationWithRefreshToken": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { @@ -24,4 +26,4 @@ "applicationUrl": "http://localhost:5000" } } -} \ No newline at end of file +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Startup.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Startup.cs new file mode 100644 index 0000000..0e7d0f1 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/Startup.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using TourmalineCore.AspNetCore.JwtAuthentication.Core; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; + +namespace Example.NetCore3._1.AuthenticationWithRefreshToken +{ + public class Startup + { + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + services + .AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()) + .AddLoginWithRefresh(); + + services.AddControllers(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app + .UseDefaultLoginMiddleware() + .UseJwtAuthentication(); + + app.UseRefreshTokenMiddleware(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/appsettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/appsettings.json new file mode 100644 index 0000000..aeb23f9 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.AuthenticationWithRefreshToken/appsettings.json @@ -0,0 +1,7 @@ +{ + "AuthenticationOptions": { + "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", + "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", + "AccessTokenExpireInMinutes": 15 + } +} \ No newline at end of file diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/Controllers/ExampleController.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Controllers/ExampleController.cs similarity index 100% rename from Examples/Example.NetCore3.1.BaseAuthentication/Controllers/ExampleController.cs rename to Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Controllers/ExampleController.cs diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj new file mode 100644 index 0000000..25cae51 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + Example.NetCore3._1.BaseAuthentication + + + + + + + + diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/Program.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Program.cs similarity index 96% rename from Examples/Example.NetCore3.1.BaseAuthentication/Program.cs rename to Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Program.cs index 513feef..ed9f386 100644 --- a/Examples/Example.NetCore3.1.BaseAuthentication/Program.cs +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Program.cs @@ -1,19 +1,19 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace Example.NetCore3._1.BaseAuthentication -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } - } -} \ No newline at end of file +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Example.NetCore3._1.BaseAuthentication +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json new file mode 100644 index 0000000..d6b6957 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:31926", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "example", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Example.NetCore3._1.BaseAuthentication": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "example", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/Startup.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Startup.cs similarity index 97% rename from Examples/Example.NetCore3.1.BaseAuthentication/Startup.cs rename to Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Startup.cs index 311bcdd..03fb858 100644 --- a/Examples/Example.NetCore3.1.BaseAuthentication/Startup.cs +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/Startup.cs @@ -1,43 +1,43 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using TourmalineCore.AspNetCore.JwtAuthentication.Core; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -namespace Example.NetCore3._1.BaseAuthentication -{ - public class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()); - - services.AddControllers(); - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - +namespace Example.NetCore3._1.BaseAuthentication +{ + public class Startup + { + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()); + + services.AddControllers(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + app.UseRouting(); app .UseDefaultLoginMiddleware() - .UseJwtAuthentication(); - - app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); - } - } -} \ No newline at end of file + .UseJwtAuthentication(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/appsettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/appsettings.json new file mode 100644 index 0000000..d09d8c4 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.BaseAuthentication/appsettings.json @@ -0,0 +1,6 @@ +{ + "AuthenticationOptions": { + "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", + "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU" + } +} \ No newline at end of file diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Controllers/ExampleController.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Controllers/ExampleController.cs new file mode 100644 index 0000000..23b326d --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Controllers/ExampleController.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Filters; + +namespace Example.NetCore3._1.PermissionBasedAuthentication.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ExampleController : ControllerBase + { + private static readonly string[] Summaries = + { + "Freezing", + "Bracing", + "Chilly", + "Cool", + "Mild", + "Warm", + "Balmy", + "Hot", + "Sweltering", + "Scorching", + }; + + [Authorize] + [RequiresPermission(CustomUserClaimsProvider.FirstExampleClaimName)] + [HttpGet] + public IEnumerable Get() + { + return Summaries; + } + } +} \ No newline at end of file diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/CustomUserClaimsProvider.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/CustomUserClaimsProvider.cs new file mode 100644 index 0000000..1fa8743 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/CustomUserClaimsProvider.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contracts; + +namespace Example.NetCore3._1.PermissionBasedAuthentication +{ + public class CustomUserClaimsProvider : UserClaimsProvider + { + public const string ExampleClaimType = "ExamplePermission"; + + public const string FirstExampleClaimName = "CanUseExampleFirst"; + + public const string SecondExampleClaimName = "CanUseExampleSecond"; + + public override Task> GetUserClaimsAsync(string login) + { + return Task.FromResult(new List + { + new Claim(ExampleClaimType, FirstExampleClaimName), + new Claim(ExampleClaimType, SecondExampleClaimName), + } + ); + } + } +} \ No newline at end of file diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Example.NetCore3.1.PermissionBasedAuthentication.csproj b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Example.NetCore3.1.PermissionBasedAuthentication.csproj new file mode 100644 index 0000000..a2c6685 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Example.NetCore3.1.PermissionBasedAuthentication.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + Example.NetCore3._1.PermissionBasedAuthentication + + + + + + + + diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Program.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Program.cs new file mode 100644 index 0000000..f8e4c4e --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Program.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Example.NetCore3._1.PermissionBasedAuthentication +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Properties/launchSettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Properties/launchSettings.json new file mode 100644 index 0000000..5ac2db1 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:23693", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "example", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Example.NetCore3._1.PermissionBasedAuthentication": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "example", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Startup.cs b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Startup.cs new file mode 100644 index 0000000..1b62293 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/Startup.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using TourmalineCore.AspNetCore.JwtAuthentication.Core; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; + +namespace Example.NetCore3._1.PermissionBasedAuthentication +{ + public class Startup + { + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) + { + _configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + services + .AddJwtAuthentication(_configuration.GetSection(nameof(AuthenticationOptions)).Get()) + .WithUserClaimsProvider(CustomUserClaimsProvider.ExampleClaimType); + + services.AddControllers(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app + .UseDefaultLoginMiddleware() + .UseJwtAuthentication(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + } +} diff --git a/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/appsettings.json b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/appsettings.json new file mode 100644 index 0000000..d09d8c4 --- /dev/null +++ b/Examples/Core/NetCore3.1/Example.NetCore3.1.PermissionBasedAuthentication/appsettings.json @@ -0,0 +1,6 @@ +{ + "AuthenticationOptions": { + "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", + "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU" + } +} \ No newline at end of file diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/Example.NetCore3.0.BaseAuthentication.csproj b/Examples/Example.NetCore3.0.BaseAuthentication/Example.NetCore3.0.BaseAuthentication.csproj deleted file mode 100644 index 4f46280..0000000 --- a/Examples/Example.NetCore3.0.BaseAuthentication/Example.NetCore3.0.BaseAuthentication.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp3.0 - Example.NetCore3._0.BaseAuthentication - - - - - - - - - PreserveNewest - true - PreserveNewest - - - - diff --git a/Examples/Example.NetCore3.0.BaseAuthentication/appsettings.json b/Examples/Example.NetCore3.0.BaseAuthentication/appsettings.json deleted file mode 100644 index dc634db..0000000 --- a/Examples/Example.NetCore3.0.BaseAuthentication/appsettings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "AuthenticationOptions": { - "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", - "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU" - } -} \ No newline at end of file diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj b/Examples/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj deleted file mode 100644 index 3e2aab9..0000000 --- a/Examples/Example.NetCore3.1.BaseAuthentication/Example.NetCore3.1.BaseAuthentication.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - netcoreapp3.1 - Example.NetCore3._1.BaseAuthentication - - - - - - - - - PreserveNewest - true - PreserveNewest - - - - diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json b/Examples/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json deleted file mode 100644 index 5d24be0..0000000 --- a/Examples/Example.NetCore3.1.BaseAuthentication/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51478", - "sslPort": 44316 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Example.NetCore3._1.BaseAuthentication": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:5000" - } - } -} \ No newline at end of file diff --git a/Examples/Example.NetCore3.1.BaseAuthentication/appsettings.json b/Examples/Example.NetCore3.1.BaseAuthentication/appsettings.json deleted file mode 100644 index dc634db..0000000 --- a/Examples/Example.NetCore3.1.BaseAuthentication/appsettings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "AuthenticationOptions": { - "PublicSigningKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB", - "PrivateSigningKey": "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU" - } -} \ No newline at end of file diff --git a/Examples/Example.NetCore5.0.AuthenticationWithRefreshToken/Startup.cs b/Examples/Example.NetCore5.0.AuthenticationWithRefreshToken/Startup.cs index c47135a..a47c555 100644 --- a/Examples/Example.NetCore5.0.AuthenticationWithRefreshToken/Startup.cs +++ b/Examples/Example.NetCore5.0.AuthenticationWithRefreshToken/Startup.cs @@ -8,9 +8,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TourmalineCore.AspNetCore.JwtAuthentication.Core; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.Models; +//using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; namespace Example.NetCore5._0.AuthenticationWithRefreshToken diff --git a/Examples/Example.NetCore5.0.RefreshTokenAuthAndRegistrationUsingIdentity/Properties/launchSettings.json b/Examples/Example.NetCore5.0.RefreshTokenAuthAndRegistrationUsingIdentity/Properties/launchSettings.json index b73f2cf..2cf93d5 100644 --- a/Examples/Example.NetCore5.0.RefreshTokenAuthAndRegistrationUsingIdentity/Properties/launchSettings.json +++ b/Examples/Example.NetCore5.0.RefreshTokenAuthAndRegistrationUsingIdentity/Properties/launchSettings.json @@ -1,28 +1,28 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:52216", - "sslPort": 44377 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Example.NetCore5._0.RefreshTokenAuthAndRegistrationUsingIdentity": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51742/", + "sslPort": 44384 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Example.NetCore5._0.RefreshTokenAuthAndRegistrationUsingIdentity": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "dotnetRunMessages": "true" + } + } +} \ No newline at end of file diff --git a/Examples/Example.NetCore6.0.AuthenticationWithRefreshToken/Program.cs b/Examples/Example.NetCore6.0.AuthenticationWithRefreshToken/Program.cs index cf09a1c..595faf8 100644 --- a/Examples/Example.NetCore6.0.AuthenticationWithRefreshToken/Program.cs +++ b/Examples/Example.NetCore6.0.AuthenticationWithRefreshToken/Program.cs @@ -2,9 +2,9 @@ using Example.NetCore6._0.AuthenticationWithRefreshToken.Models; using Microsoft.EntityFrameworkCore; using TourmalineCore.AspNetCore.JwtAuthentication.Core; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; var builder = WebApplication.CreateBuilder(args); diff --git a/Examples/Tests/NetCore3.0/BasicAuthTests.cs b/Examples/Tests/NetCore3.1/BasicAuthTests.cs similarity index 91% rename from Examples/Tests/NetCore3.0/BasicAuthTests.cs rename to Examples/Tests/NetCore3.1/BasicAuthTests.cs index e3b32b2..da6e877 100644 --- a/Examples/Tests/NetCore3.0/BasicAuthTests.cs +++ b/Examples/Tests/NetCore3.1/BasicAuthTests.cs @@ -1,11 +1,11 @@ using System.Net; -using Example.NetCore3._0.BaseAuthentication; +using Example.NetCore3._1.BaseAuthentication; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; -namespace Tests.NetCore3._0 +namespace Tests.NetCore3._1 { - [Collection(nameof(Example.NetCore3._0.BaseAuthentication))] + [Collection(nameof(Example.NetCore3._1.BaseAuthentication))] public class BasicAuthTests : AuthTestsBase { diff --git a/Examples/Tests/NetCore5.0/LoginCallbackTests.cs b/Examples/Tests/NetCore5.0/LoginCallbackTests.cs index c2c9d06..19805ed 100644 --- a/Examples/Tests/NetCore5.0/LoginCallbackTests.cs +++ b/Examples/Tests/NetCore5.0/LoginCallbackTests.cs @@ -2,12 +2,13 @@ using Microsoft.AspNetCore.Http; using Moq; using Newtonsoft.Json; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract.Implementation; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services; using Xunit; namespace Tests.NetCore5._0 @@ -45,15 +46,14 @@ public LoginCallbackTests() _onLoginExecutedMock.Setup(p => p(It.IsAny())) .Returns(Task.CompletedTask); - _loginService = new LoginService(new TokenManager(new AuthenticationOptions - { - PrivateSigningKey = - "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", - }, - new DefaultUserClaimsProvider() - ), - new FakeUserCredentialValidator() - ); + var authenticationOptions = new AuthenticationOptions() + { + PrivateSigningKey = "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", + }; + + _loginService = new LoginService( + new TokenManager(authenticationOptions, new DefaultUserClaimsProvider(), new JwtTokenCreator(authenticationOptions)), + new FakeUserCredentialValidator()); } [Fact] diff --git a/Examples/Tests/NetCore6.0/LoginCallbackTests.cs b/Examples/Tests/NetCore6.0/LoginCallbackTests.cs index 79d1fba..dff0ea6 100644 --- a/Examples/Tests/NetCore6.0/LoginCallbackTests.cs +++ b/Examples/Tests/NetCore6.0/LoginCallbackTests.cs @@ -7,7 +7,8 @@ using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices; using Xunit; namespace Tests.NetCore6._0 @@ -45,15 +46,14 @@ public LoginCallbackTests() _onLoginExecutedMock.Setup(p => p(It.IsAny())) .Returns(Task.CompletedTask); - _loginService = new LoginService(new TokenManager(new AuthenticationOptions - { - PrivateSigningKey = - "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", - }, - new DefaultUserClaimsProvider() - ), - new FakeUserCredentialValidator() - ); + var authenticationOptions = new AuthenticationOptions() + { + PrivateSigningKey = "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", + }; + + _loginService = new LoginService( + new TokenManager(authenticationOptions, new DefaultUserClaimsProvider(), new JwtTokenCreator(authenticationOptions)), + new FakeUserCredentialValidator()); } [Fact] diff --git a/Examples/Tests/Tests.csproj b/Examples/Tests/Tests.csproj index 7a49897..b78e21e 100644 --- a/Examples/Tests/Tests.csproj +++ b/Examples/Tests/Tests.csproj @@ -24,7 +24,7 @@ - + @@ -36,10 +36,6 @@ - - - - PreserveNewest diff --git a/Examples/Tests/Units/JwtTokenCreatorTests.cs b/Examples/Tests/Units/JwtTokenCreatorTests.cs new file mode 100644 index 0000000..48ae0e4 --- /dev/null +++ b/Examples/Tests/Units/JwtTokenCreatorTests.cs @@ -0,0 +1,102 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using TourmalineCore.AspNetCore.JwtAuthentication.Core; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; +using Xunit; + +namespace Tests.Units; + +public class JwtTokenCreatorTests +{ + private readonly IJwtTokenCreator _jwtTokenCreator; + private const int TokenLifetimeInMinutes = 15; + + public JwtTokenCreatorTests() + { + var authenticationOptions = new BaseAuthenticationOptions() + { + PrivateSigningKey = "MIIEowIBAAKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABAoIBAQCvue/KV3p+Pex2tD8RxvDf13kfPtfOVkDlyfQw7HXwsuDXijctBfmJAEbRGzQQlHw2pmyuF3fl4DxTB4Qb1lz8FDniJoQHV0ijhgzrz7rfVffsevajKH/OX3gYjShM4GeBTqHhwWefiqZV21YtMFhrrLniq4N4FeAfeebNRg/zlWEigraxqAWb4cplnxBE3qOBECKXdF/B8uhp743BU/2HLSO5BUdhtPlN3FKoYdyqtrKyNO2z7rC+Gk8tNd+KbMHDUMiOQXzbXkpsXYKAug9iTW+gxZG/bNyzGNrJBFrUYb1fP4iZphbxBJgobNYJBKA565cAX/wI5lFakTBB0YAhAoGBAOk0TyV0dA8WJ6NrWmRUBKsKvkSREhBveW+P3LtA8a1IgQf4K6ohIfcq9w/+nRvTLPIxo67FcqEyzVUu9TOafzIi59w4RBWG/HKOZ5lvIVicbuPyclPVWyC+9bMMgWEJy9wGwE+fGh3AvAA4PXNBcjOqfT0sSF9PBUo5qN11Q/qHAoGBAMF2IL+cXgPiUta4XoMh14ksJiwHtZeMkj+kauU3rctDITSkIGMFp4q0W5UUSG1yPcW/++rMQfuAjCZotdNpbQT+g+KfG44DMT5W7nRgv60S0/6X/OoLIhCue19yLMVzFpai0YEH+s24/XNnwl53K34G1zVMCsZcIuIng8SZVintAoGAJP/1pr2pRFOBin4X418pNnIH6h0SPqVRIRA0N0mAjru4LSmE1ANZvjuE43bEOovwz6Rskegl3cmPpnpC0SMsFypOmzQaKUg3eX16lm95XPPE7EmlNgPd534kwXm0dU72lzxC+t8FZ78SlP5XUZgKpIPiRvhlqymAb1xinHBkjrUCgYAB144YRPTgNJd1U+wSc5AJzlHOuYQRHVWHJZme9RjChrEaPzXPu44M1ArLMJY/9IaCC4HqimdWbbLn6rdQfAB9u66lyb4JbB5b6Zf7o7Avha5fDjNqRxDb981U61Fhz+a3KHW2NM0+iDRhlOtU2u2fFZGXAFJZ8Saj4JxwksUvQQKBgEQ1TAW/INhWSkEW8vGeLnjV+rxOx8EJ9ftVCRaQMlDEDlX0n7BZeQrQ1pBxwL0FSTrUQdD02MsWshrhe0agKsw2Yaxn8gYs1v9HMloS4Q3L2zl8pi7R3yx72RIcdnS4rqGXeO5t8dm305Yz2RHhqtkBmpFBssSEYCY/tUDmsQVU", + }; + + _jwtTokenCreator = new JwtTokenCreator(authenticationOptions); + } + + [Fact] + public async Task CreateJwtToken_TokenTypeIsInvalid_CatchException() + { + var exception = await Record.ExceptionAsync(() => _jwtTokenCreator.CreateAsync("invalidTokenType", TokenLifetimeInMinutes)); + Assert.NotNull(exception); + Assert.Equal("Invalid token type value", exception.Message); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + public async Task CreateJwtToken_TokenLifetimeIsInvalid_CatchException(int tokenLifetimeInMinutes) + { + var exception = await Record.ExceptionAsync(() => _jwtTokenCreator.CreateAsync(TokenType.Access, tokenLifetimeInMinutes)); + Assert.NotNull(exception); + Assert.Equal("Token lifetime cannot be negative or zero", exception.Message); + } + + [Fact] + public async Task CreateAccessJwtToken() + { + var accessTokenModel = await _jwtTokenCreator.CreateAsync(TokenType.Access, TokenLifetimeInMinutes); + Assert.Equal(TokenType.Access, GetTokenTypeClaim(accessTokenModel).Value); + } + + [Fact] + public async Task CreateRefreshJwtToken() + { + var refreshTokenModel = await _jwtTokenCreator.CreateAsync(TokenType.Refresh, TokenLifetimeInMinutes); + Assert.Equal(TokenType.Refresh, GetTokenTypeClaim(refreshTokenModel).Value); + } + + [Fact] + public async Task CreateAccessJwtToken_TokenTypeClaimAlreadyAdded() + { + var claims = new List() + { + new Claim(Consts.TokenTypeClaimName, TokenType.Access) + }; + + var accessTokenModel = await _jwtTokenCreator.CreateAsync(TokenType.Access, TokenLifetimeInMinutes, claims); + Assert.Equal(TokenType.Access, GetTokenTypeClaim(accessTokenModel).Value); + } + + [Fact] + public async Task CreateAccessJwtToken_IfThereAreAdditionalClaims() + { + const string additionalClaimType = "ExamplePermission"; + const string additionalClaimValue = "ExampleClaimValue"; + + var claims = new List() + { + new Claim(additionalClaimType, additionalClaimValue), + }; + + var accessTokenModel = await _jwtTokenCreator.CreateAsync(TokenType.Access, TokenLifetimeInMinutes, claims); + + var exampleClaim = GetClaim(accessTokenModel.Value, additionalClaimType); + var tokenTypeClaim = GetTokenTypeClaim(accessTokenModel); + + Assert.Equal(additionalClaimValue, exampleClaim.Value); + Assert.Equal(TokenType.Access, tokenTypeClaim.Value); + } + + private Claim GetClaim(string tokenValue, string claimType) + { + var token = new JwtSecurityTokenHandler().ReadJwtToken(tokenValue); + + return token.Claims.SingleOrDefault(claim => claim.Type == claimType); + } + + private Claim GetTokenTypeClaim(BaseTokenModel tokenModel) + { + return GetClaim(tokenModel.Value, Consts.TokenTypeClaimName); + } +} \ No newline at end of file diff --git a/Examples/Tests/Units/JwtTokenValidatorTests.cs b/Examples/Tests/Units/JwtTokenValidatorTests.cs new file mode 100644 index 0000000..a64a78c --- /dev/null +++ b/Examples/Tests/Units/JwtTokenValidatorTests.cs @@ -0,0 +1,40 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices; +using Xunit; + +namespace Tests.Units; + +public class JwtTokenValidatorTests +{ + private readonly JwtTokenValidator _jwtTokenValidator; + + private const string PublicSigningKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQABMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsDwLnM5sbVi326YDsLvMkQLXDKVAaHrJZ/MwkoxF4Hmq4+pu4KojgQyVDtjseXG8UW5wbxW58eXG8V0XgJzsD8zQX2Z1bBawpIeD9sXf/5CFZGif85YFIqS3brqR3ScdGxYHXcwrUMGUCThxe918Q0aNXzdSxGGP2v7ZbtpFhLRyrTXHl4u6k3eyYG7zCkwextnMb9CJuCR7x1ua1V1S0xljAqg5PicFjt0vVSKzPM/Djw7XK84sJXxaet7t4cNtXVJIAyXUMsSli6gg9Cw9CEUSE40iWUR/6wrdUYAchk3vWiBhMmnufwzmFRLKHOH9Fz8buJVSrRfyt7a6S2iN+wIDAQAB"; + private const string ValidJwtToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IkFkbWluIiwiZXhwIjoxOTc1OTIyNzg5fQ.oOU4-XE1dCyVtViamBU5BgLy9x24LrcAV4cTUB_rwgdzLvyVuL2FdvEIBAsDR-uZwzaWC4VOGWYxjOVgu-Qvr2Td4sx-6p5DLJhrycsPFSrs4qU62wC4qRtCA-7s-hrumVh_fxh04UCC00crXvZUXnyHfAxC4nKIKiNAaLZ_inM3I8TYRuyBtOt-m0-K17qs5n-NMh_7lw_nBFB76pi1LuOUW6WDGV1bVfzSVD1daCaUc33pX5RQGeYsE-BlHPgTp5trueVz79-Kn1MtXYx_Xf-ltr7wGKWy1dLNNJoZewqDq-I0zqApDfmaO6Gy8xgZAykBx529DGj5KlMLx29yoQ"; + private const string InvalidJwtToken = "abJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IkFkbWluIiwiZXhwIjoxOTc1OTIyNzg5fQ.oOU4-XE1dCyVtViamBU5BgLy9x24LrcAV4cTUB_rwgdzLvyVuL2FdvEIBAsDR-uZwzaWC4VOGWYxjOVgu-Qvr2Td4sx-6p5DLJhrycsPFSrs4qU62wC4qRtCA-7s-hrumVh_fxh04UCC00crXvZUXnyHfAxC4nKIKiNAaLZ_inM3I8TYRuyBtOt-m0-K17qs5n-NMh_7lw_nBFB76pi1LuOUW6WDGV1bVfzSVD1daCaUc33pX5RQGeYsE-BlHPgTp5trueVz79-Kn1MtXYx_Xf-ltr7wGKWy1dLNNJoZewqDq-I0zqApDfmaO6Gy8xgZAykBx529DGj5KlMLx29yoQ"; + + public JwtTokenValidatorTests() + { + var authenticationOptions = new BaseAuthenticationOptions() + { + PublicSigningKey = PublicSigningKey + }; + + _jwtTokenValidator = new JwtTokenValidator(authenticationOptions); + } + + [Fact] + public async Task ValidateJwtToken_TokenIsValid_NoExceptions() + { + var exception = await Record.ExceptionAsync(() => _jwtTokenValidator.ValidateTokenAsync(ValidJwtToken)); + Assert.Null(exception); + } + + [Fact] + public async Task ValidateJwtToken_TokenIsInvalid_CatchExceptions() + { + var exception = await Record.ExceptionAsync(() => _jwtTokenValidator.ValidateTokenAsync(InvalidJwtToken)); + Assert.NotNull(exception); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/ApplicationBuilderExtension.cs b/JwtAuthentication.Core/ApplicationBuilderExtension.cs index 59276a5..497aee2 100644 --- a/JwtAuthentication.Core/ApplicationBuilderExtension.cs +++ b/JwtAuthentication.Core/ApplicationBuilderExtension.cs @@ -1,10 +1,12 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Middlewares.Refresh; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; namespace TourmalineCore.AspNetCore.JwtAuthentication.Core { @@ -17,9 +19,7 @@ public static class ApplicationBuilderExtension /// public static IApplicationBuilder UseJwtAuthentication(this IApplicationBuilder applicationBuilder) { - return applicationBuilder - .UseAuthentication() - .UseAuthorization(); + return Shared.ApplicationBuilderExtensions.UseJwtAuthentication(applicationBuilder); } /// @@ -30,10 +30,7 @@ public static IApplicationBuilder UseJwtAuthentication(this IApplicationBuilder /// public static IApplicationBuilder UseDefaultLoginMiddleware(this IApplicationBuilder applicationBuilder, LoginEndpointOptions loginEndpointOptions = null) { - Func defaultOnLoginCallback = s => Task.CompletedTask; - - return applicationBuilder - .UseMiddleware(loginEndpointOptions ?? new LoginEndpointOptions(), defaultOnLoginCallback, defaultOnLoginCallback); + return Shared.ApplicationBuilderExtensions.UseDefaultLoginMiddleware(applicationBuilder, loginEndpointOptions ?? new LoginEndpointOptions()); } /// @@ -42,11 +39,9 @@ public static IApplicationBuilder UseDefaultLoginMiddleware(this IApplicationBui /// /// /// - public static IApplicationBuilder UseCookieLoginMiddleware(this IApplicationBuilder applicationBuilder, CookieAuthOptions options, LoginEndpointOptions loginEndpointOptions = null) + public static IApplicationBuilder UseCookieLoginMiddleware(this IApplicationBuilder applicationBuilder, CookieAuthOptions cookieAuthOptions, LoginEndpointOptions loginEndpointOptions = null) { - return applicationBuilder - .UseMiddleware(options, loginEndpointOptions ?? new LoginEndpointOptions()) - .UseMiddleware(options); + return Shared.ApplicationBuilderExtensions.RegisterCookieLoginMiddleware(applicationBuilder, cookieAuthOptions, loginEndpointOptions ?? new LoginEndpointOptions()); } /// @@ -55,26 +50,66 @@ public static IApplicationBuilder UseCookieLoginMiddleware(this IApplicationBuil /// /// /// - public static IDefaultLoginMiddlewareBuilder OnLoginExecuting(this IApplicationBuilder applicationBuilder, Func callback) + public static IApplicationBuilder OnLoginExecuting(this IApplicationBuilder applicationBuilder, Func callback) { - return DefaultLoginMiddlewareBuilder + Func loginExecutingCallback = basicLoginModel => callback(LoginModel.MapFrom(basicLoginModel)); + + return Shared.ApplicationBuilderExtensions.OnLoginExecuting(applicationBuilder, loginExecutingCallback); + } + + /// + /// Registering a callback function to perform actions when the authentication ends. + /// + /// + /// + /// + public static IApplicationBuilder OnLoginExecuted(this IApplicationBuilder applicationBuilder, Func callback) + { + Func loginExecutedCallback = basicLoginModel => callback(LoginModel.MapFrom(basicLoginModel)); + + return Shared.ApplicationBuilderExtensions.OnLoginExecuted(applicationBuilder, loginExecutedCallback); + } + + /// + /// Adds middleware to handle incoming login and token refresh requests. + /// + /// + /// + /// + public static IApplicationBuilder UseRefreshTokenMiddleware(this IApplicationBuilder applicationBuilder, RefreshEndpointOptions endpointOptions = null) + { + Func defaultOnRefreshCallback = s => Task.CompletedTask; + + return applicationBuilder + .UseMiddleware(endpointOptions ?? new RefreshEndpointOptions(), defaultOnRefreshCallback, defaultOnRefreshCallback); + } + + /// + /// Registering a callback function to perform actions when the refresh starts. + /// + /// + /// + /// + public static IRefreshMiddlewareBuilder OnRefreshExecuting(this IApplicationBuilder applicationBuilder, Func callback) + { + return RefreshMiddlewareBuilder .GetInstance() .SetAppBuilder(applicationBuilder) - .OnLoginExecuting(callback); + .OnRefreshExecuting(callback); } /// - /// Registering a callback function to perform actions when the authentication ends. + /// Registering a callback function to perform actions when the refresh ends. /// /// /// /// - public static IDefaultLoginMiddlewareBuilder OnLoginExecuted(this IApplicationBuilder applicationBuilder, Func callback) + public static IRefreshMiddlewareBuilder OnRefreshExecuted(this IApplicationBuilder applicationBuilder, Func callback) { - return DefaultLoginMiddlewareBuilder + return RefreshMiddlewareBuilder .GetInstance() .SetAppBuilder(applicationBuilder) - .OnLoginExecuted(callback); + .OnRefreshExecuted(callback); } } } \ No newline at end of file diff --git a/JwtAuthentication.Core/AuthenticationExtensions.cs b/JwtAuthentication.Core/AuthenticationExtensions.cs index 8b5fce2..e29aad5 100644 --- a/JwtAuthentication.Core/AuthenticationExtensions.cs +++ b/JwtAuthentication.Core/AuthenticationExtensions.cs @@ -1,18 +1,13 @@ -using System; -using System.IdentityModel.Tokens.Jwt; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract.Implementation; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Filters; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Signing; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.TokenHandlers; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; using AuthenticationOptions = TourmalineCore.AspNetCore.JwtAuthentication.Core.Options.AuthenticationOptions; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Core { @@ -28,9 +23,7 @@ public static IServiceCollection AddJwtValidation( this IServiceCollection services, AuthenticationOptions authenticationOptions) { - services.AddJwtBearer(authenticationOptions); - - return services; + return Shared.AuthenticationExtensions.AddJwtValidation(services, authenticationOptions); } /// @@ -43,14 +36,7 @@ public static IServiceCollection AddJwtAuthentication( this IServiceCollection services, AuthenticationOptions authenticationOptions) { - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - services.AddJwtBearer(authenticationOptions); - - return services; + return Shared.AuthenticationExtensions.AddJwtAuthentication(services, authenticationOptions); } /// @@ -60,9 +46,9 @@ public static IServiceCollection AddJwtAuthentication( /// /// public static IServiceCollection AddUserCredentialValidator(this IServiceCollection services) - where TUserCredentialsValidator : IUserCredentialsValidator + where TUserCredentialsValidator : UserCredentialsValidator { - return services.AddTransient(typeof(IUserCredentialsValidator), typeof(TUserCredentialsValidator)); + return Shared.AuthenticationExtensions.AddUserCredentialValidator(services); } /// @@ -75,58 +61,24 @@ public static IServiceCollection AddUserCredentialValidator( this IServiceCollection services, string permissionClaimTypeKey = "Permission") - where TUserClaimsProvider : IUserClaimsProvider + where TUserClaimsProvider : UserClaimsProvider { RequiresPermission.ClaimType = permissionClaimTypeKey; - - return services.AddTransient(typeof(IUserClaimsProvider), typeof(TUserClaimsProvider)); + return Shared.AuthenticationExtensions.WithUserClaimsProvider(services); } - internal static void AddJwtBearer( + public static IServiceCollection AddLoginWithRefresh( this IServiceCollection services, - AuthenticationOptions authenticationOptions) + RefreshTokenOptions refreshTokenOptions = null) { - services.AddSingleton(authenticationOptions); - - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + services.AddSingleton(refreshTokenOptions ?? new RefreshTokenOptions()); - Func schemeSelector = context => null; + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); - var authBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme); - - if (authenticationOptions.IsDebugTokenEnabled) - { - authBuilder.AddScheme(DebugTokenHandler.Schema, - options => { } - ); - - schemeSelector = context => - { - string debugHeader = context.Request.Headers[DebugTokenHandler.HeaderName]; - - return string.IsNullOrWhiteSpace(debugHeader) == false - ? DebugTokenHandler.Schema - : null; - }; - } - - authBuilder - .AddJwtBearer( - options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateLifetime = true, - ValidateIssuer = false, - ValidateAudience = false, - ValidateIssuerSigningKey = true, - IssuerSigningKey = SigningHelper.GetPublicKey(authenticationOptions.PublicSigningKey), - ClockSkew = TimeSpan.Zero, - }; - - options.ForwardDefaultSelector = schemeSelector; - } - ); - } + return services; + } } } \ No newline at end of file diff --git a/JwtAuthentication.Core/Contracts/UserClaimsProvider.cs b/JwtAuthentication.Core/Contracts/UserClaimsProvider.cs new file mode 100644 index 0000000..8a4f25e --- /dev/null +++ b/JwtAuthentication.Core/Contracts/UserClaimsProvider.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contracts +{ + public abstract class UserClaimsProvider : IUserClaimsProvider + { + public abstract Task> GetUserClaimsAsync(string login); + } +} diff --git a/JwtAuthentication.Core/Contracts/UserCredentialsValidator.cs b/JwtAuthentication.Core/Contracts/UserCredentialsValidator.cs new file mode 100644 index 0000000..c88197f --- /dev/null +++ b/JwtAuthentication.Core/Contracts/UserCredentialsValidator.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contracts +{ + public abstract class UserCredentialsValidator : IUserCredentialsValidator + { + public abstract Task ValidateUserCredentials(string login, string password); + } +} diff --git a/JwtAuthentication.Core/ErrorHandling/AuthenticationException.cs b/JwtAuthentication.Core/ErrorHandling/AuthenticationException.cs deleted file mode 100644 index 15ee0c3..0000000 --- a/JwtAuthentication.Core/ErrorHandling/AuthenticationException.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling -{ - public class AuthenticationException : Exception - { - public AuthenticationException(ErrorTypes errorType) - { - ExceptionInfo = errorType; - } - - public ErrorTypes ExceptionInfo { get; } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/ErrorHandling/ErrorTypes.cs b/JwtAuthentication.Core/ErrorHandling/ErrorTypes.cs deleted file mode 100644 index 885b98c..0000000 --- a/JwtAuthentication.Core/ErrorHandling/ErrorTypes.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling -{ - public enum ErrorTypes - { - IncorrectLoginOrPassword = 0, - RefreshTokenNotFound, - IncorrectActivationLink, - DuplicateActivationLink, - IncorrectResetPasswordLink, - InvalidPasswordFormat, - UserNotFound, - ReCaptchaTokenIncorrect, - RefreshTokenOrFingerprintNotFound, - RefreshTokenIsNotInConfidenceInterval, - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/ErrorHandling/RegistrationException.cs b/JwtAuthentication.Core/ErrorHandling/RegistrationException.cs deleted file mode 100644 index 502933e..0000000 --- a/JwtAuthentication.Core/ErrorHandling/RegistrationException.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling -{ - public class RegistrationException : Exception - { - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Filters/RequiresPermission.cs b/JwtAuthentication.Core/Filters/RequiresPermission.cs index 3bb5d53..2c7f755 100644 --- a/JwtAuthentication.Core/Filters/RequiresPermission.cs +++ b/JwtAuthentication.Core/Filters/RequiresPermission.cs @@ -3,13 +3,13 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Utils; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Extensions; namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Filters { public class RequiresPermission : Attribute, IAuthorizationFilter { - internal static string ClaimType; + public static string ClaimType; private readonly List _permissions; diff --git a/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddleware.cs b/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddleware.cs new file mode 100644 index 0000000..cab19ce --- /dev/null +++ b/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Middlewares.Refresh +{ + internal class RefreshMiddleware : RequestMiddlewareBase + { + private readonly RefreshEndpointOptions _endpointOptions; + private readonly ILogger _logger; + + private readonly Func _onRefreshExecuting; + private readonly Func _onRefreshExecuted; + + public RefreshMiddleware( + RequestDelegate next, + RefreshEndpointOptions endpointOptions, + ILogger logger, + Func onRefreshExecuting, + Func onRefreshExecuted) + : base(next) + { + _endpointOptions = endpointOptions; + _logger = logger; + _onRefreshExecuting = onRefreshExecuting; + _onRefreshExecuted = onRefreshExecuted; + } + + public async Task InvokeAsync(HttpContext context, ICoreRefreshService refreshService) + { + await InvokeAsyncBase(context, refreshService, _endpointOptions.RefreshEndpointRoute); + } + + protected override async Task ExecuteServiceMethod( + CoreRefreshTokenRequestModel requestModel, + ICoreRefreshService service, + HttpContext context) + { + var result = new BaseTokenModel(); + + try + { + var contractRefreshModel = new RefreshModel + { + RefreshTokenValue = requestModel.RefreshTokenValue, + }; + + await _onRefreshExecuting(contractRefreshModel); + result = await service.RefreshAsync(contractRefreshModel.RefreshTokenValue); + await _onRefreshExecuted(contractRefreshModel); + } + catch (Exception ex) + { + context.Response.StatusCode = StatusCodes.Status409Conflict; + _logger.LogError(ex.ToString()); + throw new Exception(ex.Message); + } + + return result; + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddlewareBuilder.cs b/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddlewareBuilder.cs new file mode 100644 index 0000000..f736a2a --- /dev/null +++ b/JwtAuthentication.Core/Internal/Middlewares/Refresh/RefreshMiddlewareBuilder.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Middlewares.Refresh +{ + internal class RefreshMiddlewareBuilder : IRefreshMiddlewareBuilder + { + private static Func _onRefreshExecutingCallback = s => Task.CompletedTask; + private static Func _onRefreshExecutedCallback = s => Task.CompletedTask; + + private IApplicationBuilder _applicationBuilder; + + private static RefreshMiddlewareBuilder _instance; + + private RefreshMiddlewareBuilder() + { + } + + internal static RefreshMiddlewareBuilder GetInstance() + { + if (_instance != null) + { + return _instance; + } + + _instance = new RefreshMiddlewareBuilder(); + + return _instance; + } + + internal IRefreshMiddlewareBuilder SetAppBuilder(IApplicationBuilder applicationBuilder) + { + _applicationBuilder = applicationBuilder; + return this; + } + + /// + /// Registering a callback function to perform actions when when the refresh starts. + /// + /// + /// + public IRefreshMiddlewareBuilder OnRefreshExecuting(Func callback) + { + _onRefreshExecutingCallback = callback; + return this; + } + + /// + /// Registering a callback function to perform actions when the refresh ends. + /// + /// + /// + public IRefreshMiddlewareBuilder OnRefreshExecuted(Func callback) + { + _onRefreshExecutedCallback = callback; + return this; + } + + /// + /// Adds middleware to handle incoming logout requests. + /// + /// + /// + public IApplicationBuilder UseRefreshMiddleware(RefreshEndpointOptions refreshEndpointOptions = null) + { + return _applicationBuilder + .UseMiddleware(refreshEndpointOptions ?? new RefreshEndpointOptions(), _onRefreshExecutingCallback, _onRefreshExecutedCallback); + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Internal/Models/Requests/CoreRefreshTokenRequestModel.cs b/JwtAuthentication.Core/Internal/Models/Requests/CoreRefreshTokenRequestModel.cs new file mode 100644 index 0000000..527bb15 --- /dev/null +++ b/JwtAuthentication.Core/Internal/Models/Requests/CoreRefreshTokenRequestModel.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Models.Requests +{ + internal class CoreRefreshTokenRequestModel + { + public string RefreshTokenValue { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshService.cs b/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshService.cs new file mode 100644 index 0000000..90c59bf --- /dev/null +++ b/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts +{ + internal interface ICoreRefreshService + { + public Task RefreshAsync(string jwtRefreshToken); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshTokenManager.cs b/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshTokenManager.cs new file mode 100644 index 0000000..dfc6b8c --- /dev/null +++ b/JwtAuthentication.Core/Internal/Services/Contracts/ICoreRefreshTokenManager.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts +{ + internal interface ICoreRefreshTokenManager + { + Task GenerateRefreshTokenAsync(); + } +} diff --git a/JwtAuthentication.Core/Internal/Services/CoreRefreshService.cs b/JwtAuthentication.Core/Internal/Services/CoreRefreshService.cs new file mode 100644 index 0000000..e9870fb --- /dev/null +++ b/JwtAuthentication.Core/Internal/Services/CoreRefreshService.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services +{ + internal class CoreRefreshService : ICoreRefreshService + { + private readonly IJwtTokenValidator _jwtTokenValidator; + private readonly IJwtTokenCreator _jwtTokenCreator; + private readonly RefreshTokenOptions _refreshTokenOptions; + + public CoreRefreshService( + IJwtTokenValidator jwtTokenValidator, + IJwtTokenCreator jwtTokenCreator, + RefreshTokenOptions refreshTokenOptions) + { + _jwtTokenValidator = jwtTokenValidator; + _jwtTokenCreator = jwtTokenCreator; + _refreshTokenOptions = refreshTokenOptions; + } + + public async Task RefreshAsync(string tokenValue) + { + try + { + await _jwtTokenValidator.ValidateTokenAsync(tokenValue); + await _jwtTokenValidator.ValidateTokenTypeAsync(tokenValue, TokenType.Refresh); + + return await _jwtTokenCreator.CreateAsync(TokenType.Refresh, _refreshTokenOptions.RefreshTokenExpireInMinutes); + } + catch (Exception) + { + throw new InvalidJwtTokenException(); + } + } + } +} diff --git a/JwtAuthentication.Core/Internal/Services/CoreRefreshTokenManager.cs b/JwtAuthentication.Core/Internal/Services/CoreRefreshTokenManager.cs new file mode 100644 index 0000000..2e3fec3 --- /dev/null +++ b/JwtAuthentication.Core/Internal/Services/CoreRefreshTokenManager.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services +{ + internal class CoreRefreshTokenManager : ICoreRefreshTokenManager + { + private readonly IJwtTokenCreator _jwtTokenCreator; + private readonly RefreshTokenOptions _refreshTokenOptions; + + public CoreRefreshTokenManager(IJwtTokenCreator jwtTokenCreator, RefreshTokenOptions refreshTokenOptions) + { + _jwtTokenCreator = jwtTokenCreator; + _refreshTokenOptions = refreshTokenOptions; + } + + public Task GenerateRefreshTokenAsync() + { + return _jwtTokenCreator.CreateAsync(TokenType.Refresh, _refreshTokenOptions.RefreshTokenExpireInMinutes); + } + } +} diff --git a/JwtAuthentication.Core/Internal/Services/LoginWithRefreshService.cs b/JwtAuthentication.Core/Internal/Services/LoginWithRefreshService.cs new file mode 100644 index 0000000..bf644e4 --- /dev/null +++ b/JwtAuthentication.Core/Internal/Services/LoginWithRefreshService.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Internal.Services +{ + internal class LoginWithRefreshService : LoginService + { + private readonly ITokenManager _tokenManager; + private readonly ICoreRefreshTokenManager _refreshTokenManager; + + public LoginWithRefreshService( + ITokenManager tokenManager, + ICoreRefreshTokenManager refreshTokenManager, + IUserCredentialsValidator userCredentialsValidator = null) + : base(tokenManager, userCredentialsValidator) + { + _tokenManager = tokenManager; + _refreshTokenManager = refreshTokenManager; + } + + public override async Task LoginAsync(LoginRequestModel model) + { + await base.ValidateCredentials(model); + + return new AuthResponseModel + { + AccessToken = await _tokenManager.GenerateAccessTokenAsync(model.Login), + RefreshToken = await _refreshTokenManager.GenerateRefreshTokenAsync(), + }; + } + } +} diff --git a/JwtAuthentication.Core/JwtAuthentication.Core.csproj b/JwtAuthentication.Core/JwtAuthentication.Core.csproj index 058af84..e3f96cf 100644 --- a/JwtAuthentication.Core/JwtAuthentication.Core.csproj +++ b/JwtAuthentication.Core/JwtAuthentication.Core.csproj @@ -1,9 +1,9 @@  - netcoreapp3.0;netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0 TourmalineCore.AspNetCore.JwtAuthentication.Core - 0.3.4-alpha.2 + 0.3.4-alpha.7 Koval Maxim, Nikita Medvedev, Aleksandr Shinkarev Tourmaline Core JwtAuthentication @@ -33,7 +33,7 @@ - @@ -44,8 +44,12 @@ - - + + + + true + JwtAuthentication.Shared.dll + diff --git a/JwtAuthentication.Core/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs b/JwtAuthentication.Core/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs deleted file mode 100644 index 993eb20..0000000 --- a/JwtAuthentication.Core/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login -{ - public class DefaultLoginMiddlewareBuilder : IDefaultLoginMiddlewareBuilder - { - private static Func _onLoginExecutingCallback = s => Task.CompletedTask; - private static Func _onLoginExecutedCallback = s => Task.CompletedTask; - - private IApplicationBuilder _applicationBuilder; - - private static DefaultLoginMiddlewareBuilder _instance; - - private DefaultLoginMiddlewareBuilder() - { - } - - internal static DefaultLoginMiddlewareBuilder GetInstance() - { - if (_instance != null) - { - return _instance; - } - - _instance = new DefaultLoginMiddlewareBuilder(); - - return _instance; - } - - internal IDefaultLoginMiddlewareBuilder SetAppBuilder(IApplicationBuilder applicationBuilder) - { - _applicationBuilder = applicationBuilder; - return this; - } - - /// - /// Registering a callback function to perform actions when when the authentication starts. - /// - /// - /// - public IDefaultLoginMiddlewareBuilder OnLoginExecuting(Func callback) - { - _onLoginExecutingCallback = callback; - return this; - } - - /// - /// Registering a callback function to perform actions when the authentication ends. - /// - /// - /// - public IDefaultLoginMiddlewareBuilder OnLoginExecuted(Func callback) - { - _onLoginExecutedCallback = callback; - return this; - } - - /// - /// Adds middleware to handle incoming login requests. - /// - /// - /// - public IApplicationBuilder UseDefaultLoginMiddleware(LoginEndpointOptions loginEndpointOptions = null) - { - return _applicationBuilder - .UseMiddleware(loginEndpointOptions ?? new LoginEndpointOptions(), _onLoginExecutingCallback, _onLoginExecutedCallback); - } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Middlewares/Login/IDefaultLoginMiddlewareBuilder.cs b/JwtAuthentication.Core/Middlewares/Login/IDefaultLoginMiddlewareBuilder.cs deleted file mode 100644 index 58b90bd..0000000 --- a/JwtAuthentication.Core/Middlewares/Login/IDefaultLoginMiddlewareBuilder.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login -{ - public interface IDefaultLoginMiddlewareBuilder - { - public IDefaultLoginMiddlewareBuilder OnLoginExecuting(Func callback); - - public IDefaultLoginMiddlewareBuilder OnLoginExecuted(Func callback); - - public IApplicationBuilder UseDefaultLoginMiddleware(LoginEndpointOptions loginEndpointOptions = null); - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Middlewares/Login/LoginMiddleware.cs b/JwtAuthentication.Core/Middlewares/Login/LoginMiddleware.cs deleted file mode 100644 index 4362437..0000000 --- a/JwtAuthentication.Core/Middlewares/Login/LoginMiddleware.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login -{ - internal class LoginMiddleware : RequestMiddlewareBase - { - private readonly LoginEndpointOptions _loginEndpointOptions; - - private readonly Func _onLoginExecuting; - private readonly Func _onLoginExecuted; - - public LoginMiddleware(RequestDelegate next, LoginEndpointOptions loginEndpointOptions, Func onLoginExecuting, Func onLoginExecuted) - : base(next) - { - _loginEndpointOptions = loginEndpointOptions; - _onLoginExecuting = onLoginExecuting; - _onLoginExecuted = onLoginExecuted; - } - - public async Task InvokeAsync(HttpContext context, ILoginService loginService) - { - await InvokeAsyncBase(context, loginService, _loginEndpointOptions.LoginEndpointRoute); - } - - protected override async Task ExecuteServiceMethod( - LoginRequestModel requestModel, - ILoginService service, - HttpContext context) - { - var result = new AuthResponseModel(); - - try - { - var contractLoginModel = new LoginModel - { - Login = requestModel.Login, - Password = requestModel.Password, - ClientFingerPrint = requestModel.ClientFingerPrint, - }; - - await _onLoginExecuting(contractLoginModel); - result = await service.LoginAsync(requestModel); - await _onLoginExecuted(contractLoginModel); - } - catch (AuthenticationException) - { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; - } - - return result; - } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Middlewares/Login/Models/LoginModel.cs b/JwtAuthentication.Core/Middlewares/Login/Models/LoginModel.cs index 12a6725..0e9c17b 100644 --- a/JwtAuthentication.Core/Middlewares/Login/Models/LoginModel.cs +++ b/JwtAuthentication.Core/Middlewares/Login/Models/LoginModel.cs @@ -1,11 +1,21 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Login.Models { - public class LoginModel + public class LoginModel : IBasicLoginModel { public string Login { get; set; } - public string Password { get; set; } - public string ClientFingerPrint { get; set; } + + public static LoginModel MapFrom(BasicLoginModel basicLoginModel) + { + return new LoginModel + { + Login = basicLoginModel.Login, + Password = basicLoginModel.Password, + ClientFingerPrint = basicLoginModel.ClientFingerPrint + }; + } } } \ No newline at end of file diff --git a/JwtAuthentication.Core/Middlewares/Refresh/IRefreshMiddlewareBuilder.cs b/JwtAuthentication.Core/Middlewares/Refresh/IRefreshMiddlewareBuilder.cs new file mode 100644 index 0000000..4a1acd7 --- /dev/null +++ b/JwtAuthentication.Core/Middlewares/Refresh/IRefreshMiddlewareBuilder.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh +{ + public interface IRefreshMiddlewareBuilder + { + public IRefreshMiddlewareBuilder OnRefreshExecuting(Func callback); + + public IRefreshMiddlewareBuilder OnRefreshExecuted(Func callback); + + public IApplicationBuilder UseRefreshMiddleware(RefreshEndpointOptions refreshEndpointOptions = null); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Middlewares/Refresh/Models/RefreshModel.cs b/JwtAuthentication.Core/Middlewares/Refresh/Models/RefreshModel.cs new file mode 100644 index 0000000..3c8ee57 --- /dev/null +++ b/JwtAuthentication.Core/Middlewares/Refresh/Models/RefreshModel.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares.Refresh.Models +{ + public class RefreshModel + { + public string RefreshTokenValue { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Models/Response/AuthResponseModel.cs b/JwtAuthentication.Core/Models/Response/AuthResponseModel.cs deleted file mode 100644 index bf1599e..0000000 --- a/JwtAuthentication.Core/Models/Response/AuthResponseModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] -[assembly: InternalsVisibleTo("Tests")] - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response -{ - internal class AuthResponseModel - { - public TokenModel AccessToken { get; set; } - - public TokenModel RefreshToken { get; set; } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Models/TokenModel.cs b/JwtAuthentication.Core/Models/TokenModel.cs deleted file mode 100644 index 42b132c..0000000 --- a/JwtAuthentication.Core/Models/TokenModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models -{ - internal class TokenModel - { - public string Value { get; set; } - - public DateTime ExpiresInUtc { get; set; } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Options/AuthenticationOptions.cs b/JwtAuthentication.Core/Options/AuthenticationOptions.cs index 176ca3f..9c8fbca 100644 --- a/JwtAuthentication.Core/Options/AuthenticationOptions.cs +++ b/JwtAuthentication.Core/Options/AuthenticationOptions.cs @@ -1,6 +1,8 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Options { - public class AuthenticationOptions + public class AuthenticationOptions : IAuthenticationOptions { private int _accessTokenExpireInMinutes; diff --git a/JwtAuthentication.Core/Options/CookieAuthOptions.cs b/JwtAuthentication.Core/Options/CookieAuthOptions.cs index 5199f37..a57de4c 100644 --- a/JwtAuthentication.Core/Options/CookieAuthOptions.cs +++ b/JwtAuthentication.Core/Options/CookieAuthOptions.cs @@ -1,6 +1,8 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Options { - public class CookieAuthOptions + public class CookieAuthOptions : ICookieAuthOptions { public string Key { get; set; } } diff --git a/JwtAuthentication.Core/Options/LoginEndpointOptions.cs b/JwtAuthentication.Core/Options/LoginEndpointOptions.cs index 48896c6..31fd3f8 100644 --- a/JwtAuthentication.Core/Options/LoginEndpointOptions.cs +++ b/JwtAuthentication.Core/Options/LoginEndpointOptions.cs @@ -1,6 +1,8 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Options { - public class LoginEndpointOptions + public class LoginEndpointOptions : ILoginEndpointOptions { private string _loginEndpointRoute; diff --git a/JwtAuthentication.Core/Options/RefreshEndpointOptions.cs b/JwtAuthentication.Core/Options/RefreshEndpointOptions.cs new file mode 100644 index 0000000..18aa7eb --- /dev/null +++ b/JwtAuthentication.Core/Options/RefreshEndpointOptions.cs @@ -0,0 +1,15 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Options +{ + public class RefreshEndpointOptions : IRefreshEndpointOptions + { + private string _refreshEndpointRoute; + + public string RefreshEndpointRoute + { + get => _refreshEndpointRoute ?? "/auth/refresh"; + set => _refreshEndpointRoute = value; + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Options/RefreshTokenOptions.cs b/JwtAuthentication.Core/Options/RefreshTokenOptions.cs new file mode 100644 index 0000000..a661b11 --- /dev/null +++ b/JwtAuthentication.Core/Options/RefreshTokenOptions.cs @@ -0,0 +1,13 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Options +{ + public class RefreshTokenOptions + { + private int _refreshTokenExpireInMinutes; + + public int RefreshTokenExpireInMinutes + { + get => _refreshTokenExpireInMinutes == default ? 10080 : _refreshTokenExpireInMinutes; + set => _refreshTokenExpireInMinutes = value; + } + } +} diff --git a/JwtAuthentication.Core/Services/ILoginService.cs b/JwtAuthentication.Core/Services/ILoginService.cs deleted file mode 100644 index 9a2493d..0000000 --- a/JwtAuthentication.Core/Services/ILoginService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; - -[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services -{ - internal interface ILoginService - { - public Task LoginAsync(LoginRequestModel model); - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Services/IRegistrationService.cs b/JwtAuthentication.Core/Services/IRegistrationService.cs deleted file mode 100644 index 379595d..0000000 --- a/JwtAuthentication.Core/Services/IRegistrationService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services -{ - internal interface IRegistrationService - where TUser : class - where TRegistrationRequestModel : RegistrationRequestModel - { - Task RegisterAsync(TRegistrationRequestModel model, Func mapping); - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Services/ITokenManager.cs b/JwtAuthentication.Core/Services/ITokenManager.cs deleted file mode 100644 index a3cd451..0000000 --- a/JwtAuthentication.Core/Services/ITokenManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services -{ - internal interface ITokenManager - { - Task GetAccessToken(string login); - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Services/Implementation/LoginService.cs b/JwtAuthentication.Core/Services/Implementation/LoginService.cs deleted file mode 100644 index a186288..0000000 --- a/JwtAuthentication.Core/Services/Implementation/LoginService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation -{ - internal class LoginService : ILoginService - { - private readonly ITokenManager _tokenManager; - - private readonly IUserCredentialsValidator _userCredentialsValidator; - - - public LoginService( - ITokenManager tokenManager, - IUserCredentialsValidator userCredentialsValidator = null) - { - _tokenManager = tokenManager; - _userCredentialsValidator = userCredentialsValidator; - } - - public async Task LoginAsync(LoginRequestModel model) - { - var isUserCredentialsValid = await _userCredentialsValidator.ValidateUserCredentials(model.Login, model.Password); - - if (!isUserCredentialsValid) - { - throw new AuthenticationException(ErrorTypes.IncorrectLoginOrPassword); - } - - var token = await _tokenManager.GetAccessToken(model.Login); - - return new AuthResponseModel - { - AccessToken = new TokenModel - { - Value = token.Value, - ExpiresInUtc = token.ExpiresInUtc, - }, - }; - } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Core/Services/Implementation/TokenManager.cs b/JwtAuthentication.Core/Services/Implementation/TokenManager.cs deleted file mode 100644 index 1dcaf31..0000000 --- a/JwtAuthentication.Core/Services/Implementation/TokenManager.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IdentityModel.Tokens.Jwt; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Microsoft.IdentityModel.Tokens; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Signing; - -[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation -{ - internal class TokenManager : ITokenManager - { - private readonly AuthenticationOptions _options; - private readonly IUserClaimsProvider _userClaimsProvider; - - public TokenManager( - AuthenticationOptions options, - IUserClaimsProvider userClaimsProvider) - { - _options = options; - _userClaimsProvider = userClaimsProvider; - } - - public async Task GetAccessToken(string login) - { - var claims = await _userClaimsProvider.GetUserClaimsAsync(login); - var privateKey = SigningHelper.GetPrivateKey(_options.PrivateSigningKey); - var credentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256); - var expires = DateTime.UtcNow.AddMinutes(_options.AccessTokenExpireInMinutes); - - var token = new JwtSecurityToken(_options.Issuer, - _options.Audience, - claims, - expires: expires, - signingCredentials: credentials - ); - - var tokenValue = new JwtSecurityTokenHandler().WriteToken(token); - - return await Task.FromResult(new TokenModel - { - Value = tokenValue, - ExpiresInUtc = expires.ToUniversalTime(), - } - ); - } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Identity/ApplicationBuilderExtensions.cs b/JwtAuthentication.Identity/ApplicationBuilderExtensions.cs index f86b6ff..f22a088 100644 --- a/JwtAuthentication.Identity/ApplicationBuilderExtensions.cs +++ b/JwtAuthentication.Identity/ApplicationBuilderExtensions.cs @@ -3,16 +3,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; -using IRefreshMiddlewareBuilder = TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.IRefreshMiddlewareBuilder; -using RefreshMiddlewareBuilder = TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.RefreshMiddlewareBuilder; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity { @@ -92,20 +88,6 @@ public static IApplicationBuilder UseDefaultDbUser( .UseAuthorization(); } - /// - /// Adds middleware to handle incoming login and token refresh requests. - /// - /// - /// - /// - public static IApplicationBuilder UseRefreshTokenMiddleware(this IApplicationBuilder applicationBuilder, RefreshEndpointOptions endpointOptions = null) - { - Func defaultOnRefreshCallback = s => Task.CompletedTask; - - return applicationBuilder - .UseMiddleware(endpointOptions ?? new RefreshEndpointOptions(), defaultOnRefreshCallback, defaultOnRefreshCallback); - } - /// /// Adds middleware to handle incoming user registration requests. It requires a function to map model received from client /// to user entity. @@ -248,34 +230,6 @@ public static ILogoutMiddlewareBuilder OnLogoutExecuted(this IApplicationBuilder .OnLogoutExecuted(callback); } - /// - /// Registering a callback function to perform actions when when the refresh starts. - /// - /// - /// - /// - public static IRefreshMiddlewareBuilder OnRefreshExecuting(this IApplicationBuilder applicationBuilder, Func callback) - { - return RefreshMiddlewareBuilder - .GetInstance() - .SetAppBuilder(applicationBuilder) - .OnRefreshExecuting(callback); - } - - /// - /// Registering a callback function to perform actions when the refresh ends. - /// - /// - /// - /// - public static IRefreshMiddlewareBuilder OnRefreshExecuted(this IApplicationBuilder applicationBuilder, Func callback) - { - return RefreshMiddlewareBuilder - .GetInstance() - .SetAppBuilder(applicationBuilder) - .OnRefreshExecuted(callback); - } - /// /// Registering a callback function to perform actions when when the refresh starts. /// diff --git a/JwtAuthentication.Identity/Filters/RequiresPermission.cs b/JwtAuthentication.Identity/Filters/RequiresPermission.cs new file mode 100644 index 0000000..7b08941 --- /dev/null +++ b/JwtAuthentication.Identity/Filters/RequiresPermission.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Extensions; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Filters +{ + public class RequiresPermission : Attribute, IAuthorizationFilter + { + public static string ClaimType; + + private readonly List _permissions; + + public RequiresPermission(params string[] permissions) + { + _permissions = permissions.ToList(); + } + + public void OnAuthorization(AuthorizationFilterContext context) + { + var user = context.HttpContext.User; + + if (_permissions.Any(x => user.HasPermission(ClaimType, x))) + { + return; + } + + context.Result = new ForbidResult(); + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Identity/JwtAuthentication.Identity.csproj b/JwtAuthentication.Identity/JwtAuthentication.Identity.csproj index d3d68c6..89a63dd 100644 --- a/JwtAuthentication.Identity/JwtAuthentication.Identity.csproj +++ b/JwtAuthentication.Identity/JwtAuthentication.Identity.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0;netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0 TourmalineCore.AspNetCore.JwtAuthentication.Identity 0.1.0-alpha.2 Koval Maxim, Nikita Medvedev @@ -26,12 +26,8 @@ - - - - - - + + diff --git a/JwtAuthentication.Identity/Middleware/Logout/LogoutMiddleware.cs b/JwtAuthentication.Identity/Middleware/Logout/LogoutMiddleware.cs index fff1f89..b5ccea9 100644 --- a/JwtAuthentication.Identity/Middleware/Logout/LogoutMiddleware.cs +++ b/JwtAuthentication.Identity/Middleware/Logout/LogoutMiddleware.cs @@ -1,13 +1,12 @@ using System; -using System.Security.Authentication; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Logout { @@ -55,7 +54,7 @@ protected override async Task ExecuteServiceMethod( await service.LogoutAsync(requestModel); await _onLogoutExecuted(contractLogoutModel); } - catch (AuthenticationException ex) + catch (Exception ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; _logger.LogError(ex.ToString()); diff --git a/JwtAuthentication.Identity/Middleware/Refresh/RefreshMiddleware.cs b/JwtAuthentication.Identity/Middleware/Refresh/RefreshMiddleware.cs index 4dde006..a037682 100644 --- a/JwtAuthentication.Identity/Middleware/Refresh/RefreshMiddleware.cs +++ b/JwtAuthentication.Identity/Middleware/Refresh/RefreshMiddleware.cs @@ -2,13 +2,12 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Refresh { @@ -58,7 +57,7 @@ protected override async Task ExecuteServiceMethod( result = await service.RefreshAsync(contractRefreshModel.RefreshTokenValue, contractRefreshModel.ClientFingerPrint); await _onRefreshExecuted(contractRefreshModel); } - catch (AuthenticationException ex) + catch (Exception ex) { context.Response.StatusCode = StatusCodes.Status409Conflict; _logger.LogError(ex.ToString()); diff --git a/JwtAuthentication.Identity/Middleware/Registration/IRegistrationMiddlewareBuilder.cs b/JwtAuthentication.Identity/Middleware/Registration/IRegistrationMiddlewareBuilder.cs index e729ad7..fbae330 100644 --- a/JwtAuthentication.Identity/Middleware/Registration/IRegistrationMiddlewareBuilder.cs +++ b/JwtAuthentication.Identity/Middleware/Registration/IRegistrationMiddlewareBuilder.cs @@ -2,9 +2,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration { diff --git a/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddleware.cs b/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddleware.cs index 1eeff09..35727c5 100644 --- a/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddleware.cs +++ b/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddleware.cs @@ -3,14 +3,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; - +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration { internal class RegistrationMiddleware : RegistrationMiddleware @@ -83,6 +83,7 @@ protected override async Task ExecuteServiceMethod( { context.Response.StatusCode = StatusCodes.Status409Conflict; _logger.LogError(ex.ToString()); + throw new Exception(ex.Message); } return result; diff --git a/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddlewareBuilder.cs b/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddlewareBuilder.cs index 6ab5a60..d0624a0 100644 --- a/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddlewareBuilder.cs +++ b/JwtAuthentication.Identity/Middleware/Registration/RegistrationMiddlewareBuilder.cs @@ -2,9 +2,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration.Models; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Middleware.Registration { diff --git a/JwtAuthentication.Identity/Options/AuthenticationOptions.cs b/JwtAuthentication.Identity/Options/AuthenticationOptions.cs new file mode 100644 index 0000000..1cbde28 --- /dev/null +++ b/JwtAuthentication.Identity/Options/AuthenticationOptions.cs @@ -0,0 +1,25 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options +{ + public class AuthenticationOptions : IAuthenticationOptions + { + private int _accessTokenExpireInMinutes; + + public string PublicSigningKey { get; set; } + + public string PrivateSigningKey { get; set; } + + public string Issuer { get; set; } + + public string Audience { get; set; } + + public virtual int AccessTokenExpireInMinutes + { + get => _accessTokenExpireInMinutes == default ? 10080 : _accessTokenExpireInMinutes; + set => _accessTokenExpireInMinutes = value; + } + + public bool IsDebugTokenEnabled { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Identity/Options/RefreshAuthenticationOptions.cs b/JwtAuthentication.Identity/Options/RefreshAuthenticationOptions.cs index 69b17b4..1ebc6db 100644 --- a/JwtAuthentication.Identity/Options/RefreshAuthenticationOptions.cs +++ b/JwtAuthentication.Identity/Options/RefreshAuthenticationOptions.cs @@ -1,5 +1,3 @@ -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; - namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options { public class RefreshAuthenticationOptions : AuthenticationOptions diff --git a/JwtAuthentication.Identity/Options/RefreshEndpointOptions.cs b/JwtAuthentication.Identity/Options/RefreshEndpointOptions.cs index ba87c67..c5c69a8 100644 --- a/JwtAuthentication.Identity/Options/RefreshEndpointOptions.cs +++ b/JwtAuthentication.Identity/Options/RefreshEndpointOptions.cs @@ -1,6 +1,8 @@ +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options { - public class RefreshEndpointOptions + public class RefreshEndpointOptions : IRefreshEndpointOptions { private string _refreshEndpointRoute; diff --git a/JwtAuthentication.Identity/Options/RefreshOptions.cs b/JwtAuthentication.Identity/Options/RefreshOptions.cs index 7873cc2..afe2a4d 100644 --- a/JwtAuthentication.Identity/Options/RefreshOptions.cs +++ b/JwtAuthentication.Identity/Options/RefreshOptions.cs @@ -14,7 +14,7 @@ public int RefreshConfidenceIntervalInMilliseconds if (value <= 0) { throw new ArgumentException("Refresh confidence interval cannot be zero or negative"); - } + } _refreshConfidenceIntervalInMilliseconds = value; } diff --git a/JwtAuthentication.Identity/Services/IdentityLoginService.cs b/JwtAuthentication.Identity/Services/IdentityLoginService.cs index f8954a4..f020b9b 100644 --- a/JwtAuthentication.Identity/Services/IdentityLoginService.cs +++ b/JwtAuthentication.Identity/Services/IdentityLoginService.cs @@ -1,11 +1,11 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services { @@ -37,23 +37,23 @@ public async Task LoginAsync(LoginRequestModel model) if (user is null) { - throw new AuthenticationException(ErrorTypes.IncorrectLoginOrPassword); + throw new IncorrectLoginOrPasswordException(); } var passwordIsCorrect = await _signInManager.UserManager.CheckPasswordAsync(user, model.Password); if (passwordIsCorrect == false) { - throw new AuthenticationException(ErrorTypes.IncorrectLoginOrPassword); + throw new IncorrectLoginOrPasswordException(); } - var token = await _tokenManager.GetAccessToken( + var token = await _tokenManager.GenerateAccessTokenAsync( model.Login ); return new AuthResponseModel { - AccessToken = new TokenModel + AccessToken = new BaseTokenModel { Value = token.Value, ExpiresInUtc = token.ExpiresInUtc, diff --git a/JwtAuthentication.Identity/Services/IdentityLogoutService.cs b/JwtAuthentication.Identity/Services/IdentityLogoutService.cs index 675b117..9b0f720 100644 --- a/JwtAuthentication.Identity/Services/IdentityLogoutService.cs +++ b/JwtAuthentication.Identity/Services/IdentityLogoutService.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services { diff --git a/JwtAuthentication.Identity/Services/IdentityRefreshLoginService.cs b/JwtAuthentication.Identity/Services/IdentityRefreshLoginService.cs index cccf917..9b778ae 100644 --- a/JwtAuthentication.Identity/Services/IdentityRefreshLoginService.cs +++ b/JwtAuthentication.Identity/Services/IdentityRefreshLoginService.cs @@ -1,13 +1,13 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Validators; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services { @@ -59,7 +59,7 @@ public async Task LoginAsync(LoginRequestModel model) if (signInResult == false) { - throw new AuthenticationException(ErrorTypes.IncorrectLoginOrPassword); + throw new IncorrectLoginOrPasswordException(); } var user = await _signInManager.UserManager.FindByNameAsync(model.Login); @@ -102,7 +102,7 @@ private async Task GenerateAuthTokensWhenRefreshConfidenceInt return await _signInManager.GenerateAuthTokens(user, clientFingerPrint); } - throw new AuthenticationException(ErrorTypes.RefreshTokenIsNotInConfidenceInterval); + throw new RefreshTokenIsNotInConfidenceIntervalException(); } } } \ No newline at end of file diff --git a/JwtAuthentication.Identity/Services/IdentityRegistrationService.cs b/JwtAuthentication.Identity/Services/IdentityRegistrationService.cs index 3c78fa8..34e7890 100644 --- a/JwtAuthentication.Identity/Services/IdentityRegistrationService.cs +++ b/JwtAuthentication.Identity/Services/IdentityRegistrationService.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services { diff --git a/JwtAuthentication.Identity/Services/RefreshSignInManager.cs b/JwtAuthentication.Identity/Services/RefreshSignInManager.cs index 506399b..0bca62a 100644 --- a/JwtAuthentication.Identity/Services/RefreshSignInManager.cs +++ b/JwtAuthentication.Identity/Services/RefreshSignInManager.cs @@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; - +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services { internal class RefreshSignInManager : RefreshSignInManager where TUser : IdentityUser @@ -69,7 +69,7 @@ public async Task GenerateAuthTokens(TUser appUser, string cl { return new AuthResponseModel { - AccessToken = await _accessTokenManager.GetAccessToken(appUser.NormalizedUserName), + AccessToken = await _accessTokenManager.GenerateAccessTokenAsync(appUser.NormalizedUserName), RefreshToken = await _refreshTokenManager.GenerateRefreshTokenAsync(appUser, clientFingerPrint), }; } diff --git a/JwtAuthentication.Identity/Services/RefreshTokenManager.cs b/JwtAuthentication.Identity/Services/RefreshTokenManager.cs index 5077084..e6dd9d5 100644 --- a/JwtAuthentication.Identity/Services/RefreshTokenManager.cs +++ b/JwtAuthentication.Identity/Services/RefreshTokenManager.cs @@ -1,147 +1,147 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Models; -using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; - -[assembly: InternalsVisibleTo("Tests")] - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services -{ - internal class RefreshTokenManager : RefreshTokenManager where TUser : IdentityUser - { - public RefreshTokenManager( - TourmalineDbContext dbContext, - RefreshAuthenticationOptions options) - :base(dbContext, options) - { - } - } - - internal class RefreshTokenManager : IRefreshTokenManager - where TUser : IdentityUser - where TKey : IEquatable - { - private readonly TourmalineDbContext _dbContext; - private readonly RefreshAuthenticationOptions _options; - - public RefreshTokenManager( - TourmalineDbContext dbContext, - RefreshAuthenticationOptions options) - { - _dbContext = dbContext; - _options = options; - } - - public async Task GenerateRefreshTokenAsync(object user, string clientFingerPrint) - { - var refreshToken = CreateRefreshToken(user, clientFingerPrint); - - _dbContext.Attach(refreshToken.User); - await _dbContext.Set>().AddAsync(refreshToken); - await _dbContext.SaveChangesAsync(); - - return BuildTokenModelByRefreshToken(refreshToken); - } - - public async Task GetRefreshTokenUserAsync(Guid refreshTokenValue, string clientFingerPrint) - { - var token = await _dbContext - .Set>() - .AsQueryable() - .AsNoTracking() - .Include(x => x.User) - .FirstOrDefaultAsync(x => x.Value == refreshTokenValue - && x.ExpiresIn > DateTime.UtcNow - && (clientFingerPrint == null || x.ClientFingerPrint == clientFingerPrint)); - - ThrowExceptionIfTokenIsNull(token); - - return token.User; - } - - public async Task IsTokenActiveAsync(TKey userId, Guid refreshTokenValue) - { - var token = await GetRefreshTokenAsync(userId, refreshTokenValue); - - return token.IsActive; - } - - public async Task InvalidateRefreshTokenAsync(TKey userId, Guid refreshTokenValue) - { - var token = await _dbContext - .Set>() - .Include(x => x.User) - .AsQueryable() - .FirstOrDefaultAsync(x => x.Value == refreshTokenValue - && x.IsActive - && x.UserId.Equals(userId)); - - ThrowExceptionIfTokenIsNull(token); - - token.Expire(); - - await _dbContext.SaveChangesAsync(); - } - - public async Task IsRefreshTokenInConfidenceIntervalAsync(TKey userId, Guid refreshTokenValue, int refreshConfidenceIntervalInMilliseconds) - { - var token = await GetRefreshTokenAsync(userId, refreshTokenValue); - - return (DateTime.UtcNow - token.ExpiredAtUtc).TotalMilliseconds <= refreshConfidenceIntervalInMilliseconds; - } - - private async Task> GetRefreshTokenAsync(TKey userId, Guid refreshTokenValue) - { - var token = await _dbContext - .Set>() - .Include(x => x.User) - .AsQueryable() - .AsNoTracking() - .FirstOrDefaultAsync(x => x.Value == refreshTokenValue && x.UserId.Equals(userId)); - - ThrowExceptionIfTokenIsNull(token); - - return token; - } - - private RefreshToken CreateRefreshToken(object user, string clientFingerPrint) - { - var expiresDate = DateTime.UtcNow.AddMinutes(_options.RefreshTokenExpireInMinutes); - - var newToken = new RefreshToken - { - Value = Guid.NewGuid(), - ExpiresIn = expiresDate, - IsActive = true, - ClientFingerPrint = clientFingerPrint, - User = (TUser)user, - }; - - return newToken; - } - - private static TokenModel BuildTokenModelByRefreshToken(RefreshToken refreshToken) - { - return new TokenModel - { - Value = refreshToken.Value.ToString(), - ExpiresInUtc = refreshToken.ExpiresIn.ToUniversalTime(), - }; - } - - private static void ThrowExceptionIfTokenIsNull(RefreshToken token) - { - if (token == null) - { - throw new AuthenticationException(ErrorTypes.RefreshTokenNotFound); - } - } - } +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; + +[assembly: InternalsVisibleTo("Tests")] + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services +{ + internal class RefreshTokenManager : RefreshTokenManager where TUser : IdentityUser + { + public RefreshTokenManager( + TourmalineDbContext dbContext, + RefreshAuthenticationOptions options) + :base(dbContext, options) + { + } + } + + internal class RefreshTokenManager : IRefreshTokenManager + where TUser : IdentityUser + where TKey : IEquatable + { + private readonly TourmalineDbContext _dbContext; + private readonly RefreshAuthenticationOptions _options; + + public RefreshTokenManager( + TourmalineDbContext dbContext, + RefreshAuthenticationOptions options) + { + _dbContext = dbContext; + _options = options; + } + + public async Task GenerateRefreshTokenAsync(object user, string clientFingerPrint) + { + var refreshToken = CreateRefreshToken(user, clientFingerPrint); + + _dbContext.Attach(refreshToken.User); + await _dbContext.Set>().AddAsync(refreshToken); + await _dbContext.SaveChangesAsync(); + + return BuildTokenModelByRefreshToken(refreshToken); + } + + public async Task GetRefreshTokenUserAsync(Guid refreshTokenValue, string clientFingerPrint) + { + var token = await _dbContext + .Set>() + .AsQueryable() + .AsNoTracking() + .Include(x => x.User) + .FirstOrDefaultAsync(x => x.Value == refreshTokenValue + && x.ExpiresIn > DateTime.UtcNow + && (clientFingerPrint == null || x.ClientFingerPrint == clientFingerPrint)); + + ThrowExceptionIfTokenIsNull(token); + + return token.User; + } + + public async Task IsTokenActiveAsync(TKey userId, Guid refreshTokenValue) + { + var token = await GetRefreshTokenAsync(userId, refreshTokenValue); + + return token.IsActive; + } + + public async Task InvalidateRefreshTokenAsync(TKey userId, Guid refreshTokenValue) + { + var token = await _dbContext + .Set>() + .Include(x => x.User) + .AsQueryable() + .FirstOrDefaultAsync(x => x.Value == refreshTokenValue + && x.IsActive + && x.UserId.Equals(userId)); + + ThrowExceptionIfTokenIsNull(token); + + token.Expire(); + + await _dbContext.SaveChangesAsync(); + } + + public async Task IsRefreshTokenInConfidenceIntervalAsync(TKey userId, Guid refreshTokenValue, int refreshConfidenceIntervalInMilliseconds) + { + var token = await GetRefreshTokenAsync(userId, refreshTokenValue); + + return (DateTime.UtcNow - token.ExpiredAtUtc).TotalMilliseconds <= refreshConfidenceIntervalInMilliseconds; + } + + private async Task> GetRefreshTokenAsync(TKey userId, Guid refreshTokenValue) + { + var token = await _dbContext + .Set>() + .Include(x => x.User) + .AsQueryable() + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Value == refreshTokenValue && x.UserId.Equals(userId)); + + ThrowExceptionIfTokenIsNull(token); + + return token; + } + + private RefreshToken CreateRefreshToken(object user, string clientFingerPrint) + { + var expiresDate = DateTime.UtcNow.AddMinutes(_options.RefreshTokenExpireInMinutes); + + var newToken = new RefreshToken + { + Value = Guid.NewGuid(), + ExpiresIn = expiresDate, + IsActive = true, + ClientFingerPrint = clientFingerPrint, + User = (TUser)user, + }; + + return newToken; + } + + private static BaseTokenModel BuildTokenModelByRefreshToken(RefreshToken refreshToken) + { + return new BaseTokenModel + { + Value = refreshToken.Value.ToString(), + ExpiresInUtc = refreshToken.ExpiresIn.ToUniversalTime(), + }; + } + + private static void ThrowExceptionIfTokenIsNull(RefreshToken token) + { + if (token == null) + { + throw new RefreshTokenNotFoundException(); + } + } + } } \ No newline at end of file diff --git a/JwtAuthentication.Identity/Signing/SigningHelper.cs b/JwtAuthentication.Identity/Signing/SigningHelper.cs deleted file mode 100644 index 0843440..0000000 --- a/JwtAuthentication.Identity/Signing/SigningHelper.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Security.Cryptography; -using Microsoft.IdentityModel.Tokens; - -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Signing -{ - public static class SigningHelper - { - public static RsaSecurityKey GetPublicKey(string key) - { - var publicKey = Convert.FromBase64String(key); - - var rsa = RSA.Create(); - rsa.ImportSubjectPublicKeyInfo(publicKey, out _); - - return new RsaSecurityKey(rsa); - } - - public static RsaSecurityKey GetPrivateKey(string key) - { - var privateKey = Convert.FromBase64String(key); - - var rsa = RSA.Create(); - rsa.ImportRSAPrivateKey(privateKey, out _); - - return new RsaSecurityKey(rsa); - } - } -} \ No newline at end of file diff --git a/JwtAuthentication.Identity/TourmalineAuthenticationBuilder.cs b/JwtAuthentication.Identity/TourmalineAuthenticationBuilder.cs index 6397298..a4c4fb4 100644 --- a/JwtAuthentication.Identity/TourmalineAuthenticationBuilder.cs +++ b/JwtAuthentication.Identity/TourmalineAuthenticationBuilder.cs @@ -1,18 +1,16 @@ using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; -using TourmalineCore.AspNetCore.JwtAuthentication.Core; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract.Implementation; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Filters; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services.Implementation; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Options; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Services; using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Validators; - +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Identity.Filters; + namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity { public class TourmalineAuthenticationBuilder : TourmalineAuthenticationBuilder @@ -204,8 +202,8 @@ private void AddJwt( IServiceCollection services, AuthenticationOptions authenticationOptions = null) { - var options = authenticationOptions ?? new RefreshAuthenticationOptions(); - services.AddJwtBearer(options); + var options = authenticationOptions ?? new RefreshAuthenticationOptions(); + Shared.AuthenticationExtensions.AddJwtBearer(services, options); } } } \ No newline at end of file diff --git a/JwtAuthentication.Identity/Validators/IdentityUserCredentialsValidator.cs b/JwtAuthentication.Identity/Validators/IdentityUserCredentialsValidator.cs index 82dc4ea..823bdfd 100644 --- a/JwtAuthentication.Identity/Validators/IdentityUserCredentialsValidator.cs +++ b/JwtAuthentication.Identity/Validators/IdentityUserCredentialsValidator.cs @@ -1,21 +1,21 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Validators { - internal class IdentityUserCredentialsValidator : IdentityUserCredentialsValidator + internal class IdentityUserCredentialsValidator : IdentityUserCredentialsValidator where TUser : IdentityUser { public IdentityUserCredentialsValidator(SignInManager signInManager) : base(signInManager) { } - } + } - internal class IdentityUserCredentialsValidator : IUserCredentialsValidator - where TUser : IdentityUser + internal class IdentityUserCredentialsValidator : IUserCredentialsValidator + where TUser : IdentityUser where TKey : IEquatable { private readonly SignInManager _signInManager; diff --git a/JwtAuthentication.Identity/Validators/RefreshTokenValidator.cs b/JwtAuthentication.Identity/Validators/RefreshTokenValidator.cs index 8444c3d..c7eba5f 100644 --- a/JwtAuthentication.Identity/Validators/RefreshTokenValidator.cs +++ b/JwtAuthentication.Identity/Validators/RefreshTokenValidator.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; namespace TourmalineCore.AspNetCore.JwtAuthentication.Identity.Validators { @@ -15,7 +15,7 @@ private void InternalValidate(RefreshTokenRequestModel model) { if (model == null || model.RefreshTokenValue == default) { - throw new AuthenticationException(ErrorTypes.RefreshTokenOrFingerprintNotFound); + throw new RefreshTokenOrFingerprintNotFoundException(); } } } diff --git a/JwtAuthentication.Shared/ApplicationBuilderExtensions.cs b/JwtAuthentication.Shared/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..8a39f2d --- /dev/null +++ b/JwtAuthentication.Shared/ApplicationBuilderExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.CookieLogin; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared +{ + public static class ApplicationBuilderExtensions + { + /// + /// Adds Authentication and Authorization to the app. + /// + /// + /// + public static IApplicationBuilder UseJwtAuthentication(this IApplicationBuilder applicationBuilder) + { + return applicationBuilder + .UseAuthentication() + .UseAuthorization(); + } + + /// + /// Adds middleware to handle incoming login requests. + /// + /// + /// + /// + public static IApplicationBuilder UseDefaultLoginMiddleware(this IApplicationBuilder applicationBuilder, ILoginEndpointOptions loginEndpointOptions) + { + return DefaultLoginMiddlewareBuilder + .GetInstance() + .SetAppBuilder(applicationBuilder) + .UseDefaultLoginMiddleware(loginEndpointOptions); + } + + /// + /// Adds middleware to handle incoming login requests using cookies to store auth token. + /// + /// + /// + /// + /// + public static IApplicationBuilder RegisterCookieLoginMiddleware(this IApplicationBuilder applicationBuilder, ICookieAuthOptions cookieAuthOptions, ILoginEndpointOptions loginEndpointOptions) + { + return applicationBuilder + .UseMiddleware(cookieAuthOptions, loginEndpointOptions) + .UseMiddleware(cookieAuthOptions); + } + + /// + /// Registering a callback function to perform actions when when the authentication starts. + /// + /// + /// + /// + public static IApplicationBuilder OnLoginExecuting(this IApplicationBuilder applicationBuilder, Func callback) + { + return DefaultLoginMiddlewareBuilder + .GetInstance() + .SetAppBuilder(applicationBuilder) + .OnLoginExecuting(callback); + } + + /// + /// Registering a callback function to perform actions when the authentication ends. + /// + /// + /// + /// + public static IApplicationBuilder OnLoginExecuted(this IApplicationBuilder applicationBuilder, Func callback) + { + return DefaultLoginMiddlewareBuilder + .GetInstance() + .SetAppBuilder(applicationBuilder) + .OnLoginExecuted(callback); + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/AuthenticationExtensions.cs b/JwtAuthentication.Shared/AuthenticationExtensions.cs new file mode 100644 index 0000000..7203b2f --- /dev/null +++ b/JwtAuthentication.Shared/AuthenticationExtensions.cs @@ -0,0 +1,130 @@ +using System; +using System.IdentityModel.Tokens.Jwt; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Signing; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenHandlers; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared +{ + public static class AuthenticationExtensions + { + /// + /// Adds the ability to validate JWT + /// + /// + /// + /// + public static IServiceCollection AddJwtValidation( + IServiceCollection services, + IAuthenticationOptions authenticationOptions) + { + services.AddJwtBearer(authenticationOptions); + + return services; + } + + /// + /// Adds the ability to use the basic functionality of JWT + /// + /// + /// + /// + public static IServiceCollection AddJwtAuthentication( + this IServiceCollection services, + IAuthenticationOptions authenticationOptions) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddJwtBearer(authenticationOptions); + + return services; + } + + /// + /// Allows to implement custom logic for checking the username and password + /// + /// + /// + /// + public static IServiceCollection AddUserCredentialValidator(IServiceCollection services) + where TUserCredentialsValidator : IUserCredentialsValidator + { + return services.AddTransient(typeof(IUserCredentialsValidator), typeof(TUserCredentialsValidator)); + } + + /// + /// Adds the ability to implement functionality for retrieving user claims + /// + /// + /// + /// + /// + public static IServiceCollection WithUserClaimsProvider(IServiceCollection services) + where TUserClaimsProvider : IUserClaimsProvider + { + return services.AddTransient(typeof(IUserClaimsProvider), typeof(TUserClaimsProvider)); + } + + public static void AddJwtBearer( + this IServiceCollection services, + IAuthenticationOptions authenticationOptions) + { + services.AddSingleton(authenticationOptions); + + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + Func schemeSelector = context => null; + + var authBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme); + + if (authenticationOptions.IsDebugTokenEnabled) + { + authBuilder.AddScheme(DebugTokenHandler.Schema, + options => { } + ); + + schemeSelector = context => + { + string debugHeader = context.Request.Headers[DebugTokenHandler.HeaderName]; + + return string.IsNullOrWhiteSpace(debugHeader) == false + ? DebugTokenHandler.Schema + : null; + }; + } + + authBuilder + .AddJwtBearer( + options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateLifetime = true, + ValidateIssuer = false, + ValidateAudience = false, + ValidateIssuerSigningKey = true, + IssuerSigningKey = SigningHelper.GetPublicKey(authenticationOptions.PublicSigningKey), + ClockSkew = TimeSpan.Zero, + }; + + options.ForwardDefaultSelector = schemeSelector; + } + ); + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Consts.cs b/JwtAuthentication.Shared/Consts.cs new file mode 100644 index 0000000..e795a61 --- /dev/null +++ b/JwtAuthentication.Shared/Consts.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared +{ + public static class Consts + { + public const string TokenTypeClaimName = "tokenType"; + } +} diff --git a/JwtAuthentication.Shared/Exceptions/IncorrectLoginOrPasswordException.cs b/JwtAuthentication.Shared/Exceptions/IncorrectLoginOrPasswordException.cs new file mode 100644 index 0000000..3d560f1 --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/IncorrectLoginOrPasswordException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class IncorrectLoginOrPasswordException : Exception + { + public IncorrectLoginOrPasswordException() : base("Incorrect login or password") { } + } +} diff --git a/JwtAuthentication.Shared/Exceptions/InvalidJwtTokenException.cs b/JwtAuthentication.Shared/Exceptions/InvalidJwtTokenException.cs new file mode 100644 index 0000000..7d2622a --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/InvalidJwtTokenException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class InvalidJwtTokenException : Exception + { + public InvalidJwtTokenException() : base("Invalid jwt token") { } + } +} diff --git a/JwtAuthentication.Shared/Exceptions/RefreshTokenIsNotInConfidenceIntervalException.cs b/JwtAuthentication.Shared/Exceptions/RefreshTokenIsNotInConfidenceIntervalException.cs new file mode 100644 index 0000000..b6734c9 --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/RefreshTokenIsNotInConfidenceIntervalException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class RefreshTokenIsNotInConfidenceIntervalException : Exception + { + public RefreshTokenIsNotInConfidenceIntervalException() : base("Refresh token is not in confidence interval") { } + } +} diff --git a/JwtAuthentication.Shared/Exceptions/RefreshTokenNotFoundException.cs b/JwtAuthentication.Shared/Exceptions/RefreshTokenNotFoundException.cs new file mode 100644 index 0000000..db6c979 --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/RefreshTokenNotFoundException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class RefreshTokenNotFoundException : Exception + { + public RefreshTokenNotFoundException() : base("Refresh token not found") { } + } +} diff --git a/JwtAuthentication.Shared/Exceptions/RefreshTokenOrFingerprintNotFoundException.cs b/JwtAuthentication.Shared/Exceptions/RefreshTokenOrFingerprintNotFoundException.cs new file mode 100644 index 0000000..8a95bb0 --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/RefreshTokenOrFingerprintNotFoundException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class RefreshTokenOrFingerprintNotFoundException : Exception + { + public RefreshTokenOrFingerprintNotFoundException() : base("Refresh token or fingerprint not found") { } + } +} diff --git a/JwtAuthentication.Shared/Exceptions/RegistrationException.cs b/JwtAuthentication.Shared/Exceptions/RegistrationException.cs new file mode 100644 index 0000000..85748ea --- /dev/null +++ b/JwtAuthentication.Shared/Exceptions/RegistrationException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors +{ + public class RegistrationException : Exception + { + public RegistrationException() : base("An registration exception occured") { } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Utils/ClaimsPrincipalExtensions.cs b/JwtAuthentication.Shared/Extensions/ClaimsPrincipalExtensions.cs similarity index 79% rename from JwtAuthentication.Core/Utils/ClaimsPrincipalExtensions.cs rename to JwtAuthentication.Shared/Extensions/ClaimsPrincipalExtensions.cs index cd22ff0..da41a92 100644 --- a/JwtAuthentication.Core/Utils/ClaimsPrincipalExtensions.cs +++ b/JwtAuthentication.Shared/Extensions/ClaimsPrincipalExtensions.cs @@ -2,9 +2,9 @@ using System.Linq; using System.Security.Claims; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Utils +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Extensions { - internal static class ClaimsPrincipalExtensions + public static class ClaimsPrincipalExtensions { public static bool HasPermission(this ClaimsPrincipal claimsPrincipal, string permissionType, string permissionName) { diff --git a/JwtAuthentication.Shared/JwtAuthentication.Shared.csproj b/JwtAuthentication.Shared/JwtAuthentication.Shared.csproj new file mode 100644 index 0000000..d0f913b --- /dev/null +++ b/JwtAuthentication.Shared/JwtAuthentication.Shared.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1;net5.0;net6.0 + TourmalineCore.AspNetCore.JwtAuthentication.Shared + + + + + + + + + + + + + + + + diff --git a/JwtAuthentication.Core/Middlewares/LoginWithCookieMiddleware.cs b/JwtAuthentication.Shared/Middlewares/CookieLogin/LoginWithCookieMiddleware.cs similarity index 69% rename from JwtAuthentication.Core/Middlewares/LoginWithCookieMiddleware.cs rename to JwtAuthentication.Shared/Middlewares/CookieLogin/LoginWithCookieMiddleware.cs index 7900581..19d5e55 100644 --- a/JwtAuthentication.Core/Middlewares/LoginWithCookieMiddleware.cs +++ b/JwtAuthentication.Shared/Middlewares/CookieLogin/LoginWithCookieMiddleware.cs @@ -1,22 +1,22 @@ +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.ErrorHandling; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Services; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.CookieLogin { internal class LoginWithCookieMiddleware : RequestMiddlewareBase { - private readonly CookieAuthOptions _cookieOptions; - private readonly LoginEndpointOptions _loginEndpointOptions; + private readonly ICookieAuthOptions _cookieOptions; + private readonly ILoginEndpointOptions _loginEndpointOptions; public LoginWithCookieMiddleware( RequestDelegate next, - CookieAuthOptions cookieOptions, - LoginEndpointOptions loginEndpointOptions) + ICookieAuthOptions cookieOptions, + ILoginEndpointOptions loginEndpointOptions) : base(next) { _cookieOptions = cookieOptions; @@ -51,9 +51,10 @@ protected override async Task ExecuteServiceMethod( ); } } - catch (AuthenticationException) + catch (Exception ex) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; + throw new Exception(ex.Message); } return new AuthResponseModel(); diff --git a/JwtAuthentication.Core/Middlewares/TokenExtractionFromCookieMiddleware.cs b/JwtAuthentication.Shared/Middlewares/CookieLogin/TokenExtractionFromCookieMiddleware.cs similarity index 72% rename from JwtAuthentication.Core/Middlewares/TokenExtractionFromCookieMiddleware.cs rename to JwtAuthentication.Shared/Middlewares/CookieLogin/TokenExtractionFromCookieMiddleware.cs index e5f1cbe..33de5c5 100644 --- a/JwtAuthentication.Core/Middlewares/TokenExtractionFromCookieMiddleware.cs +++ b/JwtAuthentication.Shared/Middlewares/CookieLogin/TokenExtractionFromCookieMiddleware.cs @@ -1,15 +1,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.CookieLogin { public class TokenExtractionFromCookieMiddleware { private readonly RequestDelegate _next; - private readonly CookieAuthOptions _options; + private readonly ICookieAuthOptions _options; - public TokenExtractionFromCookieMiddleware(RequestDelegate next, CookieAuthOptions options) + public TokenExtractionFromCookieMiddleware(RequestDelegate next, ICookieAuthOptions options) { _next = next; _options = options; diff --git a/JwtAuthentication.Shared/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs b/JwtAuthentication.Shared/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs new file mode 100644 index 0000000..ca859f5 --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/DefaultLoginMiddlewareBuilder.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login +{ + internal class DefaultLoginMiddlewareBuilder + { + private static Func _onLoginExecutingCallback = null; + private static Func _onLoginExecutedCallback = null; + + private static bool _loginMiddlewareIsRegistered = false; + private static bool _executingCallbackIsRegistered = false; + private static bool _executedCallbackIsRegistered = false; + + private IApplicationBuilder _applicationBuilder; + + private static DefaultLoginMiddlewareBuilder _instance; + + private DefaultLoginMiddlewareBuilder() + { + } + + internal static DefaultLoginMiddlewareBuilder GetInstance() + { + if (_instance != null) + { + return _instance; + } + + _instance = new DefaultLoginMiddlewareBuilder(); + + return _instance; + } + + internal DefaultLoginMiddlewareBuilder SetAppBuilder(IApplicationBuilder applicationBuilder) + { + _applicationBuilder = applicationBuilder; + return this; + } + + /// + /// Registering a callback function to perform actions when when the authentication starts. + /// + /// + /// + public IApplicationBuilder OnLoginExecuting(Func callback) + { + _onLoginExecutingCallback = callback; + _executingCallbackIsRegistered = true; + + if (!_loginMiddlewareIsRegistered) + { + return _applicationBuilder; + } + + return _applicationBuilder.UseMiddleware(_onLoginExecutingCallback); + } + + /// + /// Registering a callback function to perform actions when the authentication ends. + /// + /// + /// + public IApplicationBuilder OnLoginExecuted(Func callback) + { + _onLoginExecutedCallback = callback; + _executedCallbackIsRegistered = true; + + if (!_loginMiddlewareIsRegistered) + { + return _applicationBuilder; + } + + return _applicationBuilder.UseMiddleware(_onLoginExecutedCallback); + } + + /// + /// Adds middleware to handle incoming login requests. + /// + /// + /// + public IApplicationBuilder UseDefaultLoginMiddleware(ILoginEndpointOptions loginEndpointOptions) + { + RegisterDefaultLoginMiddleware(loginEndpointOptions); + return _applicationBuilder; + } + + private void RegisterDefaultLoginMiddleware(ILoginEndpointOptions loginEndpointOptions) + { + var middlewareArguments = new List() { loginEndpointOptions }; + + if (_executingCallbackIsRegistered) + { + middlewareArguments.Add(_onLoginExecutingCallback); + } + + if (_executedCallbackIsRegistered) + { + middlewareArguments.Add(_onLoginExecutedCallback); + } + + _applicationBuilder.UseMiddleware(middlewareArguments.ToArray()); + _loginMiddlewareIsRegistered = true; + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Middlewares/Login/LoginMiddleware.cs b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddleware.cs new file mode 100644 index 0000000..0bf4a4b --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddleware.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login +{ + internal class LoginMiddleware : RequestMiddlewareBase + { + protected static ILoginEndpointOptions _loginEndpointOptions; + protected static Func _onLoginExecuting = null; + protected static Func _onLoginExecuted = null; + + public LoginMiddleware(RequestDelegate next, ILoginEndpointOptions loginEndpointOptions, Func onLoginExecuting = null, Func onLoginExecuted = null) + : base(next) + { + _loginEndpointOptions = loginEndpointOptions; + + if (onLoginExecuting != null) + { + _onLoginExecuting = onLoginExecuting; + } + + if (onLoginExecuted != null) + { + _onLoginExecuted = onLoginExecuted; + } + } + + public async Task InvokeAsync(HttpContext context, ILoginService loginService) + { + await InvokeAsyncBase(context, loginService, _loginEndpointOptions.LoginEndpointRoute); + } + + protected override async Task ExecuteServiceMethod( + LoginRequestModel requestModel, + ILoginService service, + HttpContext context) + { + var result = new AuthResponseModel(); + + try + { + var contractLoginModel = new BasicLoginModel + { + Login = requestModel.Login, + Password = requestModel.Password, + ClientFingerPrint = requestModel.ClientFingerPrint, + }; + + if (_onLoginExecuting != null) + { + await _onLoginExecuting(contractLoginModel); + } + + result = await service.LoginAsync(requestModel); + + if (_onLoginExecuted != null) + { + await _onLoginExecuted(contractLoginModel); + } + } + catch (Exception ex) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + throw new Exception(ex.Message); + } + + return result; + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutedCallback.cs b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutedCallback.cs new file mode 100644 index 0000000..e30b60b --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutedCallback.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login +{ + internal class LoginMiddlewareWithExecutedCallback : LoginMiddleware + { + public LoginMiddlewareWithExecutedCallback(RequestDelegate next, Func onLoginExecuted) + : base(next, null, null, onLoginExecuted) { } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutingCallback.cs b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutingCallback.cs new file mode 100644 index 0000000..e6940a8 --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/LoginMiddlewareWithExecutingCallback.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login +{ + internal class LoginMiddlewareWithExecutingCallback : LoginMiddleware + { + public LoginMiddlewareWithExecutingCallback(RequestDelegate next, Func onLoginExecuting) + : base(next, null, onLoginExecuting, null) { } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Middlewares/Login/Models/BasicLoginModel.cs b/JwtAuthentication.Shared/Middlewares/Login/Models/BasicLoginModel.cs new file mode 100644 index 0000000..52611b8 --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/Models/BasicLoginModel.cs @@ -0,0 +1,11 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models +{ + public class BasicLoginModel : IBasicLoginModel + { + public string Login { get; set; } + + public string Password { get; set; } + + public string ClientFingerPrint { get; set; } + } +} diff --git a/JwtAuthentication.Shared/Middlewares/Login/Models/IBasicLoginModel.cs b/JwtAuthentication.Shared/Middlewares/Login/Models/IBasicLoginModel.cs new file mode 100644 index 0000000..9641db0 --- /dev/null +++ b/JwtAuthentication.Shared/Middlewares/Login/Models/IBasicLoginModel.cs @@ -0,0 +1,11 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares.Login.Models +{ + public interface IBasicLoginModel + { + public string Login { get; set; } + + public string Password { get; set; } + + public string ClientFingerPrint { get; set; } + } +} diff --git a/JwtAuthentication.Core/Middlewares/RequestMiddlewareBase.cs b/JwtAuthentication.Shared/Middlewares/RequestMiddlewareBase.cs similarity index 71% rename from JwtAuthentication.Core/Middlewares/RequestMiddlewareBase.cs rename to JwtAuthentication.Shared/Middlewares/RequestMiddlewareBase.cs index 83069b8..d7e8370 100644 --- a/JwtAuthentication.Core/Middlewares/RequestMiddlewareBase.cs +++ b/JwtAuthentication.Shared/Middlewares/RequestMiddlewareBase.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -11,9 +12,9 @@ using System.Text.Json.Serialization; #endif -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Middlewares +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Middlewares { - internal abstract class RequestMiddlewareBase + public abstract class RequestMiddlewareBase { private readonly RequestDelegate _next; #if NETCOREAPP3_0 || NETCOREAPP3_1 @@ -55,28 +56,41 @@ protected RequestMiddlewareBase(RequestDelegate next) protected async Task InvokeAsyncBase(HttpContext context, TService service, string endpointRoute) { - if (context.Request.Method == HttpMethods.Post) + try { - var endpoint = context.Request.Path.Value; - - if (endpoint.EndsWith(endpointRoute)) + if (context.Request.Method == HttpMethods.Post) { - var requestModel = await DeserializeModel(context.Request); - - var result = await ExecuteServiceMethod(requestModel, service, context); + var endpoint = context.Request.Path.Value; - if (result != null) + if (endpoint.EndsWith(endpointRoute)) { - await Response(context, result); - return; + var requestModel = await DeserializeModel(context.Request); + + var result = await ExecuteServiceMethod(requestModel, service, context); + + if (result != null) + { + await Response(context, result); + return; + } } } } + catch (Exception ex) + { + var errorModelExample = new + { + ErrorMessage = ex.Message, + }; + + await Response(context, errorModelExample); + return; + } await _next(context); } - private async Task Response(HttpContext context, TResponseModel result) + private async Task Response(HttpContext context, T result) { context.Response.ContentType = "application/json; charset=UTF-8"; #if NETCOREAPP3_0 || NETCOREAPP3_1 diff --git a/JwtAuthentication.Shared/Models/BaseTokenModel.cs b/JwtAuthentication.Shared/Models/BaseTokenModel.cs new file mode 100644 index 0000000..aaab9a2 --- /dev/null +++ b/JwtAuthentication.Shared/Models/BaseTokenModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models +{ + public class BaseTokenModel + { + public string Value { get; set; } + + public DateTime ExpiresInUtc { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Models/Requests/CoreRefreshTokenRequest.cs b/JwtAuthentication.Shared/Models/Requests/CoreRefreshTokenRequest.cs new file mode 100644 index 0000000..3e3dae9 --- /dev/null +++ b/JwtAuthentication.Shared/Models/Requests/CoreRefreshTokenRequest.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests +{ + public class CoreRefreshTokenRequest + { + public string RefreshTokenValue { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Models/Request/LoginRequestModel.cs b/JwtAuthentication.Shared/Models/Requests/LoginRequestModel.cs similarity index 71% rename from JwtAuthentication.Core/Models/Request/LoginRequestModel.cs rename to JwtAuthentication.Shared/Models/Requests/LoginRequestModel.cs index d0acb0d..ef097fd 100644 --- a/JwtAuthentication.Core/Models/Request/LoginRequestModel.cs +++ b/JwtAuthentication.Shared/Models/Requests/LoginRequestModel.cs @@ -2,9 +2,9 @@ [assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests { - internal class LoginRequestModel + public class LoginRequestModel { public string Login { get; set; } diff --git a/JwtAuthentication.Core/Models/Request/LogoutRequestModel.cs b/JwtAuthentication.Shared/Models/Requests/LogoutRequestModel.cs similarity index 70% rename from JwtAuthentication.Core/Models/Request/LogoutRequestModel.cs rename to JwtAuthentication.Shared/Models/Requests/LogoutRequestModel.cs index a1eab96..fb576bf 100644 --- a/JwtAuthentication.Core/Models/Request/LogoutRequestModel.cs +++ b/JwtAuthentication.Shared/Models/Requests/LogoutRequestModel.cs @@ -3,9 +3,9 @@ [assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests { - internal class LogoutRequestModel + public class LogoutRequestModel { public Guid RefreshTokenValue { get; set; } diff --git a/JwtAuthentication.Core/Models/Request/RefreshTokenRequestModel.cs b/JwtAuthentication.Shared/Models/Requests/RefreshTokenRequestModel.cs similarity index 69% rename from JwtAuthentication.Core/Models/Request/RefreshTokenRequestModel.cs rename to JwtAuthentication.Shared/Models/Requests/RefreshTokenRequestModel.cs index cb65955..8a4ddb5 100644 --- a/JwtAuthentication.Core/Models/Request/RefreshTokenRequestModel.cs +++ b/JwtAuthentication.Shared/Models/Requests/RefreshTokenRequestModel.cs @@ -1,6 +1,6 @@ using System; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests { public class RefreshTokenRequestModel { diff --git a/JwtAuthentication.Core/Models/Request/RegistrationRequestModel.cs b/JwtAuthentication.Shared/Models/Requests/RegistrationRequestModel.cs similarity index 65% rename from JwtAuthentication.Core/Models/Request/RegistrationRequestModel.cs rename to JwtAuthentication.Shared/Models/Requests/RegistrationRequestModel.cs index ec1d97c..1444294 100644 --- a/JwtAuthentication.Core/Models/Request/RegistrationRequestModel.cs +++ b/JwtAuthentication.Shared/Models/Requests/RegistrationRequestModel.cs @@ -1,4 +1,4 @@ -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests { public class RegistrationRequestModel { diff --git a/JwtAuthentication.Shared/Models/Responses/AuthResponseModel.cs b/JwtAuthentication.Shared/Models/Responses/AuthResponseModel.cs new file mode 100644 index 0000000..c7a427e --- /dev/null +++ b/JwtAuthentication.Shared/Models/Responses/AuthResponseModel.cs @@ -0,0 +1,14 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] +[assembly: InternalsVisibleTo("Tests")] + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses +{ + public class AuthResponseModel + { + public BaseTokenModel AccessToken { get; set; } + + public BaseTokenModel RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Models/TokenType.cs b/JwtAuthentication.Shared/Models/TokenType.cs new file mode 100644 index 0000000..6f569c0 --- /dev/null +++ b/JwtAuthentication.Shared/Models/TokenType.cs @@ -0,0 +1,13 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models +{ + public static class TokenType + { + public const string Refresh = "refresh"; + public const string Access = "access"; + + public static bool IsAvailableTokenType(string value) + { + return value == Refresh || value == Access; + } + } +} diff --git a/JwtAuthentication.Shared/Options/Contracts/IAuthenticationOptions.cs b/JwtAuthentication.Shared/Options/Contracts/IAuthenticationOptions.cs new file mode 100644 index 0000000..0271431 --- /dev/null +++ b/JwtAuthentication.Shared/Options/Contracts/IAuthenticationOptions.cs @@ -0,0 +1,17 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts +{ + public interface IAuthenticationOptions + { + public string PrivateSigningKey { get; set; } + + public string PublicSigningKey { get; set; } + + public string Issuer { get; set; } + + public string Audience { get; set; } + + public int AccessTokenExpireInMinutes { get; set; } + + public bool IsDebugTokenEnabled { get; set; } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Options/Contracts/ICookieAuthOptions.cs b/JwtAuthentication.Shared/Options/Contracts/ICookieAuthOptions.cs new file mode 100644 index 0000000..e8ae461 --- /dev/null +++ b/JwtAuthentication.Shared/Options/Contracts/ICookieAuthOptions.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts +{ + public interface ICookieAuthOptions + { + public string Key { get; set; } + } +} diff --git a/JwtAuthentication.Shared/Options/Contracts/ILoginEndpointOptions.cs b/JwtAuthentication.Shared/Options/Contracts/ILoginEndpointOptions.cs new file mode 100644 index 0000000..e447cb0 --- /dev/null +++ b/JwtAuthentication.Shared/Options/Contracts/ILoginEndpointOptions.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts +{ + public interface ILoginEndpointOptions + { + public string LoginEndpointRoute { get; set; } + } +} diff --git a/JwtAuthentication.Shared/Options/Contracts/IRefreshEndpointOptions.cs b/JwtAuthentication.Shared/Options/Contracts/IRefreshEndpointOptions.cs new file mode 100644 index 0000000..6e50bc3 --- /dev/null +++ b/JwtAuthentication.Shared/Options/Contracts/IRefreshEndpointOptions.cs @@ -0,0 +1,7 @@ +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts +{ + public interface IRefreshEndpointOptions + { + public string RefreshEndpointRoute { get; set; } + } +} diff --git a/JwtAuthentication.Shared/Services/Contracts/ILoginService.cs b/JwtAuthentication.Shared/Services/Contracts/ILoginService.cs new file mode 100644 index 0000000..4414f41 --- /dev/null +++ b/JwtAuthentication.Shared/Services/Contracts/ILoginService.cs @@ -0,0 +1,14 @@ +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; + +[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts +{ + public interface ILoginService + { + public Task LoginAsync(LoginRequestModel model); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Services/ILogoutService.cs b/JwtAuthentication.Shared/Services/Contracts/ILogoutService.cs similarity index 54% rename from JwtAuthentication.Core/Services/ILogoutService.cs rename to JwtAuthentication.Shared/Services/Contracts/ILogoutService.cs index d3d1d25..9809374 100644 --- a/JwtAuthentication.Core/Services/ILogoutService.cs +++ b/JwtAuthentication.Shared/Services/Contracts/ILogoutService.cs @@ -1,12 +1,12 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Request; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; [assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts { - internal interface ILogoutService + public interface ILogoutService { public Task LogoutAsync(LogoutRequestModel model); } diff --git a/JwtAuthentication.Core/Services/IRefreshService.cs b/JwtAuthentication.Shared/Services/Contracts/IRefreshService.cs similarity index 60% rename from JwtAuthentication.Core/Services/IRefreshService.cs rename to JwtAuthentication.Shared/Services/Contracts/IRefreshService.cs index 1a5461c..76df4d3 100644 --- a/JwtAuthentication.Core/Services/IRefreshService.cs +++ b/JwtAuthentication.Shared/Services/Contracts/IRefreshService.cs @@ -1,13 +1,13 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models.Response; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; [assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts { - internal interface IRefreshService + public interface IRefreshService { public Task RefreshAsync(Guid refreshTokenValue, string clientFingerPrint); } diff --git a/JwtAuthentication.Core/Services/IRefreshTokenManager.cs b/JwtAuthentication.Shared/Services/Contracts/IRefreshTokenManager.cs similarity index 63% rename from JwtAuthentication.Core/Services/IRefreshTokenManager.cs rename to JwtAuthentication.Shared/Services/Contracts/IRefreshTokenManager.cs index aa3fb78..71646b0 100644 --- a/JwtAuthentication.Core/Services/IRefreshTokenManager.cs +++ b/JwtAuthentication.Shared/Services/Contracts/IRefreshTokenManager.cs @@ -1,14 +1,14 @@ using System; using System.Threading.Tasks; -using TourmalineCore.AspNetCore.JwtAuthentication.Core.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Services +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts { - internal interface IRefreshTokenManager + public interface IRefreshTokenManager where TUser : class where TKey : IEquatable { - Task GenerateRefreshTokenAsync(object user, string clientFingerPrint); + Task GenerateRefreshTokenAsync(object user, string clientFingerPrint); Task GetRefreshTokenUserAsync(Guid refreshTokenValue, string clientFingerPrint); diff --git a/JwtAuthentication.Shared/Services/Contracts/IRegistrationService.cs b/JwtAuthentication.Shared/Services/Contracts/IRegistrationService.cs new file mode 100644 index 0000000..c93b48a --- /dev/null +++ b/JwtAuthentication.Shared/Services/Contracts/IRegistrationService.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts +{ + public interface IRegistrationService + where TUser : class + where TRegistrationRequestModel : RegistrationRequestModel + { + Task RegisterAsync(TRegistrationRequestModel model, Func mapping); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Services/Contracts/ITokenManager.cs b/JwtAuthentication.Shared/Services/Contracts/ITokenManager.cs new file mode 100644 index 0000000..2e05c55 --- /dev/null +++ b/JwtAuthentication.Shared/Services/Contracts/ITokenManager.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts +{ + public interface ITokenManager + { + Task GenerateAccessTokenAsync(string login = null); + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Services/LoginService.cs b/JwtAuthentication.Shared/Services/LoginService.cs new file mode 100644 index 0000000..99509eb --- /dev/null +++ b/JwtAuthentication.Shared/Services/LoginService.cs @@ -0,0 +1,45 @@ +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Errors; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Requests; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models.Responses; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services +{ + public class LoginService : ILoginService + { + private readonly ITokenManager _tokenManager; + + private readonly IUserCredentialsValidator _userCredentialsValidator; + + + public LoginService( + ITokenManager tokenManager, + IUserCredentialsValidator userCredentialsValidator = null) + { + _tokenManager = tokenManager; + _userCredentialsValidator = userCredentialsValidator; + } + + public virtual async Task LoginAsync(LoginRequestModel model) + { + await ValidateCredentials(model); + + return new AuthResponseModel + { + AccessToken = await _tokenManager.GenerateAccessTokenAsync(model.Login) + }; + } + + protected virtual async Task ValidateCredentials(LoginRequestModel model) + { + var isUserCredentialsValid = await _userCredentialsValidator.ValidateUserCredentials(model.Login, model.Password); + + if (!isUserCredentialsValid) + { + throw new IncorrectLoginOrPasswordException(); + } + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Shared/Services/TokenManager.cs b/JwtAuthentication.Shared/Services/TokenManager.cs new file mode 100644 index 0000000..f5ac5ea --- /dev/null +++ b/JwtAuthentication.Shared/Services/TokenManager.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Security.Claims; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; + +[assembly: InternalsVisibleTo("TourmalineCore.AspNetCore.JwtAuthentication.Identity")] + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Services +{ + public class TokenManager : ITokenManager + { + private readonly IAuthenticationOptions _options; + private readonly IUserClaimsProvider _userClaimsProvider; + private readonly IJwtTokenCreator _jwtTokenCreator; + + public TokenManager( + IAuthenticationOptions options, + IUserClaimsProvider userClaimsProvider, + IJwtTokenCreator jwtTokenCreator) + { + _options = options; + _userClaimsProvider = userClaimsProvider; + _jwtTokenCreator = jwtTokenCreator; + } + + public async Task GenerateAccessTokenAsync(string login = null) + { + var claims = login == null + ? new List() + : await _userClaimsProvider.GetUserClaimsAsync(login); + + return await _jwtTokenCreator.CreateAsync(TokenType.Access, _options.AccessTokenExpireInMinutes, claims); + } + } +} \ No newline at end of file diff --git a/JwtAuthentication.Core/Signing/SigningHelper.cs b/JwtAuthentication.Shared/Signing/SigningHelper.cs similarity index 91% rename from JwtAuthentication.Core/Signing/SigningHelper.cs rename to JwtAuthentication.Shared/Signing/SigningHelper.cs index 0843440..95daf33 100644 --- a/JwtAuthentication.Core/Signing/SigningHelper.cs +++ b/JwtAuthentication.Shared/Signing/SigningHelper.cs @@ -2,7 +2,7 @@ using System.Security.Cryptography; using Microsoft.IdentityModel.Tokens; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Signing +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.Signing { public static class SigningHelper { diff --git a/JwtAuthentication.Core/TokenHandlers/DebugTokenHandler.cs b/JwtAuthentication.Shared/TokenHandlers/DebugTokenHandler.cs similarity index 97% rename from JwtAuthentication.Core/TokenHandlers/DebugTokenHandler.cs rename to JwtAuthentication.Shared/TokenHandlers/DebugTokenHandler.cs index e585183..fded6e3 100644 --- a/JwtAuthentication.Core/TokenHandlers/DebugTokenHandler.cs +++ b/JwtAuthentication.Shared/TokenHandlers/DebugTokenHandler.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.TokenHandlers +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenHandlers { internal class DebugTokenHandler : AuthenticationHandler { diff --git a/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenCreator.cs b/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenCreator.cs new file mode 100644 index 0000000..22e38b2 --- /dev/null +++ b/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenCreator.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts +{ + public interface IJwtTokenCreator + { + Task CreateAsync(string tokenType, int tokenLifetimeInMinutes, List claims = null); + } +} diff --git a/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenValidator.cs b/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenValidator.cs new file mode 100644 index 0000000..5fb3175 --- /dev/null +++ b/JwtAuthentication.Shared/TokenServices/Contracts/IJwtTokenValidator.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts +{ + public interface IJwtTokenValidator + { + Task ValidateTokenAsync(string tokenValue); + Task ValidateTokenTypeAsync(string tokenValue, string tokenType); + } +} diff --git a/JwtAuthentication.Shared/TokenServices/JwtTokenCreator.cs b/JwtAuthentication.Shared/TokenServices/JwtTokenCreator.cs new file mode 100644 index 0000000..8d3798c --- /dev/null +++ b/JwtAuthentication.Shared/TokenServices/JwtTokenCreator.cs @@ -0,0 +1,75 @@ +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Signing; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices +{ + public class JwtTokenCreator : IJwtTokenCreator + { + private readonly IAuthenticationOptions _authenticationOptions; + + public JwtTokenCreator(IAuthenticationOptions authenticationOptions) + { + _authenticationOptions = authenticationOptions; + } + + public async Task CreateAsync(string tokenType, int tokenLifetimeInMinutes, List claims = null) + { + if (!TokenType.IsAvailableTokenType(tokenType)) + { + throw new ArgumentException("Invalid token type value"); + } + + if (tokenLifetimeInMinutes <= 0) + { + throw new ArgumentException("Token lifetime cannot be negative or zero"); + } + + if (claims == null) + { + claims = new List(); + } + + AddTokenTypeClaim(claims, tokenType); + + var tokenExpiresIn = DateTime.UtcNow.AddMinutes(tokenLifetimeInMinutes); + + var token = new JwtSecurityToken( + _authenticationOptions.Issuer, + _authenticationOptions.Audience, + claims, + expires: tokenExpiresIn, + signingCredentials: GetCredentials()); + + return await Task.FromResult(new BaseTokenModel + { + Value = new JwtSecurityTokenHandler().WriteToken(token), + ExpiresInUtc = tokenExpiresIn.ToUniversalTime(), + }); + } + + private SigningCredentials GetCredentials() + { + var privateKey = SigningHelper.GetPrivateKey(_authenticationOptions.PrivateSigningKey); + + return new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256); + } + + private void AddTokenTypeClaim(List claims, string tokenTypeValue) + { + var isTokenTypeClaimSet = claims.Exists(x => x.Type == Consts.TokenTypeClaimName); + + if (!isTokenTypeClaimSet) + { + claims.Add(new Claim(Consts.TokenTypeClaimName, tokenTypeValue)); + } + } + } +} diff --git a/JwtAuthentication.Shared/TokenServices/JwtTokenValidator.cs b/JwtAuthentication.Shared/TokenServices/JwtTokenValidator.cs new file mode 100644 index 0000000..d70a262 --- /dev/null +++ b/JwtAuthentication.Shared/TokenServices/JwtTokenValidator.cs @@ -0,0 +1,61 @@ +using Microsoft.IdentityModel.Tokens; +using System; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Models; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Options.Contracts; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.Signing; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices.Contracts; + +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.TokenServices +{ + public class JwtTokenValidator : IJwtTokenValidator + { + private readonly IAuthenticationOptions _authenticationOptions; + + public JwtTokenValidator(IAuthenticationOptions options) + { + _authenticationOptions = options; + } + + public Task ValidateTokenAsync(string tokenValue) + { + var handler = new JwtSecurityTokenHandler(); + var tokenValidationParameters = new TokenValidationParameters + { + ValidateLifetime = true, + ValidateIssuer = false, + ValidateAudience = false, + ValidateIssuerSigningKey = true, + IssuerSigningKey = SigningHelper.GetPublicKey(_authenticationOptions.PublicSigningKey), + ClockSkew = TimeSpan.Zero, + }; + + return Task.Run(() => handler.ValidateToken(tokenValue, tokenValidationParameters, out var _)); + } + + public Task ValidateTokenTypeAsync(string tokenValue, string tokenType) + { + if (!TokenType.IsAvailableTokenType(tokenType)) + { + throw new ArgumentException("Invalid token type value"); + } + + var token = new JwtSecurityTokenHandler().ReadJwtToken(tokenValue); + var tokenTypeClaim = token.Claims.SingleOrDefault(claim => claim.Type == Consts.TokenTypeClaimName); + + if (tokenTypeClaim == null) + { + throw new Exception("Token type claim is not set"); + } + + if (tokenTypeClaim.Value != tokenType) + { + throw new ArgumentException($"Token type is not '{tokenType}'"); + } + + return Task.CompletedTask; + } + } +} diff --git a/JwtAuthentication.Core/Contract/IUserClaimsProvider.cs b/JwtAuthentication.Shared/UserServices/Contracts/IUserClaimsProvider.cs similarity index 71% rename from JwtAuthentication.Core/Contract/IUserClaimsProvider.cs rename to JwtAuthentication.Shared/UserServices/Contracts/IUserClaimsProvider.cs index 715fe22..7d2dab4 100644 --- a/JwtAuthentication.Core/Contract/IUserClaimsProvider.cs +++ b/JwtAuthentication.Shared/UserServices/Contracts/IUserClaimsProvider.cs @@ -2,7 +2,7 @@ using System.Security.Claims; using System.Threading.Tasks; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts { public interface IUserClaimsProvider { diff --git a/JwtAuthentication.Core/Contract/IUserCredentialsValidator.cs b/JwtAuthentication.Shared/UserServices/Contracts/IUserCredentialsValidator.cs similarity index 66% rename from JwtAuthentication.Core/Contract/IUserCredentialsValidator.cs rename to JwtAuthentication.Shared/UserServices/Contracts/IUserCredentialsValidator.cs index a2959e8..d88a7ec 100644 --- a/JwtAuthentication.Core/Contract/IUserCredentialsValidator.cs +++ b/JwtAuthentication.Shared/UserServices/Contracts/IUserCredentialsValidator.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts { public interface IUserCredentialsValidator { diff --git a/JwtAuthentication.Core/Contract/Implementation/DefaultUserClaimsProvider.cs b/JwtAuthentication.Shared/UserServices/DefaultUserClaimsProvider.cs similarity index 63% rename from JwtAuthentication.Core/Contract/Implementation/DefaultUserClaimsProvider.cs rename to JwtAuthentication.Shared/UserServices/DefaultUserClaimsProvider.cs index 19a196d..90bf68b 100644 --- a/JwtAuthentication.Core/Contract/Implementation/DefaultUserClaimsProvider.cs +++ b/JwtAuthentication.Shared/UserServices/DefaultUserClaimsProvider.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract.Implementation +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices { - internal class DefaultUserClaimsProvider : IUserClaimsProvider + public class DefaultUserClaimsProvider : IUserClaimsProvider { public Task> GetUserClaimsAsync(string login) { diff --git a/JwtAuthentication.Core/Contract/Implementation/FakeUserCredentialValidator.cs b/JwtAuthentication.Shared/UserServices/FakeUserCredentialValidator.cs similarity index 58% rename from JwtAuthentication.Core/Contract/Implementation/FakeUserCredentialValidator.cs rename to JwtAuthentication.Shared/UserServices/FakeUserCredentialValidator.cs index beaa503..82a03f5 100644 --- a/JwtAuthentication.Core/Contract/Implementation/FakeUserCredentialValidator.cs +++ b/JwtAuthentication.Shared/UserServices/FakeUserCredentialValidator.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; +using TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices.Contracts; -namespace TourmalineCore.AspNetCore.JwtAuthentication.Core.Contract.Implementation +namespace TourmalineCore.AspNetCore.JwtAuthentication.Shared.UserServices { - internal class FakeUserCredentialValidator : IUserCredentialsValidator + public class FakeUserCredentialValidator : IUserCredentialsValidator { private const string Login = "Admin"; private const string Password = "Admin";