├── .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 | [](https://www.nuget.org/packages/CmlLib.Core.Auth.Microsoft)
6 | [](https://github.com/CmlLib/CmlLib.Core.Auth.Microsoft/blob/master/LICENSE)
7 |
8 | [](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