├── .gitattributes ├── .gitignore ├── .gitpod.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CmlLib.Core.Auth.Microsoft.sln ├── LICENSE ├── README.md ├── benchmarks └── XboxAuthNet.Game.Benchmarks │ ├── Benchmarks.cs │ ├── Program.cs │ └── XboxAuthNet.Game.Benchmarks.csproj ├── examples ├── ConsoleTest │ ├── ConsoleTest.csproj │ └── Program.cs └── WinFormTest │ ├── AccountsForm.Designer.cs │ ├── AccountsForm.cs │ ├── AccountsForm.resx │ ├── AuthForm.Designer.cs │ ├── AuthForm.cs │ ├── AuthForm.resx │ ├── DeviceCodeForm.Designer.cs │ ├── DeviceCodeForm.cs │ ├── DeviceCodeForm.resx │ ├── JEGameAccountProperties.cs │ ├── JELoginWrapper.cs │ ├── Program.cs │ ├── Util.cs │ └── WinFormTest.csproj ├── icon.png ├── src ├── CmlLib.Core.Auth.Microsoft │ ├── Authenticators │ │ ├── ExceptionHelper.cs │ │ ├── JEAuthenticationProvider.cs │ │ ├── JEAuthenticatorBuilder.cs │ │ ├── JEGameOwnershipChecker.cs │ │ ├── JEProfileAuthenticator.cs │ │ ├── JEProfileValidator.cs │ │ ├── JETokenAuthenticator.cs │ │ └── JETokenValidator.cs │ ├── CmlLib.Core.Auth.Microsoft.csproj │ ├── Extensions.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── JEAuthException.cs │ ├── JELoginHandler.cs │ ├── JELoginHandlerBuilder.cs │ ├── Log.cs │ └── Sessions │ │ ├── JEAccessTokenPayload.cs │ │ ├── JEGameAccount.cs │ │ ├── JEProfile.cs │ │ ├── JEProfileSource.cs │ │ ├── JEToken.cs │ │ └── JETokenSource.cs ├── CmlLib.Core.Bedrock.Auth │ ├── BEAuthenticator.cs │ ├── BedrockAuthException.cs │ ├── CmlLib.Core.Bedrock.Auth.csproj │ ├── Extensions.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ └── Sessions │ │ ├── BEGameAccount.cs │ │ ├── BESession.cs │ │ ├── BESessionSource.cs │ │ ├── BEToken.cs │ │ └── BETokenPayload.cs ├── XboxAuthNet.Game.Msal │ ├── Extensions.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── Log.cs │ ├── MsalCacheSettings.cs │ ├── MsalClientHelper.cs │ ├── OAuth │ │ ├── MsalCodeFlowProvider.cs │ │ ├── MsalDeviceCodeOAuth.cs │ │ ├── MsalDeviceCodeProvider.cs │ │ ├── MsalInteractiveOAuth.cs │ │ ├── MsalOAuth.cs │ │ ├── MsalOAuthBuilder.cs │ │ ├── MsalOAuthParameters.cs │ │ └── MsalSilentOAuth.cs │ └── XboxAuthNet.Game.Msal.csproj └── XboxAuthNet.Game │ ├── Accounts │ ├── AccountSaver.cs │ ├── IXboxGameAccount.cs │ ├── IXboxGameAccountManager.cs │ ├── InMemoryXboxGameAccountManager.cs │ ├── JsonXboxGameAccountManager.cs │ ├── XboxGameAccount.cs │ └── XboxGameAccountCollection.cs │ ├── Authenticators │ ├── AuthenticateContext.cs │ ├── AuthenticatorCollection.cs │ ├── CompositeAuthenticatorBase.cs │ ├── FallbackAuthenticator.cs │ ├── IAuthenticator.cs │ ├── ICompositeAuthenticator.cs │ ├── ISessionValidator.cs │ ├── LastAccessLogger.cs │ ├── NestedAuthenticator.cs │ ├── SessionAuthenticator.cs │ ├── SessionCleaner.cs │ ├── SessionValidator.cs │ ├── SessionValidatorCollection.cs │ ├── StaticSessionAuthenticator.cs │ └── StaticValidator.cs │ ├── Extensions.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── HttpHelper.cs │ ├── IAuthenticationProvider.cs │ ├── IsExternalInit.cs │ ├── Jwt │ └── JwtDecoder.cs │ ├── LastAccessSource.cs │ ├── Log.cs │ ├── LoginHandlerParameters.cs │ ├── OAuth │ ├── InteractiveMicrosoftOAuth.cs │ ├── MicrosoftOAuth.cs │ ├── MicrosoftOAuthBuilder.cs │ ├── MicrosoftOAuthClientInfo.cs │ ├── MicrosoftOAuthCodeFlowProvider.cs │ ├── MicrosoftOAuthLoginHintSource.cs │ ├── MicrosoftOAuthLoginHintValidator.cs │ ├── MicrosoftOAuthParameters.cs │ ├── MicrosoftOAuthSessionSource.cs │ ├── MicrosoftOAuthSignout.cs │ ├── MicrosoftOAuthValidator.cs │ └── SilentMicrosoftOAuth.cs │ ├── SessionStorages │ ├── Extensions.cs │ ├── ISessionSource.cs │ ├── ISessionStorage.cs │ ├── InMemorySessionSource.cs │ ├── InMemorySessionStorage.cs │ ├── JsonFileSessionStorage.cs │ ├── JsonSessionStorage.cs │ ├── KeyModeStorage.cs │ ├── SessionFromStorage.cs │ └── SessionStorageKeyMode.cs │ ├── XboxAuth │ ├── XboxAuthBuilder.cs │ ├── XboxAuthTokens.cs │ ├── XboxAuthenticationProviders.cs │ ├── XboxDeviceTokenAuth.cs │ ├── XboxSessionSource.cs │ ├── XboxSessionValidator.cs │ ├── XboxSignedUserTokenAuth.cs │ ├── XboxSisuAuth.cs │ ├── XboxUserTokenAuth.cs │ ├── XboxXstsTokenAuth.cs │ ├── XboxXuiClaimsAuth.cs │ └── XboxXuiClaimsValidator.cs │ ├── XboxAuthNet.Game.csproj │ ├── XboxGameLoginHandler.cs │ └── XboxGameLoginHandlerBuilder.cs └── tests ├── CmlLib.Core.Auth.Microsoft.Test ├── CmlLib.Core.Auth.Microsoft.Test.csproj ├── MsalSample.cs └── Sample.cs ├── CmlLib.Core.Bedrock.Auth.Test ├── CmlLib.Core.Bedrock.Auth.Test.csproj └── Sample.cs └── XboxAuthNet.Game.Test ├── Accounts ├── TestAccount.cs ├── XboxGameAccountCollectionTest.cs └── XboxGameAccountTest.cs ├── Authenticators ├── AuthenticatorCollectionTest.cs ├── FallbackAuthenticatorTest.cs ├── MockAuthenticator.cs ├── MockAuthenticatorFactory.cs ├── NestedAuthenticatorTest.cs ├── StaticSessionAuthenticatorTest.cs └── ThrowingAuthenticator.cs ├── LastAccessSourceTests.cs ├── MicrosoftOAuthBuilderTest.cs ├── MockObjects.cs ├── Program.cs ├── SessionStorages ├── JsonFileSessionStorageTests.cs ├── JsonSessionStorageComparer.cs ├── JsonSessionStorageComparerExecutor.cs └── JsonSessionStorageTests.cs ├── TestJwtDecoder.cs └── XboxAuthNet.Game.Test.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-dotnet 2 | vscode: 3 | extensions: 4 | - muhammad-sammy.csharp -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/tests/XboxAuthNet.Game.Test/bin/Debug/net6.0/XboxAuthNet.Game.Test.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/tests/XboxAuthNet.Game.Test", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "CmlLib.Core.Auth.Microsoft.sln" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/tests/XboxAuthNet.Game.Test/XboxAuthNet.Game.Test.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/tests/XboxAuthNet.Game.Test/XboxAuthNet.Game.Test.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/tests/XboxAuthNet.Game.Test/XboxAuthNet.Game.Test.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 권세인 (AlphaBs) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CmlLib.Core.Auth.Microsoft 2 | 3 | 4 | 5 | [![Nuget Badge](https://img.shields.io/nuget/v/CmlLib.Core.Auth.Microsoft)](https://www.nuget.org/packages/CmlLib.Core.Auth.Microsoft) 6 | [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft/blob/master/LICENSE) 7 | 8 | [![Discord](https://img.shields.io/discord/795952027443527690?label=discord\&logo=discord\&style=for-the-badge)](https://discord.gg/cDW2pvwHSc) 9 | 10 | 11 | 12 | Minecraft login with Microsoft Xbox account 13 | 14 | ## Features 15 | 16 | - Multi Account Management 17 | - Microsoft OAuth with WebView2 18 | - Microsoft OAuth with [MSAL.NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) 19 | - Xbox Authentication 20 | - Minecraft: JE Authentication 21 | - Minecraft: BE Authentication 22 | - Crossplatform (with MSAL.NET) 23 | 24 | ## Install 25 | 26 | Install NuGet package: [CmlLib.Core.Auth.Microsoft](https://www.nuget.org/packages/CmlLib.Core.Auth.Microsoft) 27 | 28 | ## Documentation 29 | 30 | [Documentation](https://alphabs.gitbook.io/cmllib/auth.microsoft/cmllib.core.auth.microsoft) 31 | 32 | [한국어](https://alphabs.gitbook.io/cmllib/v/ko/auth.microsoft/cmllib.core.auth.microsoft) 33 | 34 | ## Example 35 | 36 | [Sample Launcher](https://github.com/CmlLib/CmlLib-Minecraft-Launcher) 37 | -------------------------------------------------------------------------------- /benchmarks/XboxAuthNet.Game.Benchmarks/Benchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | using BenchmarkDotNet; 3 | using BenchmarkDotNet.Attributes; 4 | using XboxAuthNet.Game.SessionStorages; 5 | 6 | namespace XboxAuthNet.Game.Benchmarks 7 | { 8 | public class Benchmarks 9 | { 10 | private InMemorySessionStorage inMemorySessionStorage; 11 | private JsonSessionStorage jsonSessionStorage; 12 | 13 | private string testData; 14 | 15 | [GlobalSetup] 16 | public void GlobalSetup() 17 | { 18 | inMemorySessionStorage = new InMemorySessionStorage(); 19 | jsonSessionStorage = JsonSessionStorage.CreateEmpty(); 20 | 21 | testData = "1234567890"; 22 | 23 | this.inMemorySessionStorage.Set("key", testData); 24 | this.jsonSessionStorage.Set("key", testData); 25 | } 26 | 27 | [Benchmark] 28 | public string GetInMemorySessionStorage() 29 | { 30 | return this.inMemorySessionStorage.Get("key"); 31 | } 32 | 33 | [Benchmark] 34 | public void SetInMemorySessionStorage() 35 | { 36 | this.inMemorySessionStorage.Set("key", testData); 37 | } 38 | 39 | [Benchmark] 40 | public string GetJsonSessionStorage() 41 | { 42 | return this.jsonSessionStorage.Get("key"); 43 | } 44 | 45 | [Benchmark] 46 | public void SetJsonSessionStorage() 47 | { 48 | this.jsonSessionStorage.Set("key", testData); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /benchmarks/XboxAuthNet.Game.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace XboxAuthNet.Game.Benchmarks 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | var summary = BenchmarkRunner.Run(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /benchmarks/XboxAuthNet.Game.Benchmarks/XboxAuthNet.Game.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | 5 | 6 | AnyCPU 7 | pdbonly 8 | true 9 | true 10 | true 11 | Release 12 | false 13 | Exe 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/ConsoleTest/ConsoleTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/WinFormTest/AccountsForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /examples/WinFormTest/AuthForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /examples/WinFormTest/DeviceCodeForm.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | 3 | namespace WinFormTest 4 | { 5 | public partial class DeviceCodeForm : Form 6 | { 7 | private DeviceCodeResult? deviceCode; 8 | 9 | public DeviceCodeForm() 10 | { 11 | InitializeComponent(); 12 | } 13 | 14 | public void SetDeviceCodeResult(DeviceCodeResult code) 15 | { 16 | deviceCode = code; 17 | 18 | timer1.Start(); 19 | lbCode.Text = deviceCode.UserCode; 20 | } 21 | 22 | private void timer1_Tick(object sender, EventArgs e) 23 | { 24 | if (deviceCode == null) 25 | return; 26 | 27 | lbExpire.Text = deviceCode.ExpiresOn.LocalDateTime.Subtract(DateTime.Now).ToString(@"mm\:ss"); 28 | } 29 | 30 | private void button1_Click(object sender, EventArgs e) 31 | { 32 | if (deviceCode == null) 33 | return; 34 | 35 | Util.OpenUrl(deviceCode.VerificationUrl); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/WinFormTest/DeviceCodeForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 17, 17 62 | 63 | -------------------------------------------------------------------------------- /examples/WinFormTest/JEGameAccountProperties.cs: -------------------------------------------------------------------------------- 1 | using CmlLib.Core.Auth.Microsoft.Sessions; 2 | using System.ComponentModel; 3 | 4 | namespace WinFormTest 5 | { 6 | internal class JEGameAccountProperties 7 | { 8 | private readonly JEGameAccount account; 9 | 10 | public JEGameAccountProperties(JEGameAccount account) 11 | { 12 | this.account = account; 13 | } 14 | 15 | [Category("Account")] 16 | public string? Identifier => account.Identifier; 17 | [Category("Account")] 18 | public string? Gamertag => account.Gamertag; 19 | [Category("Account")] 20 | public DateTime? LastAccess => account.LastAccess; 21 | 22 | [Category("Profile")] 23 | public string? Username => account.Profile?.Username; 24 | [Category("Profile")] 25 | public string? UUID => account.Profile?.UUID; 26 | [Category("Profile")] 27 | public object? Skins => account.Profile?.Skins; 28 | [Category("Profile")] 29 | public object? Capes => account.Profile?.Capes; 30 | 31 | [Category("Token")] 32 | public string? TokenUsername => account.Token?.Username; 33 | [Category("Token")] 34 | public string? AccessToken => account.Token?.AccessToken; 35 | [Category("Token")] 36 | public string? TokenType => account.Token?.TokenType; 37 | [Category("Token")] 38 | public int? ExpiresIn => account.Token?.ExpiresIn; 39 | [Category("Token")] 40 | public DateTime? ExpiresOn => account.Token?.ExpiresOn; 41 | [Category("Token")] 42 | public string[]? Roles => account.Token?.Roles; 43 | 44 | public override string ToString() => Identifier ?? string.Empty; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/WinFormTest/JELoginWrapper.cs: -------------------------------------------------------------------------------- 1 | using CmlLib.Core.Auth.Microsoft; 2 | using Microsoft.Identity.Client; 3 | using XboxAuthNet.Game.OAuth; 4 | using XboxAuthNet.Game.Msal; 5 | 6 | namespace WinFormTest; 7 | 8 | public class JELoginWrapper 9 | { 10 | 11 | private static JELoginWrapper? _instance; 12 | public static JELoginWrapper Instance => _instance ??= new JELoginWrapper(); 13 | 14 | private JELoginWrapper() { } 15 | 16 | public JELoginHandler LoginHandler 17 | { 18 | get 19 | { 20 | if (loginHandler == null) 21 | initializeLoginHandler(); 22 | return loginHandler!; 23 | } 24 | } 25 | 26 | public MicrosoftOAuthClientInfo GetOAuthClientInfo(string cid) 27 | { 28 | if (checkOAuthInitializingRequired(cid)) 29 | initializeOAuthClient(cid); 30 | return oauthClient!; 31 | } 32 | 33 | public async Task GetMsalAppAsync(string cid) 34 | { 35 | if (checkMsalInitializingRequired(cid)) 36 | await initializeMsalApp(cid); 37 | return msalApp!; 38 | } 39 | 40 | private JELoginHandler? loginHandler; 41 | private MicrosoftOAuthClientInfo? oauthClient; 42 | private IPublicClientApplication? msalApp; 43 | 44 | private bool checkOAuthInitializingRequired(string cid) 45 | { 46 | return oauthClient?.ClientId != cid; 47 | } 48 | 49 | private void initializeOAuthClient(string cid) 50 | { 51 | oauthClient = new MicrosoftOAuthClientInfo( 52 | cid, JELoginHandler.DefaultMicrosoftOAuthClientInfo.Scopes); 53 | } 54 | 55 | private bool checkMsalInitializingRequired(string cid) 56 | { 57 | return msalApp?.AppConfig?.ClientId != cid; 58 | } 59 | 60 | private async Task initializeMsalApp(string cid) 61 | { 62 | msalApp = await MsalClientHelper.BuildApplicationWithCache(cid); 63 | } 64 | 65 | private void initializeLoginHandler() 66 | { 67 | loginHandler = JELoginHandlerBuilder.BuildDefault(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/WinFormTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | namespace WinFormTest 8 | { 9 | static class Program 10 | { 11 | /// 12 | /// 해당 애플리케이션의 주 진입점입니다. 13 | /// 14 | [STAThread] 15 | static void Main() 16 | { 17 | Application.EnableVisualStyles(); 18 | Application.SetCompatibleTextRenderingDefault(false); 19 | Application.Run(new AccountsForm()); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/WinFormTest/Util.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace WinFormTest 5 | { 6 | internal static class Util 7 | { 8 | // Open URL 9 | // https://stackoverflow.com/a/43232486/13228835 10 | public static void OpenUrl(string url) 11 | { 12 | try 13 | { 14 | Process.Start(url); 15 | } 16 | catch 17 | { 18 | // hack because of this: https://github.com/dotnet/corefx/issues/10361 19 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 20 | { 21 | url = url.Replace("&", "^&"); 22 | Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); 23 | } 24 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 25 | { 26 | Process.Start("xdg-open", url); 27 | } 28 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 29 | { 30 | Process.Start("open", url); 31 | } 32 | else 33 | { 34 | throw; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/WinFormTest/WinFormTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | true 8 | enable 9 | true 10 | latest 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CmlLib/CmlLib.Core.Auth.Microsoft/0268ca8311f696699350141c896796a0ba9f3c9d/icon.png -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/ExceptionHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 4 | 5 | internal static class ExceptionHelper 6 | { 7 | public static Exception CreateException(Exception ex, string resBody, HttpResponseMessage res) 8 | { 9 | if (ex is JsonException || ex is HttpRequestException) 10 | { 11 | try 12 | { 13 | return JEAuthException.FromResponseBody(resBody, (int)res.StatusCode); 14 | } 15 | catch (FormatException) 16 | { 17 | return new JEAuthException($"{(int)res.StatusCode}: {res.ReasonPhrase}"); 18 | } 19 | } 20 | else 21 | return ex; 22 | } 23 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JEAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | using XboxAuthNet.Game.Authenticators; 3 | 4 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 5 | 6 | public class JEAuthenticationProvider : IAuthenticationProvider 7 | { 8 | private readonly JEAuthenticatorBuilder _builder; 9 | 10 | public JEAuthenticationProvider() 11 | { 12 | _builder = new JEAuthenticatorBuilder(); 13 | } 14 | 15 | public JEAuthenticationProvider(JEAuthenticatorBuilder builder) 16 | { 17 | _builder = builder; 18 | } 19 | 20 | public IAuthenticator Authenticate() => _builder.Build(); 21 | public IAuthenticator AuthenticateInteractively() => _builder.Build(); 22 | public IAuthenticator AuthenticateSilently() => _builder.Build(); 23 | public IAuthenticator ClearSession() => _builder.SessionCleaner(); 24 | public ISessionValidator CreateSessionValidator() => _builder.TokenValidator(); 25 | public IAuthenticator Signout() => _builder.SessionCleaner(); 26 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JEAuthenticatorBuilder.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.XboxAuth; 4 | using CmlLib.Core.Auth.Microsoft.Sessions; 5 | using XboxAuthNet.Game; 6 | 7 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 8 | 9 | public class JEAuthenticatorBuilder 10 | { 11 | public bool CheckGameOwnership { get; set; } = false; 12 | 13 | private ISessionSource? _xboxSessionSource; 14 | public ISessionSource XboxSessionSource 15 | { 16 | get => _xboxSessionSource ??= XboxAuthNet.Game.XboxAuth.XboxSessionSource.Default; 17 | set => _xboxSessionSource = value; 18 | } 19 | 20 | private ISessionSource? _tokenSource; 21 | public ISessionSource TokenSource 22 | { 23 | get => _tokenSource ??= JETokenSource.Default; 24 | set => _tokenSource = value; 25 | } 26 | 27 | private ISessionSource? _profileSource; 28 | public ISessionSource ProfileSource 29 | { 30 | get => _profileSource ??= JEProfileSource.Default; 31 | set => _profileSource = value; 32 | } 33 | 34 | public JEAuthenticatorBuilder WithGameOwnershipChecker() => 35 | WithGameOwnershipChecker(true); 36 | 37 | public JEAuthenticatorBuilder WithGameOwnershipChecker(bool value) 38 | { 39 | CheckGameOwnership = value; 40 | return this; 41 | } 42 | 43 | public JEAuthenticatorBuilder WithXboxSessionSource(ISessionSource sessionSource) 44 | { 45 | XboxSessionSource = sessionSource; 46 | return this; 47 | } 48 | 49 | public JEAuthenticatorBuilder WithProfileSource(ISessionSource profileSource) 50 | { 51 | ProfileSource = profileSource; 52 | return this; 53 | } 54 | 55 | public JEAuthenticatorBuilder WithTokenSource(ISessionSource tokenSource) 56 | { 57 | TokenSource = tokenSource; 58 | return this; 59 | } 60 | 61 | public ISessionValidator ProfileValidator() => 62 | new JEProfileValidator(ProfileSource); 63 | 64 | public ISessionValidator TokenValidator() => 65 | new JETokenValidator(TokenSource); 66 | 67 | public ISessionValidator TokenAndProfileValidator() => 68 | new SessionValidatorCollection() 69 | { 70 | new JETokenValidator(TokenSource), 71 | new JEProfileValidator(ProfileSource) 72 | }; 73 | 74 | public IAuthenticator TokenAuthenticator() => 75 | new JETokenAuthenticator(XboxSessionSource, TokenSource); 76 | 77 | public IAuthenticator ProfileAuthenticator() => 78 | new JEProfileAuthenticator(TokenSource, ProfileSource); 79 | 80 | public IAuthenticator GameOwnershipChecker() => 81 | new JEGameOwnershipChecker(TokenSource); 82 | 83 | public IAuthenticator SessionCleaner() 84 | { 85 | var collection = new AuthenticatorCollection(); 86 | collection.AddSessionCleaner(JETokenSource.Default); 87 | collection.AddSessionCleaner(JEProfileSource.Default); 88 | return collection; 89 | } 90 | 91 | public IAuthenticator Build() 92 | { 93 | var collection = new AuthenticatorCollection(); 94 | collection.AddAuthenticator(StaticValidator.Invalid, TokenAuthenticator()); 95 | collection.AddAuthenticator(StaticValidator.Invalid, ProfileAuthenticator()); 96 | if (CheckGameOwnership) 97 | collection.AddAuthenticator(StaticValidator.Invalid, GameOwnershipChecker()); 98 | return collection; 99 | } 100 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JEGameOwnershipChecker.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.SessionStorages; 4 | using CmlLib.Core.Auth.Microsoft.Sessions; 5 | 6 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 7 | 8 | public class JEGameOwnershipChecker : IAuthenticator 9 | { 10 | private readonly ISessionSource _sessionSource; 11 | 12 | public JEGameOwnershipChecker(ISessionSource sessionSource) => 13 | _sessionSource = sessionSource; 14 | 15 | public async ValueTask ExecuteAsync(AuthenticateContext context) 16 | { 17 | context.Logger.LogJEGameOwnershipChecker(); 18 | var token = _sessionSource.Get(context.SessionStorage); 19 | 20 | if (string.IsNullOrEmpty(token?.AccessToken)) 21 | throw new JEAuthException("JEToken.AccessToken was empty. JETokenAuthenticator must run first."); 22 | 23 | var result = await checkGameOwnership(context.HttpClient, token.AccessToken); 24 | if (!result) 25 | throw new JEAuthException("The user doesn't own the game."); 26 | } 27 | 28 | private async ValueTask checkGameOwnership(HttpClient httpClient, string token) 29 | { 30 | var req = new HttpRequestMessage 31 | { 32 | Method = HttpMethod.Get, 33 | RequestUri = new Uri("https://api.minecraftservices.com/entitlements/mcstore"), 34 | }; 35 | req.Headers.Add("Authorization", "Bearer " + token); 36 | 37 | var res = await httpClient.SendAsync(req); 38 | if (!res.IsSuccessStatusCode) 39 | return false; 40 | var resBody = await res.Content.ReadAsStringAsync(); 41 | 42 | try 43 | { 44 | using var jsonDocument = JsonDocument.Parse(resBody); 45 | var root = jsonDocument.RootElement; 46 | 47 | if (root.TryGetProperty("items", out var items)) 48 | return items.EnumerateArray().Any(); 49 | else 50 | return false; 51 | } 52 | catch (JsonException) 53 | { 54 | return false; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JEProfileAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.SessionStorages; 4 | using CmlLib.Core.Auth.Microsoft.Sessions; 5 | 6 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 7 | 8 | public class JEProfileAuthenticator : SessionAuthenticator 9 | { 10 | private readonly ISessionSource _jeSessionSource; 11 | 12 | public JEProfileAuthenticator( 13 | ISessionSource jeSessionSource, 14 | ISessionSource sessionSource) 15 | : base(sessionSource) => 16 | _jeSessionSource = jeSessionSource; 17 | 18 | protected override async ValueTask Authenticate(AuthenticateContext context) 19 | { 20 | context.Logger.LogJEProfileAuthenticator(); 21 | 22 | var token = _jeSessionSource.Get(context.SessionStorage); 23 | if (string.IsNullOrEmpty(token?.AccessToken)) 24 | throw new JEAuthException("JEToken.AccessToken was empty. JETokenAuthenticator must run first."); 25 | 26 | var profile = await requestProfile(token.AccessToken, context.HttpClient); 27 | if (string.IsNullOrEmpty(profile.UUID)) 28 | throw new JEAuthException("The Mojang server returned empty UUID."); 29 | if (string.IsNullOrEmpty(profile.Username)) 30 | throw new JEAuthException("The Mojang server returned empty username."); 31 | 32 | return profile; 33 | } 34 | 35 | private async ValueTask requestProfile(string token, HttpClient httpClient) 36 | { 37 | var req = new HttpRequestMessage 38 | { 39 | Method = HttpMethod.Get, 40 | RequestUri = new Uri("https://api.minecraftservices.com/minecraft/profile") 41 | }; 42 | req.Headers.Add("Authorization", "Bearer " + token); 43 | 44 | var res = await httpClient.SendAsync(req); 45 | var resBody = await res.Content.ReadAsStringAsync(); 46 | 47 | try 48 | { 49 | res.EnsureSuccessStatusCode(); 50 | var profile = JsonSerializer.Deserialize(resBody); 51 | 52 | if (profile == null) 53 | throw new JsonException("The response was null."); 54 | 55 | return profile; 56 | } 57 | catch (Exception ex) 58 | { 59 | throw ExceptionHelper.CreateException(ex, resBody, res); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JEProfileValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using CmlLib.Core.Auth.Microsoft.Sessions; 4 | 5 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 6 | 7 | public class JEProfileValidator : SessionValidator 8 | { 9 | public JEProfileValidator(ISessionSource sessionSource) 10 | : base(sessionSource) 11 | { 12 | 13 | } 14 | 15 | protected override ValueTask Validate(AuthenticateContext context, JEProfile profile) 16 | { 17 | var isValid = ( 18 | !string.IsNullOrEmpty(profile.Username) && 19 | !string.IsNullOrEmpty(profile.UUID)); 20 | context.Logger.LogJEProfileValidator(isValid); 21 | return new ValueTask(isValid); 22 | } 23 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JETokenAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json; 3 | using CmlLib.Core.Auth.Microsoft.Sessions; 4 | using XboxAuthNet.Game.Authenticators; 5 | using XboxAuthNet.Game.SessionStorages; 6 | using XboxAuthNet.Game.XboxAuth; 7 | 8 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 9 | 10 | public class JETokenAuthenticator : SessionAuthenticator 11 | { 12 | private readonly ISessionSource _xboxSessionSource; 13 | 14 | public JETokenAuthenticator( 15 | ISessionSource xboxSessionSource, 16 | ISessionSource sessionSource) 17 | : base(sessionSource) => 18 | _xboxSessionSource = xboxSessionSource; 19 | 20 | protected override async ValueTask Authenticate(AuthenticateContext context) 21 | { 22 | context.Logger.LogJETokenAuthenticator(); 23 | 24 | var xboxTokens = _xboxSessionSource.Get(context.SessionStorage); 25 | var uhs = xboxTokens?.XstsToken?.XuiClaims?.UserHash; 26 | var xsts = xboxTokens?.XstsToken?.Token; 27 | 28 | if (string.IsNullOrEmpty(uhs)) 29 | throw new JEAuthException("UserHash was empty. Xbox authentication is required."); 30 | if (string.IsNullOrEmpty(xsts)) 31 | throw new JEAuthException("XstsToken was empty. Xbox authentication is required."); 32 | 33 | return await requestToken(uhs, xsts, context.HttpClient); 34 | } 35 | 36 | private async ValueTask requestToken(string uhs, string xsts, HttpClient httpClient) 37 | { 38 | var res = await httpClient.SendAsync(new HttpRequestMessage 39 | { 40 | Method = HttpMethod.Post, 41 | RequestUri = new Uri("https://api.minecraftservices.com/authentication/login_with_xbox"), 42 | Content = new StringContent($"{{\"identityToken\": \"XBL3.0 x={uhs};{xsts}\"}}", Encoding.UTF8, "application/json"), 43 | }); 44 | 45 | var resBody = await res.Content.ReadAsStringAsync(); 46 | 47 | try 48 | { 49 | res.EnsureSuccessStatusCode(); 50 | var resObj = JsonSerializer.Deserialize(resBody); 51 | 52 | if (resObj == null && res.IsSuccessStatusCode) 53 | throw new JEAuthException($"{(int)res.StatusCode}: {res.ReasonPhrase}"); 54 | else if (resObj == null) 55 | throw new JEAuthException("The response was null."); 56 | 57 | resObj.ExpiresOn = DateTime.UtcNow.AddSeconds(resObj.ExpiresIn); 58 | return resObj; 59 | } 60 | catch (Exception ex) 61 | { 62 | throw ExceptionHelper.CreateException(ex, resBody, res); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Authenticators/JETokenValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using CmlLib.Core.Auth.Microsoft.Sessions; 4 | 5 | namespace CmlLib.Core.Auth.Microsoft.Authenticators; 6 | 7 | public class JETokenValidator : SessionValidator 8 | { 9 | public JETokenValidator(ISessionSource sessionSource) 10 | : base(sessionSource) 11 | { 12 | 13 | } 14 | 15 | protected override ValueTask Validate(AuthenticateContext context, JEToken token) 16 | { 17 | var valid = (token != null && token.Validate()); 18 | context.Logger.LogJETokenValidator(valid); 19 | return new ValueTask(valid); 20 | } 21 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/CmlLib.Core.Auth.Microsoft.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | netstandard2.0 5 | true 6 | true 7 | 12.0 8 | enable 9 | enable 10 | 11 | 12 | 13 | Minecraft login with Microsoft Xbox Live account 14 | Copyright (c) 2023 AlphaBs 15 | MIT 16 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 17 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 18 | ksi123456ab 19 | CmlLib Minecraft Login Auth Authentication Microsoft Xbox Live XboxLive 20 | 3.2.2 21 | icon.png 22 | git 23 | CmlLib.Core.Auth.Microsoft 24 | README.md 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | all 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Extensions.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | using XboxAuthNet.Game.OAuth; 3 | using XboxAuthNet.Game.XboxAuth; 4 | using XboxAuthNet.Game.Authenticators; 5 | using CmlLib.Core.Auth.Microsoft.Sessions; 6 | using CmlLib.Core.Auth.Microsoft.Authenticators; 7 | using XboxAuthNet.Game.Accounts; 8 | 9 | namespace CmlLib.Core.Auth.Microsoft; 10 | 11 | public static class Extensions 12 | { 13 | public static void AddMicrosoftOAuthForJE( 14 | this ICompositeAuthenticator self, 15 | Func builderInvoker) => 16 | self.AddMicrosoftOAuth(JELoginHandler.DefaultMicrosoftOAuthClientInfo, builderInvoker); 17 | 18 | public static void AddForceMicrosoftOAuthForJE( 19 | this ICompositeAuthenticator self, 20 | Func builderInvoker) => 21 | self.AddForceMicrosoftOAuth(JELoginHandler.DefaultMicrosoftOAuthClientInfo, builderInvoker); 22 | 23 | public static void AddXboxAuthForJE( 24 | this ICompositeAuthenticator self, 25 | Func builderInvoker) => 26 | self.AddXboxAuth(builder => 27 | { 28 | builder.WithRelyingParty(JELoginHandler.RelyingParty); 29 | return builderInvoker(builder); 30 | }); 31 | 32 | public static void AddForceXboxAuthForJE( 33 | this ICompositeAuthenticator self, 34 | Func builderInvoker) => 35 | self.AddForceXboxAuth(builder => 36 | { 37 | builder.WithRelyingParty(JELoginHandler.RelyingParty); 38 | return builderInvoker(builder); 39 | }); 40 | 41 | public static void AddJEAuthenticator(this ICompositeAuthenticator @this) 42 | { 43 | var builder = new JEAuthenticatorBuilder(); 44 | @this.AddAuthenticator(builder.TokenValidator(), builder.TokenAuthenticator()); 45 | @this.AddAuthenticator(StaticValidator.Invalid, builder.ProfileAuthenticator()); 46 | } 47 | 48 | public static void AddJEAuthenticator( 49 | this ICompositeAuthenticator self, 50 | Func authenticatorBuilder) 51 | { 52 | var builder = new JEAuthenticatorBuilder(); 53 | var authenticator = authenticatorBuilder.Invoke(builder); 54 | self.AddAuthenticator(builder.TokenAndProfileValidator(), authenticator); 55 | } 56 | 57 | public static void AddForceJEAuthenticator(this ICompositeAuthenticator self) => 58 | self.AddForceJEAuthenticator(builder => builder.Build()); 59 | 60 | public static void AddForceJEAuthenticator( 61 | this ICompositeAuthenticator self, 62 | Func builderInvoker) 63 | { 64 | var builder = new JEAuthenticatorBuilder(); 65 | var authenticator = builderInvoker.Invoke(builder); 66 | self.AddAuthenticator(StaticValidator.Invalid, authenticator); 67 | } 68 | 69 | public static void AddJESignout(this ICompositeAuthenticator self) 70 | { 71 | var builder = new JEAuthenticatorBuilder(); 72 | self.AddAuthenticatorWithoutValidator(builder.SessionCleaner()); 73 | } 74 | 75 | public static JEGameAccount GetJEAccountByUsername(this XboxGameAccountCollection self, string username) 76 | { 77 | return (JEGameAccount)self.First(account => 78 | { 79 | if (account is JEGameAccount jeAccount) 80 | { 81 | return jeAccount.Profile?.Username == username; 82 | } 83 | else 84 | { 85 | return false; 86 | } 87 | }); 88 | } 89 | 90 | public static async Task ExecuteForLauncherAsync(this NestedAuthenticator self) 91 | { 92 | var session = await self.ExecuteAsync(); 93 | var account = JEGameAccount.FromSessionStorage(session); 94 | return account.ToLauncherSession(); 95 | } 96 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/JEAuthException.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft; 4 | 5 | public class JEAuthException : Exception 6 | { 7 | public JEAuthException(string? message) : base(message) 8 | { 9 | 10 | } 11 | 12 | public JEAuthException(string? error, string? errorType, string? errorMessage, int statusCode) : base(CreateMessageFromError(error, errorType, errorMessage)) => 13 | (Error, ErrorType, ErrorMessage, StatusCode) = (error, errorType, errorMessage, statusCode); 14 | 15 | public JEAuthException(string? message, Exception ex) : base(message, ex) 16 | { 17 | 18 | } 19 | 20 | public int StatusCode { get; private set; } 21 | public string? ErrorType { get; private set; } 22 | public string? Error { get; private set; } 23 | public string? ErrorMessage { get; private set; } 24 | 25 | private static string CreateMessageFromError(string? error, string? errorType, string? errorMessage) 26 | { 27 | if (string.IsNullOrEmpty(error)) 28 | error = errorType; 29 | if (!string.IsNullOrEmpty(error) && !string.IsNullOrEmpty(errorMessage)) 30 | return $"{error}, {errorMessage}"; 31 | if (!string.IsNullOrEmpty(error)) 32 | return error!; 33 | if (!string.IsNullOrEmpty(errorMessage)) 34 | return errorMessage!; 35 | return ""; 36 | } 37 | 38 | public static JEAuthException FromResponseBody(string responseBody, int statusCode) 39 | { 40 | try 41 | { 42 | using var doc = JsonDocument.Parse(responseBody); 43 | var root = doc.RootElement; 44 | 45 | string? error = null; 46 | string? errorType = null; 47 | string? errorMessage = null; 48 | 49 | if (root.TryGetProperty("error", out var errorProp) && 50 | errorProp.ValueKind == JsonValueKind.String) 51 | error = errorProp.GetString(); 52 | if (root.TryGetProperty("errorType", out var errorTypeProp) && 53 | errorTypeProp.ValueKind == JsonValueKind.String) 54 | errorType = errorTypeProp.GetString(); 55 | if (root.TryGetProperty("errorMessage", out var errorMessageProp) && 56 | errorMessageProp.ValueKind == JsonValueKind.String) 57 | errorMessage = errorMessageProp.GetString(); 58 | 59 | if (string.IsNullOrEmpty(error) && string.IsNullOrEmpty(errorType)) 60 | throw new FormatException(); 61 | 62 | return new JEAuthException(error, errorType, errorMessage, statusCode); 63 | } 64 | catch (JsonException) 65 | { 66 | throw new FormatException(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/JELoginHandlerBuilder.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | using XboxAuthNet.Game.Accounts; 3 | using CmlLib.Core.Auth.Microsoft.Sessions; 4 | using System.Threading.Tasks.Sources; 5 | using XboxAuthNet.Game.OAuth; 6 | using XboxAuthNet.Game.XboxAuth; 7 | 8 | namespace CmlLib.Core.Auth.Microsoft; 9 | 10 | public class JELoginHandlerBuilder : 11 | XboxGameLoginHandlerBuilderBase 12 | { 13 | public static JELoginHandler BuildDefault() => 14 | new JELoginHandlerBuilder().Build(); 15 | 16 | private IAuthenticationProvider? oauth; 17 | public IAuthenticationProvider OAuthProvider 18 | { 19 | get => oauth ??= new MicrosoftOAuthCodeFlowProvider(JELoginHandler.DefaultMicrosoftOAuthClientInfo); 20 | set => oauth = value; 21 | } 22 | 23 | private IAuthenticationProvider? xboxAuth; 24 | public IAuthenticationProvider XboxAuthProvider 25 | { 26 | get => xboxAuth ??= new BasicXboxProvider(JELoginHandler.RelyingParty); 27 | set => xboxAuth = value; 28 | } 29 | 30 | public JELoginHandlerBuilder WithOAuthProvider(IAuthenticationProvider provider) 31 | { 32 | OAuthProvider = provider; 33 | return this; 34 | } 35 | 36 | public JELoginHandlerBuilder WithXboxAuthProvider(IAuthenticationProvider provider) 37 | { 38 | XboxAuthProvider = provider; 39 | return this; 40 | } 41 | 42 | public JELoginHandlerBuilder WithAccountManager(string filePath) 43 | { 44 | return WithAccountManager(createAccountManager(filePath)); 45 | } 46 | 47 | protected override IXboxGameAccountManager CreateDefaultAccountManager() 48 | { 49 | var defaultFilePath = Path.Combine(MinecraftPath.GetOSDefaultPath(), "cml_accounts.json"); 50 | return createAccountManager(defaultFilePath); 51 | } 52 | 53 | private IXboxGameAccountManager createAccountManager(string filePath) 54 | { 55 | return new JsonXboxGameAccountManager( 56 | filePath, 57 | JEGameAccount.FromSessionStorage, 58 | JsonXboxGameAccountManager.DefaultSerializerOption); 59 | } 60 | 61 | public JELoginHandler Build() 62 | { 63 | var parameters = BuildParameters(); 64 | return new JELoginHandler(parameters, OAuthProvider, XboxAuthProvider); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft; 4 | 5 | internal static partial class Log 6 | { 7 | [LoggerMessage( 8 | EventId = 751201, 9 | Level = LogLevel.Information, 10 | Message = "Start JEGameOwnershipChecker")] 11 | public static partial void LogJEGameOwnershipChecker(this ILogger logger); 12 | 13 | [LoggerMessage( 14 | EventId = 751202, 15 | Level = LogLevel.Information, 16 | Message = "Start JEProfileAuthenticator")] 17 | public static partial void LogJEProfileAuthenticator(this ILogger logger); 18 | 19 | [LoggerMessage( 20 | EventId =751203, 21 | Level = LogLevel.Information, 22 | Message = "JEProfileValidator result: {result}")] 23 | public static partial void LogJEProfileValidator(this ILogger logger, bool result); 24 | 25 | [LoggerMessage( 26 | EventId =751204, 27 | Level = LogLevel.Information, 28 | Message = "Start JETokenAuthenticator")] 29 | public static partial void LogJETokenAuthenticator(this ILogger logger); 30 | 31 | [LoggerMessage( 32 | EventId = 751205, 33 | Level = LogLevel.Information, 34 | Message = "JETokenValidator result: {result}")] 35 | public static partial void LogJETokenValidator(this ILogger logger, bool result); 36 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JEAccessTokenPayload.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Sessions; 4 | 5 | public class MojangXboxAccessTokenPayload 6 | { 7 | [JsonPropertyName("xuid")] 8 | public string? Xuid { get; set; } 9 | 10 | [JsonPropertyName("agg")] 11 | public string? Agg { get; set; } 12 | 13 | [JsonPropertyName("sub")] 14 | public string? Sub { get; set; } 15 | 16 | [JsonPropertyName("nbf")] 17 | public long Nbf { get; set; } 18 | 19 | [JsonPropertyName("auth")] 20 | public string? Auth { get; set; } 21 | 22 | [JsonPropertyName("iss")] 23 | public string? Iss { get; set; } 24 | 25 | [JsonPropertyName("exp")] 26 | public long Exp { get; set; } 27 | 28 | [JsonPropertyName("iat")] 29 | public long Iat { get; set; } 30 | 31 | [JsonPropertyName("platform")] 32 | public string? Platform { get; set; } 33 | 34 | [JsonPropertyName("yuid")] 35 | public string? Yuid { get; set; } 36 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JEGameAccount.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Accounts; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace CmlLib.Core.Auth.Microsoft.Sessions; 5 | 6 | public class JEGameAccount : XboxGameAccount 7 | { 8 | public new static JEGameAccount FromSessionStorage(ISessionStorage sessionStorage) 9 | { 10 | return new JEGameAccount(sessionStorage); 11 | } 12 | 13 | public JEGameAccount(ISessionStorage sessionStorage) : base(sessionStorage) 14 | { 15 | 16 | } 17 | 18 | public JEProfile? Profile => JEProfileSource.Default.Get(SessionStorage); 19 | public JEToken? Token => JETokenSource.Default.Get(SessionStorage); 20 | 21 | protected override string? GetIdentifier() 22 | { 23 | return Profile?.UUID; 24 | } 25 | 26 | public MSession ToLauncherSession() 27 | { 28 | return new MSession 29 | { 30 | Username = Profile?.Username, 31 | UUID = Profile?.UUID, 32 | AccessToken = Token?.AccessToken, 33 | UserType = "msa", 34 | Xuid = XboxTokens?.XstsToken?.XuiClaims?.XboxUserId 35 | }; 36 | } 37 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JEProfile.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Sessions 4 | { 5 | public class JEProfile 6 | { 7 | [JsonPropertyName("id")] 8 | public string? UUID { get; set; } 9 | 10 | [JsonPropertyName("name")] 11 | public string? Username { get; set; } 12 | 13 | [JsonPropertyName("skins")] 14 | public object? Skins { get; set; } 15 | 16 | [JsonPropertyName("capes")] 17 | public object? Capes { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JEProfileSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Sessions; 4 | 5 | public class JEProfileSource : SessionFromStorage 6 | { 7 | private static JEProfileSource? _default; 8 | public static JEProfileSource Default => _default ??= new(); 9 | 10 | public static string KeyName { get; } = "JEProfile"; 11 | public JEProfileSource() : base(KeyName) 12 | { 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JEToken.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | using XboxAuthNet.Game.Jwt; 4 | 5 | namespace CmlLib.Core.Auth.Microsoft.Sessions; 6 | 7 | public class JEToken 8 | { 9 | [JsonPropertyName("username")] 10 | public string? Username { get; set; } 11 | 12 | [JsonPropertyName("roles")] 13 | public string[]? Roles { get; set; } 14 | 15 | [JsonPropertyName("access_token")] 16 | public string? AccessToken { get; set; } 17 | 18 | [JsonPropertyName("token_type")] 19 | public string? TokenType { get; set; } 20 | 21 | [JsonPropertyName("expires_in")] 22 | public int ExpiresIn { get; set; } 23 | 24 | [JsonPropertyName("expires_on")] 25 | public DateTime ExpiresOn { get; set; } 26 | 27 | /// 28 | /// decode jwt payload of AccessToken 29 | /// 30 | /// 31 | /// AccessToken is null or empty 32 | /// AccessToken is not valid jwt 33 | public MojangXboxAccessTokenPayload? DecodeAccesTokenPayload() 34 | { 35 | if (string.IsNullOrEmpty(this.AccessToken)) 36 | throw new InvalidOperationException("this.AccessToken was empty."); 37 | 38 | return JwtDecoder.DecodePayload(this.AccessToken!); 39 | } 40 | 41 | /// 42 | /// check if access token is valid 43 | /// 44 | /// validation result 45 | public bool Validate() 46 | { 47 | if (string.IsNullOrEmpty(this.AccessToken)) 48 | return false; 49 | 50 | if (this.ExpiresOn <= DateTime.UtcNow || string.IsNullOrEmpty(this.AccessToken)) 51 | return false; 52 | 53 | try 54 | { 55 | var payload = DecodeAccesTokenPayload(); 56 | if (payload == null) 57 | return false; 58 | 59 | var exp = DateTimeOffset.FromUnixTimeSeconds(payload.Exp); 60 | if (exp <= DateTimeOffset.UtcNow) 61 | return false; 62 | } 63 | catch (FormatException) 64 | { 65 | // when jwt payload is not valid base64 string 66 | return false; 67 | } 68 | catch (JsonException) 69 | { 70 | // when jwt payload is not valid json string 71 | return false; 72 | } 73 | catch (ArgumentException) 74 | { 75 | // when exp of jwt is not valid unix timestamp 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | public override string ToString() 83 | { 84 | return this.AccessToken ?? string.Empty; 85 | } 86 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Auth.Microsoft/Sessions/JETokenSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Sessions; 4 | 5 | public class JETokenSource : SessionFromStorage 6 | { 7 | private static JETokenSource? _sessionSource; 8 | public static JETokenSource Default => _sessionSource ??= new(); 9 | 10 | public static string KeyName { get; } = "JEToken"; 11 | public JETokenSource() : base(KeyName) 12 | { 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/BEAuthenticator.cs: -------------------------------------------------------------------------------- 1 | // reference 2 | // https://github.com/PrismarineJS/prismarine-auth/blob/master/src/TokenManagers/MinecraftBedrockTokenManager.js 3 | 4 | using System.Text; 5 | using System.Text.Json; 6 | using Microsoft.Extensions.Logging; 7 | using XboxAuthNet.Game.Authenticators; 8 | using XboxAuthNet.Game.SessionStorages; 9 | using XboxAuthNet.Game.XboxAuth; 10 | using CmlLib.Core.Bedrock.Auth.Sessions; 11 | 12 | namespace CmlLib.Core.Bedrock.Auth; 13 | 14 | public class BEAuthenticator : SessionAuthenticator 15 | { 16 | public static readonly string RelyingParty = "https://multiplayer.minecraft.net/"; 17 | private readonly ISessionSource _xboxSessionSource; 18 | 19 | public BEAuthenticator( 20 | ISessionSource xboxSessionSource, 21 | ISessionSource sessionSource) 22 | : base(sessionSource) => 23 | _xboxSessionSource = xboxSessionSource; 24 | 25 | protected override async ValueTask Authenticate(AuthenticateContext context) 26 | { 27 | var xboxTokens = _xboxSessionSource.Get(context.SessionStorage); 28 | var uhs = xboxTokens?.XstsToken?.XuiClaims?.UserHash; 29 | var xsts = xboxTokens?.XstsToken?.Token; 30 | 31 | if (string.IsNullOrEmpty(uhs) || 32 | string.IsNullOrEmpty(xsts)) 33 | { 34 | throw new BEAuthException("Cannot auth with null UserHash and null Token. Xbox authentication is required."); 35 | } 36 | 37 | context.Logger.LogInformation("Start BEAuthenticator"); 38 | var tokens = await loginWithXbox(uhs, xsts, context.HttpClient); 39 | return new BESession() 40 | { 41 | Tokens = tokens 42 | }; 43 | } 44 | 45 | public async Task loginWithXbox(string uhs, string xsts, HttpClient httpClient) 46 | { 47 | var req = JsonSerializer.Serialize(new 48 | { 49 | identityPublicKey = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEQUeCLz6XuGSZaLldVOoYSZhdT3F371zgus9VMJ5eQoTr1dPjNdN1MtYNOtN1KiWFwxWjqsxNQ4wVkFjCIufCsEYEJgje7Jh9xx37STA0Lq3W3njn8nbJuDUM866vDJAG" 50 | }); 51 | 52 | var msg = new HttpRequestMessage 53 | { 54 | RequestUri = new Uri("https://multiplayer.minecraft.net/authentication"), 55 | Content = new StringContent(req, Encoding.UTF8, "application/json"), 56 | Method = HttpMethod.Post 57 | }; 58 | 59 | msg.Headers.Add("User-Agent", "MCPC/UWP"); 60 | msg.Headers.Add("Authorization", $"XBL3.0 x={uhs};{xsts}"); 61 | 62 | var res = await httpClient.SendAsync(msg); 63 | var resStr = await res.Content.ReadAsStringAsync(); 64 | 65 | try 66 | { 67 | res.EnsureSuccessStatusCode(); 68 | 69 | using var doc = JsonDocument.Parse(resStr); 70 | var chains = doc.RootElement.GetProperty("chain").EnumerateArray(); 71 | 72 | var result = chains 73 | .Select(chain => new BEToken(chain.GetString()!)) 74 | .Where(chain => chain != null) 75 | .ToArray(); 76 | 77 | return result!; 78 | } 79 | catch (Exception ex) 80 | { 81 | throw createException(ex, resStr, res); 82 | } 83 | } 84 | 85 | private Exception createException(Exception ex, string resBody, HttpResponseMessage res) 86 | { 87 | if (ex is JsonException || ex is HttpRequestException) 88 | { 89 | try 90 | { 91 | return BEAuthException.FromResponseBody(resBody, (int)res.StatusCode); 92 | } 93 | catch (FormatException) 94 | { 95 | return new BEAuthException($"{(int)res.StatusCode}: {res.ReasonPhrase}\n{resBody}"); 96 | } 97 | } 98 | else 99 | return ex; 100 | } 101 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/BedrockAuthException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json; 3 | 4 | namespace CmlLib.Core.Bedrock.Auth; 5 | 6 | public class BEAuthException : Exception 7 | { 8 | public BEAuthException(string? message) : base(message) 9 | { 10 | 11 | } 12 | 13 | public BEAuthException(string? error, string? errorMessage) : base($"{error} {errorMessage}") 14 | { 15 | 16 | } 17 | 18 | public static BEAuthException FromResponseBody(string responseBody, int statusCode) 19 | { 20 | try 21 | { 22 | using var doc = JsonDocument.Parse(responseBody); 23 | var root = doc.RootElement; 24 | 25 | string? error = null; 26 | string? errorMessage = null; 27 | 28 | if (root.TryGetProperty("error", out var errorProp) && 29 | errorProp.ValueKind == JsonValueKind.String) 30 | error = errorProp.GetString(); 31 | if (root.TryGetProperty("errorMessage", out var errorMessageProp) && 32 | errorMessageProp.ValueKind == JsonValueKind.String) 33 | errorMessage = errorMessageProp.GetString(); 34 | 35 | if (string.IsNullOrEmpty(error)) 36 | throw new FormatException(); 37 | 38 | return new BEAuthException(error, errorMessage); 39 | } 40 | catch (JsonException) 41 | { 42 | throw new FormatException(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/CmlLib.Core.Bedrock.Auth.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | netstandard2.0 5 | true 6 | true 7 | 10.0 8 | enable 9 | enable 10 | 11 | 12 | 13 | 0.0.7 14 | Copyright (c) 2023 AlphaBs 15 | MIT 16 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 17 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 18 | ksi123456ab 19 | CmlLib Minecraft Login Auth Authentication Microsoft Xbox Live XboxLive 20 | icon.png 21 | git 22 | CmlLib.Core.Bedrock.Auth 23 | README.md 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Extensions.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | using XboxAuthNet.Game.XboxAuth; 3 | using XboxAuthNet.Game.Authenticators; 4 | using CmlLib.Core.Bedrock.Auth.Sessions; 5 | 6 | namespace CmlLib.Core.Bedrock.Auth; 7 | 8 | public static class Extensions 9 | { 10 | public static void AddXboxAuthForBE( 11 | this ICompositeAuthenticator self, 12 | Func builderInvoker) => 13 | self.AddXboxAuth(builder => 14 | { 15 | builder.RelyingParty = BEAuthenticator.RelyingParty; 16 | return builderInvoker.Invoke(builder); 17 | }); 18 | 19 | public static void AddForceXboxAuthForBE( 20 | this ICompositeAuthenticator self, 21 | Func builderInvoker) => 22 | self.AddForceXboxAuth(builder => 23 | { 24 | builder.RelyingParty = BEAuthenticator.RelyingParty; 25 | return builderInvoker.Invoke(builder); 26 | }); 27 | 28 | public static void AddBEAuthenticator(this ICompositeAuthenticator self) 29 | { 30 | var authenticator = new BEAuthenticator( 31 | XboxSessionSource.Default, 32 | BESessionSource.Default); 33 | self.AddAuthenticatorWithoutValidator(authenticator); 34 | } 35 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Sessions/BEGameAccount.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Accounts; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace CmlLib.Core.Bedrock.Auth.Sessions; 5 | 6 | public class BEGameAccount : XboxGameAccount 7 | { 8 | public new static BEGameAccount FromSessionStorage(ISessionStorage sessionStorage) => 9 | new BEGameAccount(sessionStorage); 10 | 11 | public BEGameAccount(ISessionStorage sessionStorage) : base(sessionStorage) 12 | { 13 | 14 | } 15 | 16 | public BESession? Session => BESessionSource.Default.Get(SessionStorage); 17 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Sessions/BESession.cs: -------------------------------------------------------------------------------- 1 | namespace CmlLib.Core.Bedrock.Auth.Sessions; 2 | 3 | public class BESession 4 | { 5 | public BEToken[]? Tokens { get; set; } 6 | 7 | public bool Validate() 8 | { 9 | return (Tokens != null) 10 | && (Tokens.Length > 0); 11 | } 12 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Sessions/BESessionSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace CmlLib.Core.Bedrock.Auth.Sessions; 4 | 5 | public class BESessionSource : SessionFromStorage 6 | { 7 | private static BESessionSource? _default; 8 | public static BESessionSource Default => _default ??= new(); 9 | 10 | public static readonly string KeyName = "BESession"; 11 | public BESessionSource() : base(KeyName) 12 | { 13 | 14 | } 15 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Sessions/BEToken.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Jwt; 2 | using System.Text.Json; 3 | 4 | namespace CmlLib.Core.Bedrock.Auth.Sessions; 5 | 6 | public class BEToken 7 | { 8 | public BEToken(string token) 9 | { 10 | this.Token = token; 11 | } 12 | 13 | public string Token { get; } 14 | 15 | public BETokenPayload? DecodeTokenPayload() 16 | { 17 | if (string.IsNullOrEmpty(Token)) 18 | throw new InvalidOperationException("Token was empty"); 19 | 20 | var payload = JwtDecoder.DecodePayload(Token); 21 | return payload; 22 | } 23 | 24 | public bool CheckValidation() 25 | { 26 | if (string.IsNullOrEmpty(Token)) 27 | return false; 28 | 29 | try 30 | { 31 | var payload = DecodeTokenPayload(); 32 | if (payload == null) 33 | return false; 34 | 35 | var exp = DateTimeOffset.FromUnixTimeSeconds(payload.Expire); 36 | if (exp <= DateTimeOffset.UtcNow) 37 | return false; 38 | } 39 | catch (FormatException) 40 | { 41 | // when jwt payload is not valid base64 string 42 | return false; 43 | } 44 | catch (JsonException) 45 | { 46 | // when jwt payload is not valid json string 47 | return false; 48 | } 49 | catch (ArgumentException) 50 | { 51 | // when exp of jwt is not valid unix timestamp 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | } -------------------------------------------------------------------------------- /src/CmlLib.Core.Bedrock.Auth/Sessions/BETokenPayload.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace CmlLib.Core.Bedrock.Auth.Sessions; 4 | 5 | public class BETokenPayload 6 | { 7 | [JsonPropertyName("extraData")] 8 | public BETokenExtraData? ExtraData { get; set; } 9 | 10 | [JsonPropertyName("nbf")] 11 | public long NotBefore { get; set; } 12 | 13 | [JsonPropertyName("randomNonce")] 14 | public long RandomNonce { get; set; } 15 | 16 | [JsonPropertyName("iss")] 17 | public string? Issuer { get; set; } 18 | 19 | [JsonPropertyName("exp")] 20 | public long Expire { get; set; } 21 | 22 | [JsonPropertyName("iat")] 23 | public long IssuedAt { get; set; } 24 | 25 | [JsonPropertyName("identityPublicKey")] 26 | public string? IdentityPublicKey { get; set; } 27 | } 28 | 29 | public class BETokenExtraData 30 | { 31 | [JsonPropertyName("XUID")] 32 | public string? XboxUserId { get; set; } 33 | 34 | [JsonPropertyName("identity")] 35 | public string? Identity { get; set; } 36 | 37 | [JsonPropertyName("displayName")] 38 | public string? DisplayName { get; set; } 39 | 40 | [JsonPropertyName("titleId")] 41 | public string? TitleId { get; set; } 42 | } 43 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/Extensions.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using Microsoft.Identity.Client; 3 | 4 | namespace XboxAuthNet.Game.Msal; 5 | 6 | public static class Extensions 7 | { 8 | public static void AddMsalOAuth( 9 | this ICompositeAuthenticator self, 10 | IPublicClientApplication app, 11 | Func builderInvoker) 12 | { 13 | var builder = new MsalOAuthBuilder(app); 14 | var authenticator = builderInvoker.Invoke(builder); 15 | self.AddAuthenticatorWithoutValidator(authenticator); 16 | } 17 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace XboxAuthNet.Game.Msal; 4 | 5 | public static partial class Log 6 | { 7 | [LoggerMessage( 8 | EventId = 750201, 9 | Level = LogLevel.Information, 10 | Message = "Start MsalDeviceCodeOAuth")] 11 | public static partial void LogMsalDeviceCode(this ILogger logger); 12 | 13 | [LoggerMessage( 14 | EventId = 750202, 15 | Level = LogLevel.Information, 16 | Message = "Start MsalInteractiveOAuth")] 17 | public static partial void LogMsalInteractiveOAuth(this ILogger logger); 18 | 19 | [LoggerMessage( 20 | EventId =750203, 21 | Level = LogLevel.Information, 22 | Message = "Start MsalSilentOAuth: {loginHint}")] 23 | public static partial void LogMsalSilentOAuth(this ILogger logger, string? loginHint); 24 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/MsalCacheSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client.Extensions.Msal; 2 | 3 | namespace XboxAuthNet.Game.Msal; 4 | 5 | public class MsalCacheSettings 6 | { 7 | public string CacheFileName { get; set; } = "cmllib_msal_cache.txt"; 8 | public string CacheDir { get; set; } = MsalCacheHelper.UserRootDirectory; 9 | 10 | public string KeyChainServiceName { get; set; } = "cmllib_msal_service"; 11 | public string KeyChainAccountName { get; set; } = "cmllib_msal_account"; 12 | 13 | public string LinuxKeyRingSchema { get; set; } = "com.github.cmllib.tokencache"; 14 | public string LinuxKeyRingCollection { get; set; } = MsalCacheHelper.LinuxKeyRingDefaultCollection; 15 | public string LinuxKeyRingLabel { get; set; } = "MSAL token cache for Minecraft launcher based on CmlLib.Core"; 16 | public KeyValuePair LinuxKeyRingAttr1 { get; set; } = new KeyValuePair("Version", "1"); 17 | public KeyValuePair LinuxKeyRingAttr2 { get; set; } = new KeyValuePair("ProductGroup", "CmlLib.Core"); 18 | 19 | public StorageCreationPropertiesBuilder ToStorageCreationPropertiesBuilder() 20 | { 21 | return new StorageCreationPropertiesBuilder(CacheFileName, CacheDir) 22 | .WithLinuxKeyring( 23 | LinuxKeyRingSchema, 24 | LinuxKeyRingCollection, 25 | LinuxKeyRingLabel, 26 | LinuxKeyRingAttr1, 27 | LinuxKeyRingAttr2) 28 | .WithMacKeyChain( 29 | KeyChainServiceName, 30 | KeyChainAccountName); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/MsalClientHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using Microsoft.Identity.Client.Extensions.Msal; 3 | using XboxAuthNet.OAuth; 4 | 5 | namespace XboxAuthNet.Game.Msal; 6 | 7 | public static class MsalClientHelper 8 | { 9 | public static readonly string[] XboxScopes = new[] { "XboxLive.signin" }; 10 | 11 | public static PublicClientApplicationBuilder CreateDefaultApplicationBuilder(string cid) 12 | => PublicClientApplicationBuilder.Create(cid) 13 | .WithTenantId("consumers") 14 | .WithRedirectUri("http://localhost"); 15 | 16 | public static Task RegisterCache(IPublicClientApplication app, MsalCacheSettings cacheSettings) 17 | => RegisterCache(app, cacheSettings.ToStorageCreationPropertiesBuilder().Build()); 18 | 19 | public async static Task RegisterCache(IPublicClientApplication app, StorageCreationProperties storageProperties) 20 | { 21 | var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties); 22 | cacheHelper.RegisterCache(app.UserTokenCache); 23 | } 24 | 25 | public static IPublicClientApplication BuildApplication(string cid) 26 | => CreateDefaultApplicationBuilder(cid).Build(); 27 | 28 | public static Task BuildApplicationWithCache(string cid) 29 | => BuildApplicationWithCache(cid, new MsalCacheSettings()); 30 | 31 | public async static Task BuildApplicationWithCache(string cid, StorageCreationProperties storageProperties) 32 | { 33 | var app = BuildApplication(cid); 34 | await RegisterCache(app, storageProperties); 35 | return app; 36 | } 37 | 38 | public async static Task BuildApplicationWithCache(string cid, MsalCacheSettings cacheSettings) 39 | { 40 | var storageProperties = cacheSettings.ToStorageCreationPropertiesBuilder().Build(); 41 | return await BuildApplicationWithCache(cid, storageProperties); 42 | } 43 | 44 | public static MicrosoftOAuthResponse ToMicrosoftOAuthResponse(AuthenticationResult result) 45 | { 46 | return new MicrosoftOAuthResponse 47 | { 48 | AccessToken = "d=" + result.AccessToken, // token prefix 49 | TokenType = result.TokenType, 50 | Scope = string.Join(",", result.Scopes) 51 | }; 52 | } 53 | 54 | public static async Task RemoveAccounts(IPublicClientApplication app) 55 | { 56 | var accounts = await app.GetAccountsAsync(); 57 | while (accounts.Any()) 58 | { 59 | var first = accounts.First(); 60 | await app.RemoveAsync(first); 61 | accounts = await app.GetAccountsAsync(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalCodeFlowProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using XboxAuthNet.Game.Authenticators; 3 | 4 | namespace XboxAuthNet.Game.Msal.OAuth; 5 | 6 | public class MsalCodeFlowProvider : IAuthenticationProvider 7 | { 8 | private readonly MsalOAuthBuilder _builder; 9 | 10 | public MsalCodeFlowProvider(IPublicClientApplication app) 11 | { 12 | _builder = new MsalOAuthBuilder(app); 13 | } 14 | 15 | public MsalCodeFlowProvider(MsalOAuthBuilder builder) 16 | { 17 | _builder = builder; 18 | } 19 | 20 | public IAuthenticator Authenticate() => _builder.CodeFlow(); 21 | public IAuthenticator AuthenticateInteractively() => _builder.Interactive(); 22 | public IAuthenticator AuthenticateSilently() => _builder.Silent(); 23 | public ISessionValidator CreateSessionValidator() => StaticValidator.Invalid; 24 | public IAuthenticator ClearSession() => _builder.ClearSession(); 25 | public IAuthenticator Signout() => _builder.ClearSession(); 26 | } 27 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalDeviceCodeOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using Microsoft.Identity.Client; 3 | 4 | namespace XboxAuthNet.Game.Msal.OAuth; 5 | 6 | public class MsalDeviceCodeOAuth : MsalOAuth 7 | { 8 | private readonly Func _deviceCodeResultCallback; 9 | 10 | public MsalDeviceCodeOAuth( 11 | MsalOAuthParameters parameters, 12 | Func deviceCodeResultCallback) 13 | : base(parameters) => 14 | _deviceCodeResultCallback = deviceCodeResultCallback; 15 | 16 | protected override async ValueTask AuthenticateWithMsal( 17 | AuthenticateContext context, MsalOAuthParameters parameters) 18 | { 19 | context.Logger.LogMsalDeviceCode(); 20 | var result = await parameters.MsalApplication 21 | .AcquireTokenWithDeviceCode(parameters.Scopes, _deviceCodeResultCallback) 22 | .ExecuteAsync(context.CancellationToken); 23 | return result; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalDeviceCodeProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using XboxAuthNet.Game.Authenticators; 3 | 4 | namespace XboxAuthNet.Game.Msal.OAuth; 5 | 6 | public class MsalDeviceCodeProvider : IAuthenticationProvider 7 | { 8 | private readonly MsalOAuthBuilder _builder; 9 | private readonly Func _callback; 10 | 11 | public MsalDeviceCodeProvider(IPublicClientApplication app, Func callback) 12 | { 13 | _builder = new MsalOAuthBuilder(app); 14 | _callback = callback; 15 | } 16 | 17 | public MsalDeviceCodeProvider(MsalOAuthBuilder builder, Func callback) 18 | { 19 | _builder = builder; 20 | _callback = callback; 21 | } 22 | 23 | public IAuthenticator Authenticate() 24 | { 25 | var authenticator = new FallbackAuthenticator(); 26 | authenticator.AddAuthenticatorWithoutValidator(_builder.Silent()); 27 | authenticator.AddAuthenticatorWithoutValidator(_builder.DeviceCode(_callback)); 28 | return authenticator; 29 | } 30 | 31 | public IAuthenticator AuthenticateInteractively() => _builder.DeviceCode(_callback); 32 | public IAuthenticator AuthenticateSilently() => _builder.Silent(); 33 | public ISessionValidator CreateSessionValidator() => StaticValidator.Invalid; 34 | public IAuthenticator ClearSession() => _builder.ClearSession(); 35 | public IAuthenticator Signout() => _builder.ClearSession(); 36 | } 37 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalInteractiveOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using Microsoft.Identity.Client; 3 | 4 | namespace XboxAuthNet.Game.Msal.OAuth; 5 | 6 | public class MsalInteractiveOAuth : MsalOAuth 7 | { 8 | private readonly Action _builderInvoker; 9 | 10 | public MsalInteractiveOAuth( 11 | MsalOAuthParameters parameters, 12 | Action builderInvoker) : 13 | base(parameters) 14 | { 15 | this._builderInvoker = builderInvoker; 16 | } 17 | 18 | protected override async ValueTask AuthenticateWithMsal( 19 | AuthenticateContext context, MsalOAuthParameters parameters) 20 | { 21 | context.Logger.LogMsalInteractiveOAuth(); 22 | 23 | var builder = parameters.MsalApplication 24 | .AcquireTokenInteractive(parameters.Scopes); 25 | 26 | var loginHint = parameters.LoginHintSource.Get(context.SessionStorage); 27 | if (!string.IsNullOrEmpty(loginHint)) 28 | builder.WithLoginHint(loginHint); 29 | 30 | _builderInvoker.Invoke(builder); 31 | 32 | var result = await builder.ExecuteAsync(context.CancellationToken); 33 | return result; 34 | } 35 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.OAuth; 3 | using Microsoft.Identity.Client; 4 | 5 | namespace XboxAuthNet.Game.Msal.OAuth; 6 | 7 | public abstract class MsalOAuth : SessionAuthenticator 8 | { 9 | private readonly MsalOAuthParameters _parameters; 10 | 11 | public MsalOAuth( 12 | MsalOAuthParameters parameters) 13 | : base(parameters.SessionSource) => 14 | _parameters = parameters; 15 | 16 | protected override async ValueTask Authenticate(AuthenticateContext context) 17 | { 18 | var result = await AuthenticateWithMsal(context, _parameters); 19 | 20 | var loginHint = result.Account.Username; 21 | _parameters.LoginHintSource.Set(context.SessionStorage, loginHint); 22 | _parameters.SessionSource.SetKeyMode(context.SessionStorage, SessionStorages.SessionStorageKeyMode.NoStore); 23 | 24 | return MsalClientHelper.ToMicrosoftOAuthResponse(result); 25 | } 26 | 27 | protected abstract ValueTask AuthenticateWithMsal( 28 | AuthenticateContext context, MsalOAuthParameters parameters); 29 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalOAuthParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Identity.Client; 2 | using XboxAuthNet.OAuth; 3 | using XboxAuthNet.Game.SessionStorages; 4 | 5 | namespace XboxAuthNet.Game.Msal; 6 | 7 | public class MsalOAuthParameters 8 | { 9 | public MsalOAuthParameters( 10 | IPublicClientApplication app, 11 | string[] scopes, 12 | ISessionSource loginHintSource, 13 | bool throwWhenEmptyLoginHint, 14 | ISessionSource sessionSource) => 15 | (MsalApplication, Scopes, LoginHintSource, ThrowWhenEmptyLoginHint, SessionSource) = 16 | (app, scopes, loginHintSource, throwWhenEmptyLoginHint, sessionSource); 17 | 18 | public IPublicClientApplication MsalApplication { get; } 19 | public string[] Scopes { get; } 20 | public ISessionSource SessionSource; 21 | public ISessionSource LoginHintSource { get; } 22 | public bool ThrowWhenEmptyLoginHint { get; } 23 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/OAuth/MsalSilentOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using Microsoft.Identity.Client; 3 | 4 | namespace XboxAuthNet.Game.Msal.OAuth; 5 | 6 | public class MsalSilentOAuth : MsalOAuth 7 | { 8 | public MsalSilentOAuth(MsalOAuthParameters parameters) : 9 | base(parameters) 10 | { 11 | 12 | } 13 | 14 | protected override async ValueTask AuthenticateWithMsal( 15 | AuthenticateContext context, MsalOAuthParameters parameters) 16 | { 17 | var loginHint = parameters.LoginHintSource.Get(context.SessionStorage); 18 | context.Logger.LogMsalSilentOAuth(loginHint); 19 | 20 | if (string.IsNullOrEmpty(loginHint)) 21 | throw new MsalException("loginHint was empty. Interactive Microsoft OAuth with IdToken is required. (ex: MsalInteractiveOAuth)"); 22 | 23 | return await parameters.MsalApplication 24 | .AcquireTokenSilent(parameters.Scopes, loginHint) 25 | .ExecuteAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game.Msal/XboxAuthNet.Game.Msal.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | netstandard2.0 6 | true 7 | true 8 | 10.0 9 | enable 10 | enable 11 | 12 | 13 | 14 | XboxAuthNet.Game extension for MSAL.NET 15 | Copyright (c) 2023 AlphaBs 16 | MIT 17 | https://github.com/AlphaBs/XboxAuthNet 18 | https://github.com/AlphaBs/XboxAuthNet 19 | ksi123456ab 20 | login authentication msal microsoft xbox game 21 | 0.1.2 22 | icon.png 23 | git 24 | XboxAuthNet.Game.Msal 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | all 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/AccountSaver.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game.Accounts; 4 | 5 | public class AccountSaver : IAuthenticator 6 | { 7 | private readonly IXboxGameAccountManager _accountManager; 8 | 9 | public AccountSaver(IXboxGameAccountManager accountManager) => 10 | _accountManager = accountManager; 11 | 12 | public ValueTask ExecuteAsync(AuthenticateContext context) 13 | { 14 | context.Logger.LogSaveAccounts(); 15 | _accountManager.SaveAccounts(); 16 | return new ValueTask(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/IXboxGameAccount.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Accounts; 4 | 5 | public interface IXboxGameAccount : IComparable 6 | { 7 | string? Identifier { get; } 8 | ISessionStorage SessionStorage { get; } 9 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/IXboxGameAccountManager.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Accounts; 2 | 3 | public interface IXboxGameAccountManager 4 | { 5 | XboxGameAccountCollection GetAccounts(); 6 | IXboxGameAccount GetDefaultAccount(); 7 | IXboxGameAccount NewAccount(); 8 | void ClearAccounts(); 9 | void SaveAccounts(); 10 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/InMemoryXboxGameAccountManager.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Accounts; 4 | 5 | public class InMemoryXboxGameAccountManager : IXboxGameAccountManager 6 | { 7 | private Func _converter; 8 | 9 | public InMemoryXboxGameAccountManager(Func converter) 10 | { 11 | _converter = converter; 12 | Accounts = new XboxGameAccountCollection(); 13 | } 14 | 15 | public XboxGameAccountCollection Accounts { get; private set; } 16 | 17 | public XboxGameAccountCollection GetAccounts() => Accounts; 18 | 19 | public IXboxGameAccount GetDefaultAccount() 20 | { 21 | var account = Accounts.FirstOrDefault(); 22 | return account ?? NewAccount(); 23 | } 24 | 25 | public IXboxGameAccount NewAccount() 26 | { 27 | var sessionStorage = new InMemorySessionStorage(); 28 | var account = _converter.Invoke(sessionStorage); 29 | return account; 30 | } 31 | 32 | public void ClearAccounts() 33 | { 34 | Accounts.Clear(); 35 | } 36 | 37 | public void LoadAccounts() 38 | { 39 | // Accounts are already in memory 40 | } 41 | 42 | public void SaveAccounts() 43 | { 44 | foreach (var account in Accounts) 45 | { 46 | removeNoStore(account.SessionStorage); 47 | } 48 | } 49 | 50 | private void removeNoStore(ISessionStorage sessionStorage) 51 | { 52 | foreach (var key in sessionStorage.Keys) 53 | { 54 | if (sessionStorage.GetKeyMode(key) == SessionStorageKeyMode.NoStore) 55 | sessionStorage.Remove(key); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/XboxGameAccount.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | using XboxAuthNet.Game.XboxAuth; 3 | 4 | namespace XboxAuthNet.Game.Accounts; 5 | 6 | public class XboxGameAccount : IXboxGameAccount 7 | { 8 | public static XboxGameAccount FromSessionStorage(ISessionStorage sessionStorage) 9 | { 10 | return new XboxGameAccount(sessionStorage); 11 | } 12 | 13 | public XboxGameAccount(ISessionStorage sessionStorage) 14 | { 15 | this.SessionStorage = sessionStorage; 16 | } 17 | 18 | public string? Identifier => GetIdentifier(); 19 | public ISessionStorage SessionStorage { get; } 20 | public XboxAuthTokens? XboxTokens => XboxSessionSource.Default.Get(SessionStorage); 21 | public string? Gamertag => XboxTokens?.XstsToken?.XuiClaims?.Gamertag; 22 | public DateTime LastAccess => LastAccessSource.Default.Get(SessionStorage); 23 | 24 | protected virtual string? GetIdentifier() 25 | { 26 | var uhs = XboxTokens?.XstsToken?.XuiClaims?.UserHash; 27 | return uhs; 28 | } 29 | 30 | public int CompareTo(object? other) 31 | { 32 | // -1: this instance precedes other 33 | // 0: same position 34 | // 1: this instance follows other or other is not a valid object 35 | 36 | if (other is not XboxGameAccount account) 37 | return 1; 38 | 39 | return LastAccess.CompareTo(account.LastAccess); 40 | } 41 | 42 | public override bool Equals(object? obj) 43 | { 44 | if (obj is XboxGameAccount account) 45 | { 46 | return account.Identifier == Identifier; 47 | } 48 | else if (obj is string) 49 | { 50 | return obj.Equals(Identifier); 51 | } 52 | else 53 | { 54 | return false; 55 | } 56 | } 57 | 58 | public override int GetHashCode() 59 | { 60 | return Identifier?.GetHashCode() ?? 0; 61 | } 62 | 63 | public override string ToString() 64 | { 65 | return Identifier ?? string.Empty; 66 | } 67 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Accounts/XboxGameAccountCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.Accounts; 5 | 6 | public class XboxGameAccountCollection : ICollection 7 | { 8 | private readonly List _accounts = new(); 9 | 10 | public int Count => getAccounts().Count(); 11 | public bool IsReadOnly => false; 12 | 13 | private IEnumerable getAccounts() 14 | { 15 | // 1) Ignore accounts which has empty Identifier 16 | // 2) Remove duplicated accounts which has same Identifier 17 | // 2-1) If two accounts has same identifier, remove old ones and take the most recent one 18 | // 3) Order by the most recently accessed account 19 | 20 | return _accounts 21 | .Where(account => !string.IsNullOrEmpty(account.Identifier)) 22 | .GroupBy(account => account.Identifier) 23 | .Select(group => group.OrderByDescending(_ => _).First()) 24 | .OrderByDescending(_ => _); 25 | } 26 | 27 | public IXboxGameAccount GetAccount(string identifier) 28 | { 29 | if (string.IsNullOrEmpty(identifier)) 30 | throw new ArgumentNullException(nameof(identifier)); 31 | 32 | var account = getAccount(identifier); 33 | if (account != null) 34 | return account; 35 | throw new KeyNotFoundException("Cannot find any account with the specified identifier: " + identifier); 36 | } 37 | 38 | public bool TryGetAccount(string identifier, out IXboxGameAccount account) 39 | { 40 | account = getAccount(identifier)!; 41 | return account != null; 42 | } 43 | 44 | private IXboxGameAccount? getAccount(string identifier) 45 | { 46 | return _accounts 47 | .Where(account => account.Identifier == identifier) 48 | .OrderByDescending(_ => _) 49 | .FirstOrDefault(); 50 | } 51 | 52 | public void Add(IXboxGameAccount account) 53 | { 54 | _accounts.Add(account); 55 | } 56 | 57 | public bool Remove(IXboxGameAccount account) 58 | { 59 | return _accounts.Remove(account); 60 | } 61 | 62 | public bool RemoveAccount(string identifier) 63 | { 64 | var result = false; 65 | 66 | while (true) 67 | { 68 | var findIndex = _accounts.FindIndex(0, account => account.Identifier == identifier); 69 | if (findIndex == -1) 70 | break; 71 | 72 | _accounts.RemoveAt(findIndex); 73 | result = true; 74 | } 75 | 76 | return result; 77 | } 78 | 79 | public void Clear() 80 | { 81 | _accounts.Clear(); 82 | } 83 | 84 | public bool Contains(IXboxGameAccount toFind) 85 | { 86 | return _accounts.Exists(account => account.Equals(toFind)); 87 | } 88 | 89 | public void CopyTo(IXboxGameAccount[] array, int startIndex) 90 | { 91 | if (array == null) 92 | throw new ArgumentNullException(nameof(array)); 93 | if (startIndex < 0) 94 | throw new ArgumentOutOfRangeException(nameof(startIndex), "The start index cannot be negative."); 95 | 96 | var accounts = getAccounts().ToArray(); 97 | if (startIndex + accounts.Length > array.Length) 98 | throw new ArgumentException("The number of elements in the source collection exceeds the available space in the array."); // generated by chatGPT 99 | 100 | Array.Copy(accounts, 0, array, startIndex, accounts.Length); 101 | } 102 | 103 | public IEnumerable ToSessionStorages() 104 | { 105 | return getAccounts().Select(account => account.SessionStorage); 106 | } 107 | 108 | public IEnumerator GetEnumerator() 109 | { 110 | return getAccounts().GetEnumerator(); 111 | } 112 | 113 | IEnumerator IEnumerable.GetEnumerator() 114 | { 115 | return GetEnumerator(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/AuthenticateContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.Authenticators; 5 | 6 | public class AuthenticateContext 7 | { 8 | public AuthenticateContext( 9 | ISessionStorage sessionStorage, 10 | HttpClient httpClient, 11 | CancellationToken cancellationToken, 12 | ILogger logger) => 13 | (CancellationToken, SessionStorage, HttpClient, Logger) = 14 | (cancellationToken, sessionStorage, httpClient, logger); 15 | 16 | public CancellationToken CancellationToken { get; } 17 | public ISessionStorage SessionStorage { get; } 18 | public HttpClient HttpClient { get; } 19 | public ILogger Logger { get; } 20 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/AuthenticatorCollection.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public class AuthenticatorCollection : CompositeAuthenticatorBase 4 | { 5 | public override async ValueTask ExecuteAsync(AuthenticateContext context) 6 | { 7 | for (int i = 0; i < Authenticators.Count(); i++) 8 | { 9 | context.CancellationToken.ThrowIfCancellationRequested(); 10 | 11 | var valid = await Validators.ElementAt(i).Validate(context); 12 | if (!valid) 13 | await Authenticators.ElementAt(i).ExecuteAsync(context); 14 | } 15 | 16 | await ExecutePostAuthenticators(context); 17 | } 18 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/CompositeAuthenticatorBase.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public abstract class CompositeAuthenticatorBase : ICompositeAuthenticator 4 | { 5 | private readonly List _validators = new(); 6 | private readonly List _authenticators = new(); 7 | private readonly List _posts = new(); 8 | 9 | protected IEnumerable Validators => _validators; 10 | protected IEnumerable Authenticators => _authenticators; 11 | protected IEnumerable PostAuthenticators => _posts; 12 | 13 | public void AddAuthenticator(ISessionValidator validator, IAuthenticator authenticator) 14 | { 15 | _validators.Add(validator); 16 | _authenticators.Add(authenticator); 17 | } 18 | 19 | public void AddPostAuthenticator(IAuthenticator authenticator) 20 | { 21 | _posts.Add(authenticator); 22 | } 23 | 24 | public void Clear() 25 | { 26 | _validators.Clear(); 27 | _authenticators.Clear(); 28 | _posts.Clear(); 29 | } 30 | 31 | public abstract ValueTask ExecuteAsync(AuthenticateContext context); 32 | 33 | protected async ValueTask ExecutePostAuthenticators(AuthenticateContext context) 34 | { 35 | foreach (var authenticator in PostAuthenticators) 36 | { 37 | context.CancellationToken.ThrowIfCancellationRequested(); 38 | await authenticator.ExecuteAsync(context); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/FallbackAuthenticator.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public class FallbackAuthenticator : CompositeAuthenticatorBase 4 | { 5 | private readonly Type[] _catchExceptions; 6 | 7 | public FallbackAuthenticator() : this(new[] { typeof(Exception) }) 8 | { 9 | 10 | } 11 | 12 | public FallbackAuthenticator(Type[] exceptions) 13 | { 14 | _catchExceptions = exceptions; 15 | } 16 | 17 | public override async ValueTask ExecuteAsync(AuthenticateContext context) 18 | { 19 | await tryAuth(context); 20 | await ExecutePostAuthenticators(context); 21 | } 22 | 23 | private async ValueTask tryAuth(AuthenticateContext context) 24 | { 25 | var exceptions = new List(); 26 | var count = Authenticators.Count(); 27 | 28 | for (int i = 0; i < count; i++) 29 | { 30 | context.CancellationToken.ThrowIfCancellationRequested(); 31 | 32 | var authenticator = Authenticators.ElementAt(i); 33 | var validator = Validators.ElementAt(i); 34 | 35 | try 36 | { 37 | var valid = await validator.Validate(context); 38 | if (valid) 39 | continue; 40 | await authenticator.ExecuteAsync(context); 41 | return; 42 | } 43 | catch (Exception ex) 44 | { 45 | if (!checkToCatch(ex)) 46 | throw; 47 | 48 | context.Logger.LogFallbackAuthenticatorException(ex); 49 | exceptions.Add(ex); 50 | 51 | if (i == count - 1) // failed at last authenticator 52 | { 53 | throw new AggregateException(exceptions.AsEnumerable().Reverse()); 54 | } 55 | } 56 | } 57 | } 58 | 59 | private bool checkToCatch(Exception ex) 60 | { 61 | foreach (var exType in _catchExceptions) 62 | { 63 | if (exType.IsAssignableFrom(ex.GetType())) 64 | return true; 65 | } 66 | return false; 67 | } 68 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/IAuthenticator.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public interface IAuthenticator 4 | { 5 | ValueTask ExecuteAsync(AuthenticateContext context); 6 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/ICompositeAuthenticator.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public interface ICompositeAuthenticator : IAuthenticator 4 | { 5 | void AddAuthenticator(ISessionValidator validator, IAuthenticator authenticator); 6 | void AddPostAuthenticator(IAuthenticator authenticator); 7 | void Clear(); 8 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/ISessionValidator.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public interface ISessionValidator 4 | { 5 | ValueTask Validate(AuthenticateContext context); 6 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/LastAccessLogger.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public class LastAccessLogger : IAuthenticator 4 | { 5 | private static LastAccessLogger? _default; 6 | public static LastAccessLogger Default => _default ??= new(); 7 | 8 | public ValueTask ExecuteAsync(AuthenticateContext context) 9 | { 10 | var str = LastAccessSource.Default.SetToNow(context.SessionStorage); 11 | context.Logger.LogLastAccess(str); 12 | return new ValueTask(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/NestedAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Authenticators; 4 | 5 | public class NestedAuthenticator : CompositeAuthenticatorBase 6 | { 7 | public AuthenticateContext? Context { get; set; } 8 | 9 | public async ValueTask ExecuteAsync() 10 | { 11 | if (Context == null) 12 | throw new InvalidOperationException("Context was not set. Set Context property first or use ExecuteAsync(AuthenticateContext context) method instead."); 13 | await ExecuteAsync(Context); 14 | return Context.SessionStorage; 15 | } 16 | 17 | public override async ValueTask ExecuteAsync(AuthenticateContext context) 18 | { 19 | await doInnerAuth(Authenticators.Count() - 1, context); // starts from last one 20 | await ExecutePostAuthenticators(context); 21 | } 22 | 23 | private async ValueTask doInnerAuth(int index, AuthenticateContext context) 24 | { 25 | context.CancellationToken.ThrowIfCancellationRequested(); 26 | 27 | if (index < 0) 28 | return; 29 | 30 | var valid = await Validators.ElementAt(index).Validate(context); 31 | if (!valid) 32 | { 33 | await doInnerAuth(index - 1, context); 34 | await Authenticators.ElementAt(index).ExecuteAsync(context); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/SessionAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Authenticators; 4 | 5 | public abstract class SessionAuthenticator : IAuthenticator 6 | { 7 | public ISessionSource SessionSource { get; private set; } 8 | private AuthenticateContext? _context; 9 | 10 | public SessionAuthenticator(ISessionSource sessionSource) 11 | { 12 | SessionSource = sessionSource; 13 | } 14 | 15 | public async ValueTask ExecuteAsync(AuthenticateContext context) 16 | { 17 | _context = context; 18 | var result = await Authenticate(context); 19 | SessionSource.Set(context.SessionStorage, result); 20 | } 21 | 22 | protected abstract ValueTask Authenticate(AuthenticateContext context); 23 | 24 | protected T? GetSessionFromStorage() 25 | { 26 | if (_context == null) 27 | throw new InvalidOperationException("Call ExecuteAsync() first"); 28 | return SessionSource.Get(_context.SessionStorage); 29 | } 30 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/SessionCleaner.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.Authenticators; 5 | 6 | public class SessionCleaner : SessionAuthenticator 7 | { 8 | public SessionCleaner(ISessionSource sessionSource) 9 | : base(sessionSource) 10 | { 11 | 12 | } 13 | 14 | protected override ValueTask Authenticate(AuthenticateContext context) 15 | { 16 | context.Logger.LogSessionCleaner(typeof(T).Name); 17 | return default; 18 | } 19 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/SessionValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Authenticators; 4 | 5 | public abstract class SessionValidator : ISessionValidator 6 | { 7 | private readonly ISessionSource _sessionSource; 8 | 9 | public SessionValidator(ISessionSource sessionSource) 10 | { 11 | _sessionSource = sessionSource; 12 | } 13 | 14 | public ValueTask Validate(AuthenticateContext context) 15 | { 16 | var session = _sessionSource.Get(context.SessionStorage); 17 | if (session == null) 18 | return new ValueTask(false); 19 | return Validate(context, session); 20 | } 21 | 22 | protected abstract ValueTask Validate(AuthenticateContext context, T session); 23 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/SessionValidatorCollection.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections; 3 | 4 | namespace XboxAuthNet.Game.Authenticators; 5 | 6 | public class SessionValidatorCollection : ISessionValidator, IEnumerable 7 | { 8 | private readonly List _validators = new(); 9 | 10 | public void Add(ISessionValidator validator) 11 | { 12 | _validators.Add(validator); 13 | } 14 | 15 | public async ValueTask Validate(AuthenticateContext context) 16 | { 17 | foreach (var validator in _validators) 18 | { 19 | var result = await validator.Validate(context); 20 | if (!result) 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | public IEnumerator GetEnumerator() => _validators.GetEnumerator(); 27 | IEnumerator IEnumerable.GetEnumerator() => _validators.GetEnumerator(); 28 | } 29 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/StaticSessionAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.Authenticators; 4 | 5 | public class StaticSessionAuthenticator : SessionAuthenticator 6 | { 7 | private readonly T? _session; 8 | 9 | public StaticSessionAuthenticator( 10 | T? session, 11 | ISessionSource sessionSource) 12 | : base(sessionSource) => 13 | _session = session; 14 | 15 | protected override ValueTask Authenticate(AuthenticateContext context) 16 | { 17 | return new ValueTask(_session); 18 | } 19 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Authenticators/StaticValidator.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.Authenticators; 2 | 3 | public class StaticValidator : ISessionValidator 4 | { 5 | public static StaticValidator Valid = new(true); 6 | public static StaticValidator Invalid = new(false); 7 | 8 | private readonly bool _valid; 9 | 10 | public StaticValidator(bool valid) => _valid = valid; 11 | 12 | public ValueTask Validate(AuthenticateContext context) 13 | { 14 | return new ValueTask(_valid); 15 | } 16 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 16 | 17 | 18 | 19 | 20 | A comma-separated list of error codes that can be safely ignored in assembly verification. 21 | 22 | 23 | 24 | 25 | 'false' to turn off automatic generation of the XML Schema file. 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/HttpHelper.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game; 2 | 3 | public static class HttpHelper 4 | { 5 | public static Lazy DefaultHttpClient 6 | = new Lazy(() => new HttpClient()); 7 | } 8 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/IAuthenticationProvider.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game; 4 | 5 | public interface IAuthenticationProvider 6 | { 7 | IAuthenticator Authenticate(); 8 | ISessionValidator CreateSessionValidator(); 9 | IAuthenticator AuthenticateSilently(); 10 | IAuthenticator AuthenticateInteractively(); 11 | IAuthenticator ClearSession(); 12 | IAuthenticator Signout(); 13 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // just dummy class for C# 9.0 `init` keyword 2 | 3 | using System.ComponentModel; 4 | 5 | namespace System.Runtime.CompilerServices 6 | { 7 | [EditorBrowsable(EditorBrowsableState.Never)] 8 | internal static class IsExternalInit {} // should be internal 9 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Jwt/JwtDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | 6 | namespace XboxAuthNet.Game.Jwt 7 | { 8 | public static class JwtDecoder 9 | { 10 | /// 11 | /// decode jwt payload 12 | /// 13 | /// entire jwt 14 | /// decoded jwt payload 15 | /// 16 | /// invalid jwt 17 | public static string DecodePayloadString(string jwt) 18 | { 19 | if (string.IsNullOrEmpty(jwt)) 20 | throw new ArgumentNullException(jwt); 21 | 22 | var spl = jwt.Split('.'); 23 | if (spl.Length != 3) 24 | throw new FormatException("invalid jwt"); 25 | 26 | var encodedPayload = spl[1]; 27 | switch (encodedPayload.Length % 4) 28 | { 29 | case 0: 30 | break; 31 | case 2: 32 | encodedPayload += "=="; 33 | break; 34 | case 3: 35 | encodedPayload += "="; 36 | break; 37 | default: 38 | throw new FormatException("jwt payload"); 39 | 40 | } 41 | 42 | var decodedPayload = Encoding.UTF8.GetString(Convert.FromBase64String(encodedPayload)); // encodedPayload can't be null since string.Split never return null element 43 | return decodedPayload; 44 | } 45 | 46 | /// 47 | /// decode jwt payload and deserialize 48 | /// 49 | /// 50 | /// deserialized object of jwt payload 51 | /// 52 | /// invalid jwt 53 | public static T? DecodePayload(string jwt) where T : class 54 | { 55 | var payload = DecodePayloadString(jwt); 56 | return JsonSerializer.Deserialize(payload); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/LastAccessSource.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game; 5 | 6 | public class LastAccessSource : ISessionSource 7 | { 8 | private static LastAccessSource? _default; 9 | public static LastAccessSource Default => _default ??= new(); 10 | 11 | public static string KeyName { get; } = "lastAccess"; 12 | 13 | public DateTime Get(ISessionStorage sessionStorage) 14 | { 15 | if (sessionStorage.TryGetValue(KeyName, out var dateTimeStr)) 16 | { 17 | if (DateTime.TryParse(dateTimeStr, out var dateTime)) 18 | return dateTime; 19 | else 20 | return DateTime.MinValue; 21 | } 22 | else 23 | { 24 | return DateTime.MinValue; 25 | } 26 | } 27 | 28 | public void Set(ISessionStorage sessionStorage, DateTime value) => 29 | setInternal(sessionStorage, value); 30 | 31 | public string SetToNow(ISessionStorage sessionStorage) => 32 | setInternal(sessionStorage, DateTime.UtcNow); 33 | 34 | public void Clear(ISessionStorage sessionStorage) => 35 | Set(sessionStorage, DateTime.MinValue); 36 | 37 | private string setInternal(ISessionStorage sessionStorage, DateTime value) 38 | { 39 | var dateTimeStr = value.ToString("o", CultureInfo.InvariantCulture); 40 | sessionStorage.Set(KeyName, dateTimeStr); 41 | return dateTimeStr; 42 | } 43 | 44 | public SessionStorageKeyMode GetKeyMode(ISessionStorage sessionStorage) => 45 | sessionStorage.GetKeyMode(KeyName); 46 | 47 | public void SetKeyMode(ISessionStorage sessionStorage, SessionStorageKeyMode mode) => 48 | sessionStorage.SetKeyMode(KeyName, mode); 49 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace XboxAuthNet.Game; 4 | 5 | internal static partial class Log 6 | { 7 | [LoggerMessage( 8 | EventId = 749001, 9 | Level = LogLevel.Trace, 10 | Message = "Save accounts")] 11 | public static partial void LogSaveAccounts(this ILogger logger); 12 | 13 | [LoggerMessage( 14 | EventId = 749002, 15 | Level = LogLevel.Trace, 16 | Message = "Write last access: {lastAccess}")] 17 | public static partial void LogLastAccess(this ILogger logger, string lastAccess); 18 | 19 | public static void LogFallbackAuthenticatorException(this ILogger logger, Exception exception) 20 | { 21 | logger.LogError(new EventId(749401), exception, "Catch exception by FallbackAuthenticator"); 22 | } 23 | 24 | [LoggerMessage( 25 | EventId =749201, 26 | Level = LogLevel.Information, 27 | Message = "Clean {name} by SessionCleaner")] 28 | public static partial void LogSessionCleaner(this ILogger logger, string name); 29 | 30 | [LoggerMessage( 31 | EventId = 749202, 32 | Level = LogLevel.Information, 33 | Message = "Start InteractiveMicrosoftOAuth")] 34 | public static partial void LogInteractiveMicrosoftOAuth(this ILogger logger); 35 | 36 | [LoggerMessage( 37 | EventId = 749203, 38 | Level = LogLevel.Information, 39 | Message = "Start SilentMicrosoftOAuth")] 40 | public static partial void LogSilentMicrosoftOAuth(this ILogger logger); 41 | 42 | [LoggerMessage( 43 | EventId = 749204, 44 | Level = LogLevel.Information, 45 | Message = "Start MicrosoftOAuthSignout")] 46 | public static partial void LogMicrosoftOAuthSignout(this ILogger logger); 47 | 48 | [LoggerMessage( 49 | EventId = 749205, 50 | Level = LogLevel.Information, 51 | Message = "MicrosoftOAuthValidator result: {result}")] 52 | public static partial void LogMicrosoftOAuthValidation(this ILogger logger, bool result); 53 | 54 | [LoggerMessage( 55 | EventId = 749206, 56 | Level = LogLevel.Information, 57 | Message = "Start XboxDeviceTokenAuth")] 58 | public static partial void LogXboxDeviceToken(this ILogger logger); 59 | 60 | [LoggerMessage( 61 | EventId = 749207, 62 | Level = LogLevel.Information, 63 | Message = "Start XboxSignedUserTokenAuth")] 64 | public static partial void LogXboxSignedUserToken(this ILogger logger); 65 | 66 | [LoggerMessage( 67 | EventId = 749208, 68 | Level = LogLevel.Information, 69 | Message = "Start XboxSisuAuth, {relyingParty}")] 70 | public static partial void LogXboxSisu(this ILogger logger, string relyingParty); 71 | 72 | [LoggerMessage( 73 | EventId = 749209, 74 | Level = LogLevel.Information, 75 | Message = "Start XboxUserTokenAuth")] 76 | public static partial void LogXboxUserTokenAuth(this ILogger logger); 77 | 78 | 79 | [LoggerMessage( 80 | EventId = 749210, 81 | Level = LogLevel.Information, 82 | Message = "Start XstsTokenAuth, {relyingParty}")] 83 | public static partial void LogXboxXstsTokenAuth(this ILogger logger, string relyingParty); 84 | 85 | [LoggerMessage( 86 | EventId = 749211, 87 | Level = LogLevel.Information, 88 | Message = "XboxXuiClaimsValidatorResult: {result}")] 89 | public static partial void LogXboxXuiClaimsValidation(this ILogger logger, bool result); 90 | 91 | [LoggerMessage( 92 | EventId = 749212, 93 | Level = LogLevel.Information, 94 | Message = "Start XboxXuiClaimsAuth")] 95 | public static partial void LogXboxXuiClaims(this ILogger logger); 96 | 97 | [LoggerMessage( 98 | EventId = 749213, 99 | Level = LogLevel.Information, 100 | Message = "XboxSessionValidatorResult: {result}")] 101 | public static partial void LogXboxValidation(this ILogger logger, bool result); 102 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/LoginHandlerParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using XboxAuthNet.Game.Accounts; 3 | 4 | namespace XboxAuthNet.Game; 5 | 6 | public class LoginHandlerParameters 7 | { 8 | public LoginHandlerParameters( 9 | HttpClient httpClient, 10 | IXboxGameAccountManager accountManager, 11 | ILogger logger) => 12 | (HttpClient, AccountManager, Logger) = 13 | (httpClient, accountManager, logger); 14 | 15 | public HttpClient HttpClient { get; } 16 | public IXboxGameAccountManager AccountManager { get; } 17 | public ILogger Logger { get; } 18 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/InteractiveMicrosoftOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.OAuth; 4 | using XboxAuthNet.OAuth.CodeFlow; 5 | using XboxAuthNet.OAuth.CodeFlow.Parameters; 6 | 7 | namespace XboxAuthNet.Game.OAuth; 8 | 9 | public class InteractiveMicrosoftOAuth : MicrosoftOAuth 10 | { 11 | private readonly Action _codeFlowBuilder; 12 | private readonly CodeFlowAuthorizationParameter _codeFlowParameters; 13 | 14 | public InteractiveMicrosoftOAuth( 15 | MicrosoftOAuthParameters parameters, 16 | Action codeFlowBuilder, 17 | CodeFlowAuthorizationParameter codeFlowParameters) 18 | : base(parameters) => 19 | (_codeFlowBuilder, _codeFlowParameters) = 20 | (codeFlowBuilder, codeFlowParameters); 21 | 22 | protected override async ValueTask Authenticate( 23 | AuthenticateContext context, MicrosoftOAuthParameters parameters) 24 | { 25 | context.Logger.LogInteractiveMicrosoftOAuth(); 26 | 27 | var apiClient = parameters.ClientInfo.CreateApiClientForOAuthCode(context.HttpClient); 28 | var builder = new CodeFlowBuilder(apiClient); 29 | _codeFlowBuilder.Invoke(builder); 30 | var oauthHandler = builder.Build(); 31 | 32 | var loginHint = parameters.LoginHintSource.Get(context.SessionStorage); 33 | if (string.IsNullOrEmpty(_codeFlowParameters.LoginHint)) 34 | _codeFlowParameters.LoginHint = loginHint; 35 | 36 | return await oauthHandler.AuthenticateInteractively(_codeFlowParameters, context.CancellationToken); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.OAuth; 3 | 4 | namespace XboxAuthNet.Game.OAuth; 5 | 6 | public abstract class MicrosoftOAuth : SessionAuthenticator 7 | { 8 | private MicrosoftOAuthParameters _parameters; 9 | 10 | public MicrosoftOAuth(MicrosoftOAuthParameters parameters) : base(parameters.SessionSource) 11 | { 12 | this._parameters = parameters; 13 | } 14 | 15 | protected override async ValueTask Authenticate(AuthenticateContext context) 16 | { 17 | var response = await Authenticate(context, _parameters); 18 | var loginHint = response?.DecodeIdTokenPayload()?.Subject; 19 | _parameters.LoginHintSource.Set(context.SessionStorage, loginHint); 20 | return response; 21 | } 22 | 23 | protected abstract ValueTask Authenticate( 24 | AuthenticateContext context, MicrosoftOAuthParameters parameters); 25 | } 26 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthClientInfo.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.OAuth; 2 | using XboxAuthNet.OAuth.CodeFlow; 3 | 4 | namespace XboxAuthNet.Game.OAuth; 5 | 6 | public record class MicrosoftOAuthClientInfo(string ClientId, string Scopes) 7 | { 8 | public ICodeFlowApiClient CreateApiClientForOAuthCode(HttpClient httpClient) 9 | { 10 | if (string.IsNullOrEmpty(ClientId)) 11 | throw new InvalidOperationException("ClientId was empty"); 12 | if (string.IsNullOrEmpty(Scopes)) 13 | throw new InvalidCastException("Scopes was empty"); 14 | 15 | return new CodeFlowLiveApiClient(ClientId, Scopes, httpClient); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthCodeFlowProvider.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game.OAuth; 4 | 5 | public class MicrosoftOAuthCodeFlowProvider : IAuthenticationProvider 6 | { 7 | private readonly MicrosoftOAuthBuilder _oauth; 8 | 9 | public MicrosoftOAuthCodeFlowProvider(MicrosoftOAuthClientInfo clientInfo) 10 | { 11 | _oauth = new MicrosoftOAuthBuilder(clientInfo); 12 | } 13 | 14 | public IAuthenticator Authenticate() => _oauth.CodeFlow(); 15 | public IAuthenticator AuthenticateInteractively() => _oauth.Interactive(); 16 | public IAuthenticator AuthenticateSilently() => _oauth.Silent(); 17 | public IAuthenticator ClearSession() => _oauth.Signout(); 18 | public IAuthenticator Signout() => _oauth.SignoutWithBrowser(); 19 | public ISessionValidator CreateSessionValidator() => _oauth.Validator(); 20 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthLoginHintSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.OAuth; 4 | 5 | public class MicrosoftOAuthLoginHintSource : SessionFromStorage 6 | { 7 | private static MicrosoftOAuthLoginHintSource? _instance; 8 | public static MicrosoftOAuthLoginHintSource Default => _instance ??= new MicrosoftOAuthLoginHintSource(); 9 | 10 | public const string KeyName = "MicrosoftOAuthLoginHint"; 11 | 12 | public MicrosoftOAuthLoginHintSource() : base(KeyName) 13 | { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthLoginHintValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.OAuth; 4 | 5 | namespace XboxAuthNet.Game.OAuth; 6 | 7 | public class MicrosoftOAuthLoginHintValidator : ISessionValidator 8 | { 9 | private ISessionSource _loginHintSource; 10 | private readonly bool _throwWhenInvalid = false; 11 | 12 | public MicrosoftOAuthLoginHintValidator(bool throwWhenInvalid, ISessionSource loginHintSource) 13 | { 14 | _throwWhenInvalid = throwWhenInvalid; 15 | _loginHintSource = loginHintSource; 16 | } 17 | 18 | public ValueTask Validate(AuthenticateContext context) 19 | { 20 | var loginHint = _loginHintSource.Get(context.SessionStorage); 21 | var loginHintExists = !string.IsNullOrEmpty(loginHint); 22 | if (_throwWhenInvalid && !loginHintExists) 23 | throw new MicrosoftOAuthException("LoginHint was empty.", 0); 24 | return new ValueTask(loginHintExists); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthParameters.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | using XboxAuthNet.OAuth; 3 | 4 | namespace XboxAuthNet.Game.OAuth; 5 | 6 | public class MicrosoftOAuthParameters 7 | { 8 | public MicrosoftOAuthParameters( 9 | MicrosoftOAuthClientInfo clientInfo, 10 | ISessionSource sessionSource, 11 | ISessionSource loginHintSource) 12 | { 13 | ClientInfo = clientInfo; 14 | SessionSource = sessionSource; 15 | LoginHintSource = loginHintSource; 16 | } 17 | 18 | public MicrosoftOAuthClientInfo ClientInfo { get; } 19 | public ISessionSource SessionSource { get; } 20 | public ISessionSource LoginHintSource { get; } 21 | } 22 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthSessionSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | using XboxAuthNet.OAuth; 3 | 4 | namespace XboxAuthNet.Game.OAuth; 5 | 6 | public class MicrosoftOAuthSessionSource : SessionFromStorage 7 | { 8 | private static MicrosoftOAuthSessionSource? _default; 9 | public static MicrosoftOAuthSessionSource Default => _default ??= new(); 10 | 11 | public static string KeyName { get; } = "MicrosoftOAuth"; 12 | public MicrosoftOAuthSessionSource() 13 | : base(KeyName) 14 | { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthSignout.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.OAuth; 4 | using XboxAuthNet.OAuth.CodeFlow; 5 | 6 | namespace XboxAuthNet.Game.OAuth; 7 | 8 | public class MicrosoftOAuthSignout : MicrosoftOAuth 9 | { 10 | private readonly Action builderInvoker; 11 | 12 | public MicrosoftOAuthSignout( 13 | MicrosoftOAuthParameters parameters, 14 | Action builderInvoker) 15 | : base(parameters) 16 | { 17 | this.builderInvoker = builderInvoker; 18 | } 19 | 20 | protected override async ValueTask Authenticate( 21 | AuthenticateContext context, MicrosoftOAuthParameters parameters) 22 | { 23 | context.Logger.LogMicrosoftOAuthSignout(); 24 | 25 | var apiClient = parameters.ClientInfo.CreateApiClientForOAuthCode(context.HttpClient); 26 | var builder = new CodeFlowBuilder(apiClient); 27 | builderInvoker.Invoke(builder); 28 | var codeFlow = builder.Build(); 29 | 30 | await codeFlow.Signout(context.CancellationToken); 31 | return null; 32 | } 33 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/MicrosoftOAuthValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.OAuth; 4 | 5 | namespace XboxAuthNet.Game.OAuth; 6 | 7 | public class MicrosoftOAuthValidator : SessionValidator 8 | { 9 | public MicrosoftOAuthValidator( 10 | ISessionSource sessionSource) 11 | : base(sessionSource) 12 | { 13 | 14 | } 15 | 16 | protected override ValueTask Validate(AuthenticateContext context, MicrosoftOAuthResponse session) 17 | { 18 | var result = session.Validate(); 19 | context.Logger.LogMicrosoftOAuthValidation(result); 20 | return new ValueTask(result); 21 | } 22 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/OAuth/SilentMicrosoftOAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.OAuth; 3 | using XboxAuthNet.OAuth.CodeFlow.Parameters; 4 | 5 | namespace XboxAuthNet.Game.OAuth; 6 | 7 | public class SilentMicrosoftOAuth : MicrosoftOAuth 8 | { 9 | public SilentMicrosoftOAuth(MicrosoftOAuthParameters parameters) : base(parameters) 10 | { 11 | 12 | } 13 | 14 | protected override async ValueTask Authenticate( 15 | AuthenticateContext context, MicrosoftOAuthParameters parameters) 16 | { 17 | context.Logger.LogSilentMicrosoftOAuth(); 18 | 19 | var session = GetSessionFromStorage(); 20 | if (string.IsNullOrEmpty(session?.RefreshToken)) 21 | throw new MicrosoftOAuthException( 22 | "Cached RefreshToken of the user was empty. " + 23 | "Interactive Microsoft authentication is required.", 0); 24 | 25 | var apiClient = parameters.ClientInfo.CreateApiClientForOAuthCode(context.HttpClient); 26 | return await apiClient.RefreshToken( 27 | new CodeFlowRefreshTokenParameter 28 | { 29 | RefreshToken = session.RefreshToken 30 | }, 31 | context.CancellationToken); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/Extensions.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public static class Extensions 4 | { 5 | public static IEnumerable GetKeysForStoring(this ISessionStorage sessionStorage) => 6 | sessionStorage.Keys 7 | .Where(k => sessionStorage.GetKeyMode(k) != SessionStorageKeyMode.NoStore); 8 | } 9 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/ISessionSource.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public interface ISessionSource 4 | { 5 | T? Get(ISessionStorage sessionStorage); 6 | void Set(ISessionStorage sessionStorage, T? obj); 7 | void Clear(ISessionStorage sessionStorage); 8 | SessionStorageKeyMode GetKeyMode(ISessionStorage sessionStorage); 9 | void SetKeyMode(ISessionStorage sessionStorage, SessionStorageKeyMode mode); 10 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/ISessionStorage.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public interface ISessionStorage 4 | { 5 | IEnumerable Keys { get; } 6 | T Get(string key); 7 | T GetOrDefault(string key, T defaultValue); 8 | bool TryGetValue(string key, out T value); 9 | void Set(string key, T obj); 10 | SessionStorageKeyMode GetKeyMode(string key); 11 | void SetKeyMode(string key, SessionStorageKeyMode mode); 12 | bool Remove(string key); 13 | bool ContainsKey(string key); 14 | bool ContainsKey(string key); 15 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/InMemorySessionSource.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public class InMemorySessionSource : ISessionSource 4 | { 5 | T? innerObj; 6 | SessionStorageKeyMode keyMode = SessionStorageKeyMode.Default; 7 | 8 | public InMemorySessionSource(T obj) 9 | { 10 | innerObj = obj; 11 | } 12 | 13 | public void Clear(ISessionStorage sessionStorage) => 14 | innerObj = default!; 15 | 16 | public T? Get(ISessionStorage sessionStorage) => innerObj; 17 | 18 | public SessionStorageKeyMode GetKeyMode(ISessionStorage sessionStorage) => keyMode; 19 | 20 | public void Set(ISessionStorage sessionStorage, T? obj) => 21 | innerObj = obj; 22 | 23 | public void SetKeyMode(ISessionStorage sessionStorage, SessionStorageKeyMode mode) => keyMode = mode; 24 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/InMemorySessionStorage.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public class InMemorySessionStorage : ISessionStorage 4 | { 5 | private readonly Dictionary _storage = new Dictionary(); 6 | private readonly KeyModeStorage _keyModeStorage = new(); 7 | 8 | public IEnumerable Keys => _storage.Keys; 9 | public int Count => _storage.Count; 10 | 11 | public T Get(string key) => 12 | (T)_storage[key]; 13 | 14 | public T GetOrDefault(string key, T defaultValue) 15 | { 16 | if (TryGetValue(key, out var value)) 17 | return value; 18 | else 19 | return defaultValue; 20 | } 21 | 22 | public bool TryGetValue(string key, out T value) 23 | { 24 | _storage.TryGetValue(key, out var objValue); 25 | if (objValue == null) 26 | { 27 | value = default!; 28 | return false; 29 | } 30 | else if (objValue is not T) 31 | { 32 | value = default!; 33 | return false; 34 | } 35 | else 36 | { 37 | value = (T)objValue; 38 | return true; 39 | } 40 | } 41 | 42 | public void Set(string key, T obj) 43 | { 44 | if (EqualityComparer.Default.Equals(obj, default(T))) 45 | _storage.Remove(key); 46 | else 47 | _storage[key] = obj!; 48 | } 49 | 50 | public bool ContainsKey(string key) => 51 | _storage.ContainsKey(key); 52 | 53 | public bool ContainsKey(string key) 54 | { 55 | var contains = _storage.TryGetValue(key, out var value); 56 | return contains && value is T; 57 | } 58 | 59 | public bool Remove(string key) => 60 | _storage.Remove(key); 61 | 62 | public void Clear() => 63 | _storage.Clear(); 64 | 65 | public SessionStorageKeyMode GetKeyMode(string key) => 66 | _keyModeStorage.Get(key); 67 | 68 | public void SetKeyMode(string key, SessionStorageKeyMode mode) => 69 | _keyModeStorage.Set(key, mode); 70 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/JsonFileSessionStorage.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | 4 | namespace XboxAuthNet.Game.SessionStorages 5 | { 6 | public class JsonFileSessionStorage : ISessionStorage 7 | { 8 | private readonly string _filePath; 9 | private readonly JsonSerializerOptions _jsonOptions; 10 | private readonly KeyModeStorage _keyModeStorage = new(); 11 | 12 | private JsonSessionStorage? _innerStorage; 13 | public IEnumerable Keys => _innerStorage?.Keys ?? Enumerable.Empty(); 14 | 15 | public JsonFileSessionStorage(string filePath) : this(filePath, JsonSerializerOptions.Default) {} 16 | 17 | public JsonFileSessionStorage(string filePath, JsonSerializerOptions jsonSerializerOptions) 18 | { 19 | this._filePath = filePath; 20 | this._jsonOptions = jsonSerializerOptions; 21 | } 22 | 23 | public T Get(string key) 24 | { 25 | return getStorage().Get(key); 26 | } 27 | 28 | public T GetOrDefault(string key, T defaultValue) 29 | { 30 | if (TryGetValue(key, out var value)) 31 | return value; 32 | else 33 | return defaultValue; 34 | } 35 | 36 | public bool TryGetValue(string key, out T value) 37 | => getStorage().TryGetValue(key, out value); 38 | 39 | public void Set(string key, T obj) 40 | { 41 | getStorage().Set(key, obj); 42 | saveStorage(); 43 | } 44 | 45 | public bool ContainsKey(string key) 46 | { 47 | return getStorage().ContainsKey(key); 48 | } 49 | 50 | public bool ContainsKey(string key) 51 | { 52 | return getStorage().ContainsKey(key); 53 | } 54 | 55 | public bool Remove(string key) 56 | { 57 | return getStorage().Remove(key); 58 | } 59 | 60 | public void Clear() 61 | { 62 | getStorage().Clear(); 63 | saveStorage(); 64 | } 65 | 66 | private JsonSessionStorage getStorage() 67 | { 68 | if (_innerStorage == null) 69 | loadJson(); 70 | return _innerStorage!; 71 | } 72 | 73 | private void loadJson() 74 | { 75 | JsonObject? jsonObject = null; 76 | _innerStorage = null; 77 | 78 | if (File.Exists(_filePath)) 79 | { 80 | try 81 | { 82 | using var fs = File.OpenRead(_filePath); 83 | jsonObject = JsonNode.Parse(fs) as JsonObject; 84 | } 85 | catch (JsonException) // reset storage if json file is corrupted 86 | { 87 | jsonObject = new JsonObject(); 88 | } 89 | } 90 | 91 | if (jsonObject == null) 92 | jsonObject = new JsonObject(); 93 | 94 | _innerStorage = new JsonSessionStorage(jsonObject, _jsonOptions); 95 | } 96 | 97 | private void saveStorage() 98 | { 99 | var dirPath = Path.GetDirectoryName(_filePath); 100 | if (!string.IsNullOrEmpty(dirPath)) 101 | Directory.CreateDirectory(dirPath); 102 | 103 | var json = getStorage().ToJsonObjectForStoring(); 104 | using var fs = File.Create(_filePath); 105 | using var jsonWriter = new Utf8JsonWriter(fs); 106 | json.WriteTo(jsonWriter, _jsonOptions); 107 | } 108 | 109 | public SessionStorageKeyMode GetKeyMode(string key) => 110 | _keyModeStorage.Get(key); 111 | 112 | public void SetKeyMode(string key, SessionStorageKeyMode mode) => 113 | _keyModeStorage.Set(key, mode); 114 | } 115 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/KeyModeStorage.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public class KeyModeStorage 4 | { 5 | private readonly Dictionary _storage = new(); 6 | 7 | public SessionStorageKeyMode Get(string key) 8 | { 9 | var result = _storage.TryGetValue(key, out var keyMode); 10 | if (result) 11 | return keyMode; 12 | else 13 | return SessionStorageKeyMode.Default; 14 | } 15 | 16 | public void Set(string key, SessionStorageKeyMode value) 17 | { 18 | _storage[key] = value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/SessionFromStorage.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | 3 | public class SessionFromStorage : ISessionSource 4 | { 5 | private readonly string _keyName; 6 | 7 | public SessionFromStorage(string keyName) => 8 | _keyName = keyName; 9 | 10 | public T? Get(ISessionStorage sessionStorage) => 11 | sessionStorage.GetOrDefault(_keyName, default); 12 | 13 | public void Set(ISessionStorage sessionStorage, T? obj) => 14 | sessionStorage.Set(_keyName, obj); 15 | 16 | public void Clear(ISessionStorage sessionStorage) => 17 | Set(sessionStorage, default); 18 | 19 | public SessionStorageKeyMode GetKeyMode(ISessionStorage sessionStorage) => 20 | sessionStorage.GetKeyMode(_keyName); 21 | 22 | public void SetKeyMode(ISessionStorage sessionStorage, SessionStorageKeyMode mode) => 23 | sessionStorage.SetKeyMode(_keyName, mode); 24 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/SessionStorages/SessionStorageKeyMode.cs: -------------------------------------------------------------------------------- 1 | namespace XboxAuthNet.Game.SessionStorages; 2 | public enum SessionStorageKeyMode 3 | { 4 | Default, 5 | 6 | /// 7 | /// Do not store the key value pair to permanent storage (ex: disk) 8 | /// After ISessionStorage is disposed, The key is not accessible anymore. 9 | /// 10 | NoStore 11 | } 12 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxAuthTokens.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using XboxAuthNet.XboxLive.Responses; 3 | 4 | namespace XboxAuthNet.Game.XboxAuth; 5 | 6 | public class XboxAuthTokens 7 | { 8 | [JsonPropertyName("deviceToken")] 9 | public XboxAuthResponse? DeviceToken { get; set; } 10 | 11 | [JsonPropertyName("titleToken")] 12 | public XboxAuthResponse? TitleToken { get; set; } 13 | 14 | [JsonPropertyName("userToken")] 15 | public XboxAuthResponse? UserToken { get; set; } 16 | 17 | [JsonPropertyName("xstsToken")] 18 | public XboxAuthResponse? XstsToken { get; set; } 19 | 20 | public bool Validate() => XstsToken?.Validate() ?? false; 21 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxAuthenticationProviders.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game.XboxAuth; 4 | 5 | public abstract class XboxAuthenticationProviderBase : IAuthenticationProvider 6 | { 7 | protected XboxAuthBuilder Builder { get; } 8 | 9 | public XboxAuthenticationProviderBase(string relyingParty) 10 | { 11 | Builder = new XboxAuthBuilder(); 12 | Builder.WithRelyingParty(relyingParty); 13 | } 14 | 15 | public XboxAuthenticationProviderBase(XboxAuthBuilder builder) 16 | { 17 | Builder = builder; 18 | } 19 | 20 | protected abstract IAuthenticator CreateAuthenticator(); 21 | 22 | public IAuthenticator Authenticate() => CreateAuthenticator(); 23 | public IAuthenticator AuthenticateSilently() => CreateAuthenticator(); 24 | public IAuthenticator AuthenticateInteractively() => CreateAuthenticator(); 25 | public ISessionValidator CreateSessionValidator() => Builder.Validator(); 26 | public IAuthenticator ClearSession() => Builder.ClearSession(); 27 | public IAuthenticator Signout() => Builder.ClearSession(); 28 | } 29 | 30 | public class BasicXboxProvider : XboxAuthenticationProviderBase 31 | { 32 | public BasicXboxProvider(string relyingParty) : base(relyingParty) 33 | { 34 | } 35 | 36 | public BasicXboxProvider(XboxAuthBuilder builder) : base(builder) 37 | { 38 | } 39 | 40 | protected override IAuthenticator CreateAuthenticator() => Builder.Basic(); 41 | } 42 | 43 | public class FullXboxProvider : XboxAuthenticationProviderBase 44 | { 45 | public FullXboxProvider(string relyingParty) : base(relyingParty) 46 | { 47 | } 48 | 49 | public FullXboxProvider(XboxAuthBuilder builder) : base(builder) 50 | { 51 | } 52 | 53 | protected override IAuthenticator CreateAuthenticator() => Builder.Full(); 54 | } 55 | 56 | public class SisuXboxProvider : XboxAuthenticationProviderBase 57 | { 58 | private readonly string _clientId; 59 | 60 | public SisuXboxProvider(string relyingParty, string clientId) : base(relyingParty) 61 | { 62 | _clientId = clientId; 63 | } 64 | 65 | public SisuXboxProvider(XboxAuthBuilder builder, string clientId) : base(builder) 66 | { 67 | _clientId = clientId; 68 | } 69 | 70 | protected override IAuthenticator CreateAuthenticator() => Builder.Sisu(_clientId); 71 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxDeviceTokenAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.XboxLive; 4 | using XboxAuthNet.XboxLive.Crypto; 5 | 6 | namespace XboxAuthNet.Game.XboxAuth; 7 | 8 | public class XboxDeviceTokenAuth : SessionAuthenticator 9 | { 10 | private readonly IXboxRequestSigner _signer; 11 | private readonly string _deviceType; 12 | private readonly string _deviceVersion; 13 | 14 | public XboxDeviceTokenAuth( 15 | string deviceType, 16 | string deviceVersion, 17 | IXboxRequestSigner signer, 18 | ISessionSource sessionSource) 19 | : base(sessionSource) => 20 | (_deviceType, _deviceVersion, _signer) = (deviceType, deviceVersion, signer); 21 | 22 | protected override async ValueTask Authenticate(AuthenticateContext context) 23 | { 24 | context.Logger.LogXboxDeviceToken(); 25 | 26 | var xboxTokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 27 | var xboxAuthClient = new XboxSignedClient(_signer, context.HttpClient); 28 | xboxTokens.DeviceToken = await xboxAuthClient.RequestDeviceToken( 29 | _deviceType, _deviceVersion); 30 | return xboxTokens; 31 | } 32 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxSessionSource.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.SessionStorages; 2 | 3 | namespace XboxAuthNet.Game.XboxAuth; 4 | 5 | public class XboxSessionSource : SessionFromStorage 6 | { 7 | private static XboxSessionSource? _default; 8 | public static XboxSessionSource Default => _default ??= new(); 9 | 10 | public static string KeyName { get; } = "XboxTokens"; 11 | 12 | public XboxSessionSource() 13 | : base(KeyName) 14 | { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxSessionValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.XboxAuth; 5 | 6 | public class XboxSessionValidator : SessionValidator 7 | { 8 | public XboxSessionValidator(ISessionSource sessionSource) 9 | : base(sessionSource) 10 | { 11 | 12 | } 13 | 14 | protected override ValueTask Validate(AuthenticateContext context, XboxAuthTokens session) 15 | { 16 | var result = session?.XstsToken?.Validate() ?? false; 17 | context.Logger.LogXboxValidation(result); 18 | return new ValueTask(result); 19 | } 20 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxSignedUserTokenAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.OAuth; 4 | using XboxAuthNet.XboxLive; 5 | using XboxAuthNet.XboxLive.Crypto; 6 | 7 | namespace XboxAuthNet.Game.XboxAuth; 8 | 9 | public class XboxSignedUserTokenAuth : SessionAuthenticator 10 | { 11 | private readonly ISessionSource _oAuthSessionSource; 12 | private readonly IXboxRequestSigner _signer; 13 | 14 | public XboxSignedUserTokenAuth( 15 | IXboxRequestSigner signer, 16 | ISessionSource oauthSessionSource, 17 | ISessionSource sessionSource) 18 | : base(sessionSource) => 19 | (_signer, _oAuthSessionSource) = (signer, oauthSessionSource); 20 | 21 | protected override async ValueTask Authenticate(AuthenticateContext context) 22 | { 23 | context.Logger.LogXboxSignedUserToken(); 24 | 25 | var oAuthAccessToken = _oAuthSessionSource 26 | .Get(context.SessionStorage)? 27 | .AccessToken; 28 | 29 | if (string.IsNullOrEmpty(oAuthAccessToken)) 30 | throw new XboxAuthException("OAuth access token was empty. Microsoft OAuth is required.", 0); 31 | 32 | var xboxAuthClient = new XboxSignedClient(_signer, context.HttpClient); 33 | var userToken = await xboxAuthClient.RequestSignedUserToken(oAuthAccessToken); 34 | 35 | var xboxTokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 36 | xboxTokens.UserToken = userToken; 37 | return xboxTokens; 38 | } 39 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxSisuAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.XboxLive; 2 | using XboxAuthNet.XboxLive.Requests; 3 | using XboxAuthNet.Game.Authenticators; 4 | using XboxAuthNet.Game.SessionStorages; 5 | using XboxAuthNet.OAuth; 6 | using XboxAuthNet.XboxLive.Crypto; 7 | 8 | namespace XboxAuthNet.Game.XboxAuth; 9 | 10 | public class XboxSisuAuth : SessionAuthenticator 11 | { 12 | private readonly string _clientId; 13 | private readonly string _tokenPrefix; 14 | private readonly string _relyingParty; 15 | private readonly ISessionSource _oAuthSessionSource; 16 | private readonly IXboxRequestSigner _signer; 17 | 18 | public XboxSisuAuth( 19 | string clientId, 20 | string tokenPrefix, 21 | string relyingParty, 22 | IXboxRequestSigner signer, 23 | ISessionSource oauthSessionSource, 24 | ISessionSource sessionSource) 25 | : base(sessionSource) => 26 | (_clientId, _tokenPrefix, _relyingParty, _signer, _oAuthSessionSource) = 27 | (clientId, tokenPrefix, relyingParty, signer, oauthSessionSource); 28 | 29 | protected override async ValueTask Authenticate(AuthenticateContext context) 30 | { 31 | context.Logger.LogXboxSisu(_relyingParty); 32 | 33 | var oAuthAccessToken = _oAuthSessionSource 34 | .Get(context.SessionStorage)? 35 | .AccessToken; 36 | 37 | if (string.IsNullOrEmpty(oAuthAccessToken)) 38 | throw new XboxAuthException("OAuth access token was empty. Microsoft OAuth is required.", 0); 39 | 40 | var xboxTokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 41 | 42 | var xboxAuthClient = new XboxSignedClient(_signer, context.HttpClient); 43 | var sisuResponse = await xboxAuthClient.SisuAuth(new XboxSisuAuthRequest 44 | { 45 | AccessToken = oAuthAccessToken, 46 | DeviceToken = xboxTokens.DeviceToken?.Token, 47 | TokenPrefix = _tokenPrefix, 48 | ClientId = _clientId, 49 | RelyingParty = _relyingParty, 50 | }); 51 | 52 | return new XboxAuthTokens 53 | { 54 | UserToken = sisuResponse.UserToken, 55 | DeviceToken = xboxTokens.DeviceToken, 56 | TitleToken = sisuResponse.TitleToken, 57 | XstsToken = sisuResponse.AuthorizationToken, 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxUserTokenAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.OAuth; 4 | using XboxAuthNet.XboxLive; 5 | 6 | namespace XboxAuthNet.Game.XboxAuth; 7 | 8 | public class XboxUserTokenAuth : SessionAuthenticator 9 | { 10 | private readonly ISessionSource _oauthSessionSource; 11 | 12 | public XboxUserTokenAuth( 13 | ISessionSource oAuthSessionSource, 14 | ISessionSource sessionSource) 15 | : base(sessionSource) => 16 | _oauthSessionSource = oAuthSessionSource; 17 | 18 | protected override async ValueTask Authenticate(AuthenticateContext context) 19 | { 20 | context.Logger.LogXboxUserTokenAuth(); 21 | 22 | var oAuthAccessToken = _oauthSessionSource 23 | .Get(context.SessionStorage)? 24 | .AccessToken; 25 | 26 | if (string.IsNullOrEmpty(oAuthAccessToken)) 27 | throw new XboxAuthException("OAuth access token was empty. Microsoft OAuth is required.", 0); 28 | 29 | var xboxAuthClient = new XboxAuthClient(context.HttpClient); 30 | var userToken = await xboxAuthClient.RequestUserToken(oAuthAccessToken); 31 | 32 | var tokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 33 | tokens.UserToken = userToken; 34 | return tokens; 35 | } 36 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxXstsTokenAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.XboxLive; 4 | using XboxAuthNet.XboxLive.Requests; 5 | 6 | namespace XboxAuthNet.Game.XboxAuth; 7 | 8 | public class XboxXstsTokenAuth : SessionAuthenticator 9 | { 10 | private readonly string _relyingParty; 11 | 12 | public XboxXstsTokenAuth( 13 | string relyingParty, 14 | ISessionSource sessionSource) 15 | : base(sessionSource) => 16 | _relyingParty = relyingParty; 17 | 18 | protected override async ValueTask Authenticate(AuthenticateContext context) 19 | { 20 | context.Logger.LogXboxXstsTokenAuth(_relyingParty); 21 | 22 | var xboxTokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 23 | 24 | var xboxAuthClient = new XboxAuthClient(context.HttpClient); 25 | var xsts = await xboxAuthClient.RequestXsts(new XboxXstsRequest 26 | { 27 | UserToken = xboxTokens.UserToken?.Token, 28 | DeviceToken = xboxTokens.DeviceToken?.Token, 29 | TitleToken = xboxTokens.TitleToken?.Token, 30 | RelyingParty = _relyingParty, 31 | }); 32 | 33 | xboxTokens.XstsToken = xsts; 34 | return xboxTokens; 35 | } 36 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxXuiClaimsAuth.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.XboxLive; 4 | 5 | namespace XboxAuthNet.Game.XboxAuth; 6 | 7 | public class XboxXuiClaimsAuth : SessionAuthenticator 8 | { 9 | private readonly string[] _xuiClaimNames; 10 | 11 | public XboxXuiClaimsAuth( 12 | string[] xuiClaimNames, 13 | ISessionSource sessionSource) : 14 | base(sessionSource) 15 | { 16 | _xuiClaimNames = xuiClaimNames; 17 | } 18 | 19 | protected async override ValueTask Authenticate(AuthenticateContext context) 20 | { 21 | context.Logger.LogXboxXuiClaims(); 22 | var xboxAuthTokens = GetSessionFromStorage() ?? new XboxAuthTokens(); 23 | 24 | var tempXboxAuthToken = new XboxAuthTokens 25 | { 26 | UserToken = xboxAuthTokens.UserToken 27 | }; 28 | var tempSessionSource = new InMemorySessionSource(tempXboxAuthToken); 29 | 30 | var xstsAuth = new XboxXstsTokenAuth(XboxAuthConstants.XboxLiveRelyingParty, tempSessionSource); 31 | await xstsAuth.ExecuteAsync(context); 32 | tempXboxAuthToken = tempSessionSource.Get(context.SessionStorage); 33 | 34 | if (tempXboxAuthToken != null) 35 | copyXuiClaims(tempXboxAuthToken, xboxAuthTokens); 36 | 37 | return xboxAuthTokens; 38 | } 39 | 40 | 41 | private void copyXuiClaims(XboxAuthTokens from, XboxAuthTokens to) 42 | { 43 | to.XstsToken ??= new XboxLive.Responses.XboxAuthResponse(); 44 | to.XstsToken.XuiClaims ??= new XboxLive.Responses.XboxAuthXuiClaims(); 45 | 46 | var fromClaims = from.XstsToken?.XuiClaims; 47 | var toClaims = to.XstsToken.XuiClaims; 48 | 49 | if (fromClaims == null) 50 | return; 51 | 52 | foreach (var name in _xuiClaimNames) 53 | { 54 | if (name == XboxAuthXuiClaimNames.AgeGroup) 55 | toClaims.AgeGroup = fromClaims.AgeGroup; 56 | if (name == XboxAuthXuiClaimNames.Gamertag) 57 | toClaims.Gamertag = fromClaims.Gamertag; 58 | if (name == XboxAuthXuiClaimNames.Privileges) 59 | toClaims.Privileges = fromClaims.Privileges; 60 | if (name == XboxAuthXuiClaimNames.UserHash) 61 | toClaims.UserHash = fromClaims.UserHash; 62 | if (name == XboxAuthXuiClaimNames.UserSettingsRestrictions) 63 | toClaims.UserSettingsRestrictions = fromClaims.UserSettingsRestrictions; 64 | if (name == XboxAuthXuiClaimNames.UserTitleRestrictions) 65 | toClaims.UserTitleRestrictions = fromClaims.UserTitleRestrictions; 66 | if (name == XboxAuthXuiClaimNames.XboxUserId) 67 | toClaims.XboxUserId = fromClaims.XboxUserId; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuth/XboxXuiClaimsValidator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | using XboxAuthNet.Game.SessionStorages; 3 | using XboxAuthNet.XboxLive; 4 | 5 | namespace XboxAuthNet.Game.XboxAuth; 6 | 7 | public class XboxXuiClaimsValidator : SessionValidator 8 | { 9 | private readonly string[] _xuiClaimNames; 10 | 11 | public XboxXuiClaimsValidator( 12 | string[] xuiClaimNames, 13 | ISessionSource sessionSource) : 14 | base(sessionSource) 15 | { 16 | _xuiClaimNames = xuiClaimNames; 17 | } 18 | 19 | protected override ValueTask Validate(AuthenticateContext context, XboxAuthTokens session) 20 | { 21 | var result = validateClaims(session); 22 | context.Logger.LogXboxXuiClaimsValidation(result); 23 | return new ValueTask(result); 24 | } 25 | 26 | private bool validateClaims(XboxAuthTokens tokens) 27 | { 28 | var claims = tokens.XstsToken?.XuiClaims; 29 | if (claims == null) 30 | return false; 31 | 32 | foreach (var name in _xuiClaimNames) 33 | { 34 | var invalid = 35 | (name == XboxAuthXuiClaimNames.AgeGroup && string.IsNullOrEmpty(claims.AgeGroup)) || 36 | (name == XboxAuthXuiClaimNames.Gamertag && string.IsNullOrEmpty(claims.Gamertag)) || 37 | (name == XboxAuthXuiClaimNames.Privileges && string.IsNullOrEmpty(claims.Privileges)) || 38 | (name == XboxAuthXuiClaimNames.UserHash && string.IsNullOrEmpty(claims.UserHash)) || 39 | (name == XboxAuthXuiClaimNames.XboxUserId && string.IsNullOrEmpty(claims.XboxUserId)) || 40 | (name == XboxAuthXuiClaimNames.UserSettingsRestrictions && string.IsNullOrEmpty(claims.UserSettingsRestrictions)) || 41 | (name == XboxAuthXuiClaimNames.UserTitleRestrictions && string.IsNullOrEmpty(claims.UserTitleRestrictions)); 42 | if (invalid) 43 | return false; 44 | } 45 | return true; 46 | } 47 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxAuthNet.Game.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | true 6 | true 7 | enable 8 | netstandard2.0 9 | enable 10 | 10.0 11 | 12 | 13 | 14 | XboxAuthNet.Game 15 | Copyright (c) 2023 AlphaBs 16 | MIT 17 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 18 | https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft 19 | ksi123456ab 20 | microsoft xbox game auth 21 | 1.3.2 22 | icon.png 23 | git 24 | XboxAuthNet.Game 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | all 38 | runtime; build; native; contentfiles; analyzers; buildtransitive 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxGameLoginHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using XboxAuthNet.Game.Accounts; 3 | using XboxAuthNet.Game.Authenticators; 4 | using XboxAuthNet.Game.SessionStorages; 5 | 6 | namespace XboxAuthNet.Game; 7 | 8 | public class XboxGameLoginHandler 9 | { 10 | private readonly ILogger _logger; 11 | protected readonly HttpClient HttpClient; 12 | public IXboxGameAccountManager AccountManager { get; } 13 | 14 | public XboxGameLoginHandler(LoginHandlerParameters parameters) 15 | { 16 | _logger = parameters.Logger; 17 | HttpClient = parameters.HttpClient; 18 | AccountManager = parameters.AccountManager; 19 | } 20 | 21 | public NestedAuthenticator CreateAuthenticator( 22 | IXboxGameAccount account, 23 | CancellationToken cancellationToken) => 24 | CreateAuthenticator(account.SessionStorage, cancellationToken); 25 | 26 | public NestedAuthenticator CreateAuthenticator( 27 | ISessionStorage sessionStorage, 28 | CancellationToken cancellationToken) 29 | { 30 | var authenticator = new NestedAuthenticator(); 31 | authenticator.Context = createContext(sessionStorage, cancellationToken); 32 | authenticator.AddPostAuthenticator(LastAccessLogger.Default); 33 | authenticator.AddPostAuthenticator(new AccountSaver(AccountManager)); 34 | return authenticator; 35 | } 36 | 37 | private AuthenticateContext createContext( 38 | ISessionStorage sessionStorage, 39 | CancellationToken cancellationToken) 40 | { 41 | return new AuthenticateContext( 42 | sessionStorage, 43 | HttpClient, 44 | cancellationToken, 45 | _logger); 46 | } 47 | 48 | public NestedAuthenticator CreateAuthenticatorWithDefaultAccount( 49 | CancellationToken cancellationToken = default) => 50 | CreateAuthenticator(AccountManager.GetDefaultAccount(), cancellationToken); 51 | 52 | public NestedAuthenticator CreateAuthenticatorWithNewAccount( 53 | CancellationToken cancellationToken = default) => 54 | CreateAuthenticator(AccountManager.NewAccount(), cancellationToken); 55 | } -------------------------------------------------------------------------------- /src/XboxAuthNet.Game/XboxGameLoginHandlerBuilder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Abstractions; 3 | using XboxAuthNet.Game.Accounts; 4 | 5 | namespace XboxAuthNet.Game; 6 | 7 | public class XboxGameLoginHandlerBuilder : 8 | XboxGameLoginHandlerBuilderBase 9 | { 10 | 11 | } 12 | 13 | public abstract class XboxGameLoginHandlerBuilderBase 14 | where T : XboxGameLoginHandlerBuilderBase 15 | { 16 | private HttpClient? _httpClient; 17 | public HttpClient HttpClient 18 | { 19 | get => _httpClient ??= HttpHelper.DefaultHttpClient.Value; 20 | set => _httpClient = value; 21 | } 22 | 23 | private IXboxGameAccountManager? _accountManager; 24 | public IXboxGameAccountManager AccountManager 25 | { 26 | get => _accountManager ??= CreateDefaultAccountManager(); 27 | set => _accountManager = value; 28 | } 29 | 30 | public ILogger Logger { get; set; } = NullLogger.Instance; 31 | 32 | public T WithHttpClient(HttpClient httpClient) 33 | { 34 | HttpClient = httpClient; 35 | return getThis(); 36 | } 37 | 38 | public T WithAccountManager(IXboxGameAccountManager accountManager) 39 | { 40 | AccountManager = accountManager; 41 | return getThis(); 42 | } 43 | 44 | public T WithLogger(ILogger logger) 45 | { 46 | Logger = logger; 47 | return getThis(); 48 | } 49 | 50 | protected virtual IXboxGameAccountManager CreateDefaultAccountManager() 51 | { 52 | return new JsonXboxGameAccountManager("accounts.json"); 53 | } 54 | 55 | private T getThis() => (T)this; 56 | 57 | public LoginHandlerParameters BuildParameters() 58 | { 59 | return new LoginHandlerParameters( 60 | httpClient: HttpClient, 61 | accountManager: AccountManager, 62 | logger: Logger 63 | ); 64 | } 65 | } -------------------------------------------------------------------------------- /tests/CmlLib.Core.Auth.Microsoft.Test/CmlLib.Core.Auth.Microsoft.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/CmlLib.Core.Auth.Microsoft.Test/MsalSample.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Msal; 2 | using Microsoft.Identity.Client; 3 | 4 | namespace CmlLib.Core.Auth.Microsoft.Test; 5 | 6 | public class MsalSample 7 | { 8 | IPublicClientApplication app = null!; 9 | 10 | public async Task Setup() 11 | { 12 | app = await MsalClientHelper.BuildApplicationWithCache("499c8d36-be2a-4231-9ebd-ef291b7bb64c"); 13 | } 14 | 15 | public async Task Silently() 16 | { 17 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 18 | 19 | var authenticator = loginHandler.CreateAuthenticatorWithDefaultAccount(); 20 | authenticator.AddMsalOAuth(app, msal => msal.Silent()); 21 | authenticator.AddXboxAuthForJE(xbox => xbox.Basic()); 22 | authenticator.AddJEAuthenticator(); 23 | return await authenticator.ExecuteForLauncherAsync(); 24 | } 25 | 26 | public async Task Interactively() 27 | { 28 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 29 | 30 | var authenticator = loginHandler.CreateAuthenticatorWithNewAccount(); 31 | authenticator.AddMsalOAuth(app, msal => msal.Interactive()); 32 | authenticator.AddXboxAuthForJE(xbox => xbox.Basic()); 33 | authenticator.AddJEAuthenticator(); 34 | return await authenticator.ExecuteForLauncherAsync(); 35 | } 36 | 37 | public async Task DeviceCode() 38 | { 39 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 40 | 41 | var authenticator = loginHandler.CreateAuthenticatorWithNewAccount(); 42 | authenticator.AddMsalOAuth(app, msal => msal.DeviceCode(deviceCodeHandler)); 43 | authenticator.AddXboxAuthForJE(xbox => xbox.Basic()); 44 | authenticator.AddJEAuthenticator(); 45 | return await authenticator.ExecuteForLauncherAsync(); 46 | } 47 | 48 | private Task deviceCodeHandler(DeviceCodeResult deviceCode) 49 | { 50 | Console.WriteLine(deviceCode.Message); 51 | return Task.CompletedTask; 52 | } 53 | } -------------------------------------------------------------------------------- /tests/CmlLib.Core.Auth.Microsoft.Test/Sample.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | 3 | namespace CmlLib.Core.Auth.Microsoft.Test; 4 | 5 | public class Sample 6 | { 7 | public static async Task Simplest() 8 | { 9 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 10 | var session = await loginHandler.Authenticate(); 11 | return session; 12 | } 13 | 14 | public static async Task Interactively() 15 | { 16 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 17 | return await loginHandler.AuthenticateInteractively(); 18 | } 19 | 20 | public static async Task Silently() 21 | { 22 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 23 | return await loginHandler.AuthenticateSilently(); 24 | } 25 | 26 | public static async Task InteractivelyWithOptions() 27 | { 28 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 29 | var authenticator = loginHandler.CreateAuthenticatorWithNewAccount(); 30 | authenticator.AddMicrosoftOAuthForJE(oauth => oauth.Interactive()); 31 | authenticator.AddXboxAuthForJE(xbox => xbox.Basic()); 32 | authenticator.AddJEAuthenticator(); 33 | return await authenticator.ExecuteForLauncherAsync(); 34 | } 35 | 36 | public static async Task SilentlyWithOptions() 37 | { 38 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 39 | var authenticator = loginHandler.CreateAuthenticatorWithDefaultAccount(); 40 | authenticator.AddForceMicrosoftOAuthForJE(oauth => oauth.Silent()); 41 | authenticator.AddForceXboxAuth(xbox => xbox.Basic()); 42 | authenticator.AddJEAuthenticator(); 43 | return await authenticator.ExecuteForLauncherAsync(); 44 | } 45 | 46 | public static async Task Signout() 47 | { 48 | var loginHandler = JELoginHandlerBuilder.BuildDefault(); 49 | await loginHandler.Signout(); 50 | } 51 | } -------------------------------------------------------------------------------- /tests/CmlLib.Core.Bedrock.Auth.Test/CmlLib.Core.Bedrock.Auth.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/CmlLib.Core.Bedrock.Auth.Test/Sample.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game; 2 | using XboxAuthNet.Game.Accounts; 3 | using CmlLib.Core.Bedrock.Auth.Sessions; 4 | using XboxAuthNet.Game.OAuth; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | 7 | namespace CmlLib.Core.Bedrock.Auth.Test; 8 | 9 | public class Sample 10 | { 11 | MicrosoftOAuthClientInfo clientInfo = new( 12 | "", ""); 13 | 14 | private XboxGameLoginHandler buildLoginHandler() 15 | { 16 | var parameters = new XboxGameLoginHandlerBuilder() 17 | .BuildParameters(); 18 | return new XboxGameLoginHandler(parameters); 19 | } 20 | 21 | public async Task AuthenticateInteractively() 22 | { 23 | var loginHandler = buildLoginHandler(); 24 | var authenticator = loginHandler.CreateAuthenticatorWithNewAccount(); 25 | authenticator.AddForceMicrosoftOAuth(clientInfo, oauth => oauth.Interactive()); 26 | authenticator.AddForceXboxAuthForBE(xbox => xbox.Basic()); 27 | authenticator.AddBEAuthenticator(); 28 | 29 | var result = await authenticator.ExecuteAsync(); 30 | var account = BEGameAccount.FromSessionStorage(result); 31 | return account.Session; 32 | } 33 | 34 | public async Task AuthenticateSilently() 35 | { 36 | var loginHandler = buildLoginHandler(); 37 | var authenticator = loginHandler.CreateAuthenticatorWithDefaultAccount(); 38 | authenticator.AddForceMicrosoftOAuth(clientInfo, oauth => oauth.Silent()); 39 | authenticator.AddForceXboxAuthForBE(xbox => xbox.Basic()); 40 | authenticator.AddBEAuthenticator(); 41 | 42 | var result = await authenticator.ExecuteAsync(); 43 | var account = BEGameAccount.FromSessionStorage(result); 44 | return account.Session; 45 | } 46 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Accounts/TestAccount.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Accounts; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.Test.Accounts; 5 | 6 | public class TestAccount : XboxGameAccount 7 | { 8 | public static TestAccount Create(string identifier) => 9 | createInternal(identifier, DateTime.MinValue); 10 | 11 | public static TestAccount Create(string identifier, DateTime lastAccess) => 12 | createInternal(identifier, lastAccess); 13 | 14 | public static TestAccount CreateNull() => 15 | createInternal(null, DateTime.MinValue); 16 | 17 | private static TestAccount createInternal(string? identifier, DateTime lastAccess) 18 | { 19 | var sessionStorage = new InMemorySessionStorage(); 20 | sessionStorage.Set("identifier", identifier); 21 | LastAccessSource.Default.Set(sessionStorage, lastAccess); 22 | return new TestAccount(sessionStorage); 23 | } 24 | 25 | public TestAccount(ISessionStorage sessionStorage) 26 | : base(sessionStorage) 27 | { 28 | 29 | } 30 | 31 | protected override string? GetIdentifier() 32 | { 33 | SessionStorage.TryGetValue("identifier", out var value); 34 | return value; 35 | } 36 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Accounts/XboxGameAccountCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.Accounts; 3 | using XboxAuthNet.Game.SessionStorages; 4 | 5 | namespace XboxAuthNet.Game.Test.Accounts; 6 | 7 | [TestFixture] 8 | public class XboxGameAccountCollectionTest 9 | { 10 | [Test] 11 | public void TestEmpty() 12 | { 13 | var collection = new XboxGameAccountCollection(); 14 | Assert.That(collection.Count, Is.EqualTo(0)); 15 | } 16 | 17 | [Test] 18 | public void TestAddOne() 19 | { 20 | var account = TestAccount.Create("test"); 21 | var collection = new XboxGameAccountCollection(); 22 | collection.Add(account); 23 | 24 | Assert.That(collection.Count, Is.EqualTo(1)); 25 | Assert.That(collection.First(), Is.EqualTo(account)); 26 | } 27 | 28 | [Test] 29 | public void TestAddSameIdentifiers() 30 | { 31 | var account1 = TestAccount.Create("test", DateTime.MinValue); 32 | var account2 = TestAccount.Create("test", DateTime.MaxValue); 33 | var collection = new XboxGameAccountCollection() 34 | { 35 | account1, account2 36 | }; 37 | 38 | var actualAccount = collection.GetAccount("test"); 39 | Assert.That(collection.Count, Is.EqualTo(1)); 40 | Assert.That(actualAccount, Is.EqualTo(account2)); 41 | } 42 | 43 | [Test] 44 | public void TestRemoveAccount() 45 | { 46 | var account1 = TestAccount.Create("test", DateTime.MinValue); 47 | var account2 = TestAccount.Create("test", DateTime.MaxValue); 48 | var account3 = TestAccount.Create("test2"); 49 | var collection = new XboxGameAccountCollection() 50 | { 51 | account1, account2, account3 52 | }; 53 | 54 | collection.RemoveAccount("test"); 55 | Assert.That(collection.Count, Is.EqualTo(1)); 56 | } 57 | 58 | [Test] 59 | public void TestGetAccount() 60 | { 61 | var account = TestAccount.Create("test"); 62 | var collection = new XboxGameAccountCollection(); 63 | collection.Add(account); 64 | 65 | var actualAccount = collection.GetAccount("test"); 66 | Assert.That(actualAccount, Is.EqualTo(account)); 67 | } 68 | 69 | [Test] 70 | public void TestTryGetAccount() 71 | { 72 | var account = TestAccount.Create("test"); 73 | var collection = new XboxGameAccountCollection(); 74 | collection.Add(account); 75 | 76 | var result = collection.TryGetAccount("test", out var actualAccount); 77 | Assert.True(result); 78 | Assert.That(actualAccount, Is.EqualTo(account)); 79 | } 80 | 81 | [Test] 82 | public void TestTryGetAccountForNonExisting() 83 | { 84 | var account = TestAccount.Create("test"); 85 | var collection = new XboxGameAccountCollection(); 86 | collection.Add(account); 87 | 88 | var result = collection.TryGetAccount("1234", out var actualAccount); 89 | Assert.False(result); 90 | Assert.That(actualAccount, Is.Not.EqualTo(account)); 91 | } 92 | 93 | [Test] 94 | public void TestEnumerate() 95 | { 96 | var account1 = TestAccount.Create("z", DateTime.MinValue.AddDays(8)); 97 | var account2 = TestAccount.Create("z", DateTime.MinValue.AddDays(1)); 98 | var account3 = TestAccount.Create("a", DateTime.MinValue.AddDays(3)); 99 | var account4 = TestAccount.Create("a", DateTime.MinValue.AddDays(6)); 100 | var collection = new XboxGameAccountCollection() 101 | { 102 | account1, account2, account3, account4 103 | }; 104 | 105 | IXboxGameAccount[] enumerated = collection.ToArray(); 106 | Assert.That(enumerated, Is.EqualTo(new IXboxGameAccount[] 107 | { 108 | account1, // account1 and account2 have a same identifier, choose more recent one 109 | account4 // accounts are ordered by access time with descending order 110 | })); 111 | } 112 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/AuthenticatorCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.SessionStorages; 4 | 5 | namespace XboxAuthNet.Game.Test.Authenticators; 6 | 7 | [TestFixture] 8 | public class AuthenticatorCollectionTest 9 | { 10 | [Test] 11 | public async Task TestIsAllAuthenticatorExecuted() 12 | { 13 | var mocks = new MockAuthenticatorFactory(); 14 | var collection = new AuthenticatorCollection(); 15 | collection.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 16 | collection.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 17 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 18 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 19 | 20 | await collection.ExecuteAsync(mocks.CreateContext()); 21 | mocks.TestExpectations(); 22 | } 23 | 24 | [Test] 25 | public async Task TestValidAuthenticator() 26 | { 27 | var mocks = new MockAuthenticatorFactory(); 28 | var collection = new AuthenticatorCollection(); 29 | collection.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 30 | collection.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 31 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 32 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 33 | 34 | await collection.ExecuteAsync(mocks.CreateContext()); 35 | mocks.TestExpectations(); 36 | } 37 | 38 | [Test] 39 | public async Task TestMixedAuthenticator() 40 | { 41 | var mocks = new MockAuthenticatorFactory(); 42 | var collection = new AuthenticatorCollection(); 43 | collection.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 44 | collection.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 45 | collection.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 46 | collection.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 47 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 48 | collection.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 49 | 50 | await collection.ExecuteAsync(mocks.CreateContext()); 51 | mocks.TestExpectations(); 52 | } 53 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/MockAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game.Test.Authenticators; 4 | 5 | public class MockAuthenticator : IAuthenticator 6 | { 7 | public MockAuthenticator(bool expectedToBeExecuted) 8 | { 9 | ExpectedToBeExecuted = expectedToBeExecuted; 10 | } 11 | 12 | public bool ExpectedToBeExecuted { get; private set; } 13 | public bool IsExecuted { get; private set; } = false; 14 | 15 | public ValueTask ExecuteAsync(AuthenticateContext context) 16 | { 17 | IsExecuted = true; 18 | return new ValueTask(); 19 | } 20 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/MockAuthenticatorFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging.Abstractions; 2 | using NUnit.Framework; 3 | using XboxAuthNet.Game.Authenticators; 4 | using XboxAuthNet.Game.SessionStorages; 5 | 6 | namespace XboxAuthNet.Game.Test.Authenticators; 7 | 8 | public class MockAuthenticatorFactory 9 | { 10 | private List authenticators = new(); 11 | 12 | public IAuthenticator ExpectToBeExecuted() 13 | { 14 | var authenticator = new MockAuthenticator(true); 15 | authenticators.Add(authenticator); 16 | return authenticator; 17 | } 18 | 19 | public IAuthenticator ExpectToNotBeExecuted() 20 | { 21 | var authenticator = new MockAuthenticator(false); 22 | authenticators.Add(authenticator); 23 | return authenticator; 24 | } 25 | 26 | public IAuthenticator Throw() => 27 | Throw(new TaskCanceledException()); 28 | 29 | public IAuthenticator Throw(Exception ex) 30 | { 31 | var authenticator = new ThrowingAuthenticator(ex); 32 | return authenticator; 33 | } 34 | 35 | public void TestExpectations() 36 | { 37 | string toStr(bool v) => v ? "EXECUTED" : "NOT EXECUTED"; 38 | 39 | var failIndex = authenticators.FindIndex(x => (x.IsExecuted != x.ExpectedToBeExecuted)); 40 | if (failIndex > -1) 41 | { 42 | Assert.Fail($"Unexpected behaviour. Index number: {failIndex}, " + 43 | $"Expected: {toStr(authenticators[failIndex].ExpectedToBeExecuted)}, " + 44 | $"Actual: {toStr(authenticators[failIndex].IsExecuted)}"); 45 | } 46 | } 47 | 48 | public AuthenticateContext CreateContext() => 49 | CreateContext(new InMemorySessionStorage()); 50 | 51 | public AuthenticateContext CreateContext(ISessionStorage sessionStorage) 52 | { 53 | return new AuthenticateContext( 54 | sessionStorage, 55 | null!, 56 | default, 57 | NullLogger.Instance); 58 | } 59 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/NestedAuthenticatorTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.Authenticators; 3 | 4 | namespace XboxAuthNet.Game.Test.Authenticators; 5 | 6 | [TestFixture] 7 | public class NestedAuthenticatorTest 8 | { 9 | [Test] 10 | public async Task TestMixedAuthenticator1() 11 | { 12 | var mocks = new MockAuthenticatorFactory(); 13 | var authenticator = new NestedAuthenticator(); 14 | authenticator.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToNotBeExecuted()); 15 | authenticator.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 16 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 17 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 18 | 19 | await authenticator.ExecuteAsync(mocks.CreateContext()); 20 | mocks.TestExpectations(); 21 | } 22 | 23 | [Test] 24 | public async Task TestMixedAuthenticator2() 25 | { 26 | var mocks = new MockAuthenticatorFactory(); 27 | var authenticator = new AuthenticatorCollection(); 28 | authenticator.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 29 | authenticator.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 30 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 31 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 32 | 33 | await authenticator.ExecuteAsync(mocks.CreateContext()); 34 | mocks.TestExpectations(); 35 | } 36 | 37 | [Test] 38 | public async Task TestValidAuthenticator() 39 | { 40 | var mocks = new MockAuthenticatorFactory(); 41 | var authenticator = new NestedAuthenticator(); 42 | authenticator.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 43 | authenticator.AddAuthenticator(StaticValidator.Valid, mocks.ExpectToNotBeExecuted()); 44 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 45 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 46 | 47 | await authenticator.ExecuteAsync(mocks.CreateContext()); 48 | mocks.TestExpectations(); 49 | } 50 | 51 | [Test] 52 | public async Task TestInvalidAuthenticator() 53 | { 54 | var mocks = new MockAuthenticatorFactory(); 55 | var authenticator = new AuthenticatorCollection(); 56 | authenticator.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 57 | authenticator.AddAuthenticator(StaticValidator.Invalid, mocks.ExpectToBeExecuted()); 58 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 59 | authenticator.AddPostAuthenticator(mocks.ExpectToBeExecuted()); 60 | 61 | await authenticator.ExecuteAsync(mocks.CreateContext()); 62 | mocks.TestExpectations(); 63 | } 64 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/StaticSessionAuthenticatorTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.SessionStorages; 4 | 5 | namespace XboxAuthNet.Game.Test.Authenticators; 6 | 7 | [TestFixture] 8 | public class StaticSessionAuthenticatorTest 9 | { 10 | [Test] 11 | public async Task Test() 12 | { 13 | var mocks = new MockAuthenticatorFactory(); 14 | var testObject = new object(); 15 | var sessionStorage = new InMemorySessionStorage(); 16 | var sessionSource = new SessionFromStorage("testKey"); 17 | 18 | var authenticator = new StaticSessionAuthenticator(testObject, sessionSource); 19 | await authenticator.ExecuteAsync(mocks.CreateContext(sessionStorage)); 20 | 21 | var stored = sessionSource.Get(sessionStorage); 22 | Assert.That(stored, Is.EqualTo(testObject)); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Authenticators/ThrowingAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using XboxAuthNet.Game.Authenticators; 2 | 3 | namespace XboxAuthNet.Game.Test.Authenticators; 4 | 5 | public class ThrowingAuthenticator : IAuthenticator 6 | { 7 | private readonly Exception _ex; 8 | 9 | public ThrowingAuthenticator(Exception ex) => 10 | _ex = ex; 11 | 12 | public ValueTask ExecuteAsync(AuthenticateContext context) 13 | { 14 | throw _ex; 15 | } 16 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/LastAccessSourceTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.SessionStorages; 3 | 4 | namespace XboxAuthNet.Game.Test; 5 | 6 | [TestFixture] 7 | public class LastAccessSourceTests 8 | { 9 | [Test] 10 | public void Get() 11 | { 12 | var test = DateTime.Now; 13 | 14 | var sessionStorage = new InMemorySessionStorage(); 15 | sessionStorage.Set(LastAccessSource.KeyName, test.ToString("o")); 16 | 17 | var lastAccessSource = new LastAccessSource(); 18 | var actual = lastAccessSource.Get(sessionStorage); 19 | 20 | Assert.That(actual, Is.EqualTo(test).Within(1).Seconds); 21 | } 22 | 23 | [Test] 24 | public void GetInvalidFormat() 25 | { 26 | var test = "random_text_it_is_invalid_date_format"; 27 | 28 | var sessionStorage = new InMemorySessionStorage(); 29 | sessionStorage.Set(LastAccessSource.KeyName, test); 30 | 31 | var lastAccessSource = new LastAccessSource(); 32 | var actual = lastAccessSource.Get(sessionStorage); 33 | 34 | Assert.AreEqual(DateTime.MinValue, actual); 35 | } 36 | 37 | [Test] 38 | public void GetNoKey() 39 | { 40 | var sessionStorage = new InMemorySessionStorage(); 41 | var lastAccessSource = new LastAccessSource(); 42 | var actual = lastAccessSource.Get(sessionStorage); 43 | 44 | Assert.AreEqual(DateTime.MinValue, actual); 45 | } 46 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/MicrosoftOAuthBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using XboxAuthNet.Game.Authenticators; 3 | using XboxAuthNet.Game.OAuth; 4 | using XboxAuthNet.OAuth; 5 | 6 | namespace XboxAuthNet.Game.Test; 7 | 8 | [TestFixture] 9 | public class MicrosoftOAuthBuilderTest 10 | { 11 | [Test] 12 | public void TestInteractive() 13 | { 14 | var builder = createBuilder(); 15 | var authenticator = builder.Interactive(); 16 | Assert.That(authenticator, Is.InstanceOf()); 17 | } 18 | 19 | [Test] 20 | public void TestSilent() 21 | { 22 | var builder = createBuilder(); 23 | var authenticator = builder.Silent(); 24 | Assert.That(authenticator, Is.InstanceOf()); 25 | } 26 | 27 | [Test] 28 | public void TestValidator() 29 | { 30 | var builder = createBuilder(); 31 | var authenticator = builder.Validator(); 32 | Assert.That(authenticator, Is.InstanceOf()); 33 | } 34 | 35 | public void TestSignout() 36 | { 37 | var builder = createBuilder(); 38 | var authenticator = builder.Signout(); 39 | Assert.That(authenticator, Is.InstanceOf()); 40 | } 41 | 42 | public void TestFromMicrosoftOAuthResponse() 43 | { 44 | var builder = createBuilder(); 45 | var authenticator = builder.FromMicrosoftOAuthResponse(new MicrosoftOAuthResponse()); 46 | Assert.That(authenticator, Is.InstanceOf>()); 47 | } 48 | 49 | public MicrosoftOAuthBuilder createBuilder() 50 | { 51 | return new MicrosoftOAuthBuilder(new MicrosoftOAuthClientInfo("ClientId", "Scopes")); 52 | } 53 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/MockObjects.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace XboxAuthNet.Game.Test 3 | { 4 | public record MockObject 5 | { 6 | public string? Name { get; init; } 7 | public int Age { get; init; } 8 | public char Type { get; init; } 9 | public bool IsObject { get; init; } 10 | public InnerMockObject? InnerData1 { get; init; } 11 | public InnerMockObject? InnerData2 { get; init; } 12 | } 13 | 14 | public record InnerMockObject(string text, int number); 15 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/Program.cs: -------------------------------------------------------------------------------- 1 | // THIS FILE SHOULD NOT BE COMPILED! 2 | // I can't debug unit test project in some reason. (maybe my cloud development environment setting has some problem) 3 | // So whenever I need to debug unit test I just change project type and manually call unit test method 4 | // It works 5 | 6 | #if !TEST_SDK 7 | 8 | using XboxAuthNet.Game.Test; 9 | 10 | var test = new XboxGameAccountTest(); 11 | test.TestEqualOne(); 12 | 13 | #endif -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/SessionStorages/JsonFileSessionStorageTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using NUnit.Framework; 3 | using XboxAuthNet.Game.SessionStorages; 4 | 5 | namespace XboxAuthNet.Game.Test; 6 | 7 | [TestFixture] 8 | public class JsonFileSessionStorageTests 9 | { 10 | private ISessionStorage? sessionStorage; 11 | private string? filePath; 12 | 13 | [OneTimeSetUp] 14 | public void Setup() 15 | { 16 | filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); 17 | sessionStorage = createSessionStorage(); 18 | Trace.Listeners.Add(new ConsoleTraceListener()); 19 | } 20 | 21 | private ISessionStorage createSessionStorage() 22 | { 23 | if (string.IsNullOrEmpty(filePath)) 24 | throw new InvalidOperationException(); 25 | return new JsonFileSessionStorage(filePath); 26 | } 27 | 28 | [Test] 29 | [TestCase(1)] 30 | [TestCase(int.MaxValue)] 31 | [TestCase(int.MinValue)] 32 | public void TestIntValue(int value) 33 | { 34 | Assert.NotNull(sessionStorage); 35 | sessionStorage!.Set(nameof(TestIntValue), value); 36 | var savedValue = sessionStorage!.Get(nameof(TestIntValue)); 37 | 38 | Assert.AreEqual(value, savedValue); 39 | } 40 | 41 | [Test] 42 | public void TestObject() 43 | { 44 | Assert.NotNull(sessionStorage); 45 | 46 | var mockObject = new MockObject() 47 | { 48 | Name = "Hello", 49 | Age = 1234, 50 | Type = 'c', 51 | IsObject = true, 52 | InnerData1 = new InnerMockObject("T1", 1), 53 | InnerData2 = new InnerMockObject("T2", 2) 54 | }; 55 | 56 | sessionStorage!.Set(nameof(TestObject), mockObject); 57 | var savedValue = sessionStorage.Get(nameof(TestObject)); 58 | 59 | // compare the value of object, not a reference. 60 | var copiedMockObject = mockObject with { }; // same value, new reference 61 | Assert.True(!Object.ReferenceEquals(mockObject, copiedMockObject)); 62 | Assert.AreEqual(copiedMockObject, savedValue); 63 | } 64 | 65 | [Test] 66 | public void TestWithNewInstance() 67 | { 68 | Assert.NotNull(sessionStorage); 69 | 70 | var testKey = nameof(TestWithNewInstance); 71 | var testData = "test_data_for_" + testKey; 72 | 73 | sessionStorage!.Set(testKey, testData); 74 | 75 | var newSessionStorage = createSessionStorage(); 76 | var savedValue = newSessionStorage.Get(testKey); 77 | 78 | Assert.AreEqual(testData, savedValue); 79 | } 80 | 81 | [OneTimeTearDown] 82 | public void Teardown() 83 | { 84 | if (string.IsNullOrEmpty(filePath)) 85 | return; 86 | 87 | if (File.Exists(filePath)) 88 | { 89 | File.Delete(filePath); 90 | } 91 | 92 | Trace.Flush(); 93 | } 94 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/SessionStorages/JsonSessionStorageComparer.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using XboxAuthNet.Game.SessionStorages; 9 | 10 | namespace XboxAuthNet.Game.Test.SessionStorages 11 | { 12 | internal class JsonSessionStorageComparer 13 | { 14 | private readonly Random _rnd; 15 | 16 | public ISessionStorage SessionStorage { get; } 17 | public Dictionary Dictionary { get; } 18 | public Dictionary> Operations { get; } 19 | 20 | public JsonSessionStorageComparer(Random rnd) 21 | { 22 | Operations = new() 23 | { 24 | ['g'] = get, 25 | ['r'] = remove, 26 | ['s'] = set, 27 | ['c'] = containsKey, 28 | }; 29 | SessionStorage = JsonSessionStorage.CreateEmpty(); 30 | Dictionary = new(); 31 | _rnd = rnd; 32 | } 33 | 34 | public void Do(char op) 35 | { 36 | var param = _rnd.Next(0, 2); 37 | Operations[op].Invoke(param); 38 | } 39 | 40 | void get(int param) 41 | { 42 | var key = param.ToString(); 43 | assertSameResult( 44 | () => SessionStorage.Get(key), 45 | () => Dictionary[key]); 46 | } 47 | 48 | void remove(int param) 49 | { 50 | var key = param.ToString(); 51 | assertSameResult( 52 | () => SessionStorage.Remove(key), 53 | () => Dictionary.Remove(key)); 54 | } 55 | 56 | void set(int param) 57 | { 58 | var key = param.ToString(); 59 | var value = "test_string"; 60 | assertSameResult( 61 | () => SessionStorage.Set(key, value), 62 | () => Dictionary[key] = value); 63 | } 64 | 65 | void containsKey(int param) 66 | { 67 | var key = param.ToString(); 68 | assertSameResult( 69 | () => SessionStorage.ContainsKey(key), 70 | () => Dictionary.ContainsKey(key)); 71 | } 72 | 73 | void assertSameResult(Action action1, Action action2) 74 | { 75 | Exception? ex1 = null; 76 | Exception? ex2 = null; 77 | 78 | try 79 | { 80 | action1(); 81 | } 82 | catch (Exception e) 83 | { 84 | ex1 = e; 85 | } 86 | 87 | try 88 | { 89 | action2(); 90 | } 91 | catch (Exception e) 92 | { 93 | ex2 = e; 94 | } 95 | 96 | Assert.That(ex1, Is.EqualTo(ex2)); 97 | } 98 | 99 | void assertSameResult(Func action1, Func action2) 100 | { 101 | T? result1 = default; 102 | T? result2 = default; 103 | Exception? ex1 = null; 104 | Exception? ex2 = null; 105 | 106 | try 107 | { 108 | result1 = action1(); 109 | } 110 | catch (Exception e) 111 | { 112 | ex1 = e; 113 | } 114 | 115 | try 116 | { 117 | result2 = action2(); 118 | } 119 | catch (Exception e) 120 | { 121 | ex2 = e; 122 | } 123 | 124 | Assert.That(result1, Is.EqualTo(result2)); 125 | Assert.That(ex1?.GetType(), Is.EqualTo(ex2?.GetType())); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/SessionStorages/JsonSessionStorageComparerExecutor.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace XboxAuthNet.Game.Test.SessionStorages 7 | { 8 | [TestFixture] 9 | [Category("long")] 10 | public class JsonSessionStorageComparerExecutor 11 | { 12 | private JsonSessionStorageComparer comparer = null!; 13 | 14 | [SetUp] 15 | public void Setup() 16 | { 17 | comparer = new JsonSessionStorageComparer(Random.Shared); 18 | } 19 | 20 | [Test] 21 | public void Test() 22 | { 23 | foreach (var operations in generateOperations()) 24 | { 25 | //Console.WriteLine(operations); 26 | Test(operations); 27 | } 28 | } 29 | 30 | private IEnumerable generateOperations() 31 | { 32 | var availableOps = comparer.Operations.Keys.ToArray(); 33 | return M(6, 0, availableOps.Length, availableOps) 34 | .Select(chars => new string(chars.ToArray())); 35 | } 36 | 37 | public void Test(string ops) 38 | { 39 | foreach (var op in ops.ToCharArray()) 40 | { 41 | assertEqualResult(op); 42 | } 43 | } 44 | 45 | public void assertEqualResult(char op) 46 | { 47 | comparer.Do(op); 48 | assertCollectionIsEqual(); 49 | } 50 | 51 | public void assertCollectionIsEqual() 52 | { 53 | var count = 0; 54 | foreach (var key in comparer.SessionStorage.Keys) 55 | { 56 | Assert.That(comparer.SessionStorage.Get(key), Is.EqualTo(comparer.Dictionary[key])); 57 | count++; 58 | } 59 | 60 | Assert.That(count, Is.EqualTo(comparer.Dictionary.Count)); 61 | } 62 | 63 | static IEnumerable Prepend(T first, IEnumerable rest) 64 | { 65 | yield return first; 66 | foreach (var item in rest) 67 | yield return item; 68 | } 69 | 70 | static IEnumerable> M(int p, int t1, int t2, T[] arr) 71 | { 72 | if (p == 0) 73 | yield return Enumerable.Empty(); 74 | else 75 | for (int first = t1; first < t2; ++first) 76 | foreach (var rest in M(p - 1, t1, t2, arr)) 77 | yield return Prepend(arr[first], rest); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/TestJwtDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XboxAuthNet.Game.Jwt; 3 | using NUnit.Framework; 4 | 5 | namespace XboxAuthNet.Game.Test 6 | { 7 | public class TestJwtDecoder 8 | { 9 | [Test] 10 | // encoded payload length: 95 % 4 = 3 11 | [TestCase("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3MiLCJpYXQiOjE2NTcxOTUzNTYsImV4cCI6MTY1NzE5NjU2OCwiYXVkIjoiYXVkIiwic3ViIjoic3ViIn0._Oii5N3wxCTHNMzznLAr6hIMkDTdcHn33If0YjLd6Wk", 12 | "{\"iss\":\"iss\",\"iat\":1657195356,\"exp\":1657196568,\"aud\":\"aud\",\"sub\":\"sub\"}")] 13 | // encoded payload length: 96 % 4 = 0 14 | [TestCase("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3MiLCJpYXQiOjE2NTcxOTUzNTYsImV4cCI6MTY1NzE5NjU2OCwiYXVkIjoiYXVkZCIsInN1YiI6InN1YiJ9._8FqWLFG1xQI8ydIiIiy9Jpilu-zsvhEkK65H3NICcY", 15 | "{\"iss\":\"iss\",\"iat\":1657195356,\"exp\":1657196568,\"aud\":\"audd\",\"sub\":\"sub\"}")] 16 | // encoded payload length: 98 % 4 = 2 17 | [TestCase("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3MiLCJpYXQiOjE2NTcxOTUzNTYsImV4cCI6MTY1NzE5NjU2OCwiYXVkIjoiYXVkZGQiLCJzdWIiOiJzdWIifQ._Oii5N3wxCTHNMzznLAr6hIMkDTdcHn33If0YjLd6Wk", 18 | "{\"iss\":\"iss\",\"iat\":1657195356,\"exp\":1657196568,\"aud\":\"auddd\",\"sub\":\"sub\"}")] 19 | public void TestNormalJwt(string inputJwt, string expectedPayload) 20 | { 21 | var result = JwtDecoder.DecodePayloadString(inputJwt); 22 | Assert.AreEqual(expectedPayload, result); 23 | } 24 | 25 | [Test] 26 | [TestCase(null)] 27 | [TestCase("")] 28 | public void TestArgumentNullException(string inputJwt) 29 | { 30 | Assert.Throws(typeof(ArgumentNullException), () => 31 | { 32 | JwtDecoder.DecodePayloadString(inputJwt); 33 | }); 34 | } 35 | 36 | [Test] 37 | [TestCase(".")] 38 | [TestCase("hi")] 39 | [TestCase("head.!@#$.tail")] 40 | public void TestFormatException(string inputJwt) 41 | { 42 | Assert.Throws(typeof(FormatException), () => 43 | { 44 | JwtDecoder.DecodePayloadString(inputJwt); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /tests/XboxAuthNet.Game.Test/XboxAuthNet.Game.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | net6.0 7 | enable 8 | false 9 | enable 10 | 11 | 12 | 13 | $(DefineConstants);TEST_SDK 14 | Library 15 | 16 | 17 | 18 | Exe 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------