Skip to content

Commit

Permalink
add InteractiveWithSingleAccount
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaBs committed Jul 7, 2023
1 parent a76e8dc commit 3dcab10
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 42 deletions.
8 changes: 1 addition & 7 deletions src/CmlLib.Core.Auth.Microsoft/JELoginHandler.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using Microsoft.Extensions.Logging;
using XboxAuthNet.XboxLive;
using XboxAuthNet.Game;
using XboxAuthNet.Game.Accounts;
using XboxAuthNet.Game.OAuth;
using XboxAuthNet.Game.XboxAuth;
using CmlLib.Core.Auth.Microsoft.Sessions;
using XboxAuthNet.Game.Authenticators;

namespace CmlLib.Core.Auth.Microsoft;

Expand Down Expand Up @@ -35,11 +33,7 @@ public async Task<MSession> Authenticate(
CancellationToken cancellationToken = default)
{
var authenticator = CreateAuthenticator(account, cancellationToken);
authenticator.AddFallbackAuthenticator(StaticValidator.Invalid, fallback =>
{
fallback.AddMicrosoftOAuthForJE(oauth => oauth.Silent());
fallback.AddForceMicrosoftOAuthForJE(oauth => oauth.Interactive());
});
authenticator.AddMicrosoftOAuthForJE(oauth => oauth.CodeFlow());
authenticator.AddXboxAuthForJE(xbox => xbox.Basic());
authenticator.AddJEAuthenticator();
return await authenticator.ExecuteForLauncherAsync();
Expand Down
16 changes: 10 additions & 6 deletions src/XboxAuthNet.Game.Msal/OAuth/MsalInteractiveOAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ namespace XboxAuthNet.Game.Msal.OAuth;

public class MsalInteractiveOAuth : MsalOAuth
{
public bool UseDefaultWebViewOption { get; set; } = true;
public bool UseEmbeddedWebView { get; set; } = true;
private readonly Action<AcquireTokenInteractiveParameterBuilder> _builderInvoker;

public MsalInteractiveOAuth(MsalOAuthParameters parameters) :
public MsalInteractiveOAuth(
MsalOAuthParameters parameters,
Action<AcquireTokenInteractiveParameterBuilder> builderInvoker) :
base(parameters)
{

this._builderInvoker = builderInvoker;
}

protected override async ValueTask<AuthenticationResult> AuthenticateWithMsal(
Expand All @@ -22,8 +23,11 @@ protected override async ValueTask<AuthenticationResult> AuthenticateWithMsal(
var builder = parameters.MsalApplication
.AcquireTokenInteractive(parameters.Scopes);

if (!UseDefaultWebViewOption)
builder.WithUseEmbeddedWebView(UseEmbeddedWebView);
var loginHint = parameters.LoginHintSource.Get(context.SessionStorage);
if (!string.IsNullOrEmpty(loginHint))
builder.WithLoginHint(loginHint);

_builderInvoker.Invoke(builder);

var result = await builder.ExecuteAsync(context.CancellationToken);
return result;
Expand Down
50 changes: 40 additions & 10 deletions src/XboxAuthNet.Game.Msal/OAuth/MsalOAuthBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,69 @@ public ISessionSource<string> LoginHintSource
set => _loginHintSource = value;
}

public ISessionValidator LoginHintValidator(bool throwWhenInvalid = false) =>
new MicrosoftOAuthLoginHintValidator(throwWhenInvalid, LoginHintSource);

public IAuthenticator Silent() =>
new MsalSilentOAuth(createParameters());
new MsalSilentOAuth(createParameters(false));

public IAuthenticator Interactive() =>
new MsalInteractiveOAuth(createParameters());
Interactive(builderInvoker => { });

public IAuthenticator Interactive(Action<AcquireTokenInteractiveParameterBuilder> builderInvoker) =>
new MsalInteractiveOAuth(createParameters(false), builderInvoker);

public IAuthenticator EmbeddedWebView()
{
var authenticator = new MsalInteractiveOAuth(createParameters());
authenticator.UseDefaultWebViewOption = false;
authenticator.UseEmbeddedWebView = true;
var authenticator = new MsalInteractiveOAuth(createParameters(false), builder =>
{
builder.WithUseEmbeddedWebView(true);
});
return authenticator;
}

public IAuthenticator SystemBrowser()
{
var authenticator = new MsalInteractiveOAuth(createParameters());
authenticator.UseDefaultWebViewOption = false;
authenticator.UseEmbeddedWebView = false;
var authenticator = new MsalInteractiveOAuth(createParameters(false), builder =>
{
builder.WithUseEmbeddedWebView(false);
});
return authenticator;
}

public IAuthenticator InteractiveWithSingleAccount()
{
var authenticator = new FallbackAuthenticator();
authenticator.AddAuthenticatorWithoutValidator(Interactive(builder =>
{
// NoPrompt with LoginHint will try 'none' and also 'login' mode.
// Prompt.ForceLogin is unnecessary.
builder.WithPrompt(Prompt.NoPrompt);
}));
return authenticator;
}

public IAuthenticator CodeFlow()
{
var authenticator = new FallbackAuthenticator();
authenticator.AddAuthenticatorWithoutValidator(Silent());
authenticator.AddAuthenticator(LoginHintValidator(true), InteractiveWithSingleAccount());
authenticator.AddAuthenticatorWithoutValidator(Interactive());
return authenticator;
}

public IAuthenticator DeviceCode(Func<DeviceCodeResult, Task> deviceResultCallback) =>
new MsalDeviceCodeOAuth(createParameters(), deviceResultCallback);
new MsalDeviceCodeOAuth(createParameters(false), deviceResultCallback);

public IAuthenticator FromResult(AuthenticationResult result) =>
new StaticSessionAuthenticator<MicrosoftOAuthResponse>(
MsalClientHelper.ToMicrosoftOAuthResponse(result),
SessionSource);

private MsalOAuthParameters createParameters() => new(
private MsalOAuthParameters createParameters(bool loginHint) => new(
app: _app,
scopes: Scopes,
loginHintSource: LoginHintSource,
throwWhenEmptyLoginHint: loginHint,
sessionSource: SessionSource);
}
6 changes: 4 additions & 2 deletions src/XboxAuthNet.Game.Msal/OAuth/MsalOAuthParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ public MsalOAuthParameters(
IPublicClientApplication app,
string[] scopes,
ISessionSource<string> loginHintSource,
bool throwWhenEmptyLoginHint,
ISessionSource<MicrosoftOAuthResponse> sessionSource) =>
(MsalApplication, Scopes, LoginHintSource, SessionSource) =
(app, scopes, loginHintSource, sessionSource);
(MsalApplication, Scopes, LoginHintSource, ThrowWhenEmptyLoginHint, SessionSource) =
(app, scopes, loginHintSource, throwWhenEmptyLoginHint, sessionSource);

public IPublicClientApplication MsalApplication { get; }
public string[] Scopes { get; }
public ISessionSource<MicrosoftOAuthResponse> SessionSource;
public ISessionSource<string> LoginHintSource { get; }
public bool ThrowWhenEmptyLoginHint { get; }
}
10 changes: 7 additions & 3 deletions src/XboxAuthNet.Game/OAuth/InteractiveMicrosoftOAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ namespace XboxAuthNet.Game.OAuth;
public class InteractiveMicrosoftOAuth : MicrosoftOAuth
{
private readonly Action<CodeFlowBuilder> _codeFlowBuilder;
private readonly CodeFlowAuthorizationParameter _parameters;
private readonly CodeFlowAuthorizationParameter _codeFlowParameters;

public InteractiveMicrosoftOAuth(
MicrosoftOAuthParameters parameters,
Action<CodeFlowBuilder> codeFlowBuilder,
CodeFlowAuthorizationParameter codeFlowParameters)
: base(parameters) =>
(_codeFlowBuilder, _parameters) =
(_codeFlowBuilder, _codeFlowParameters) =
(codeFlowBuilder, codeFlowParameters);

protected override async ValueTask<MicrosoftOAuthResponse?> Authenticate(
Expand All @@ -27,7 +27,11 @@ public InteractiveMicrosoftOAuth(
_codeFlowBuilder.Invoke(builder);
var oauthHandler = builder.Build();

var loginHint = parameters.LoginHintSource.Get(context.SessionStorage);
if (string.IsNullOrEmpty(_codeFlowParameters.LoginHint))
_codeFlowParameters.LoginHint = loginHint;

context.Logger.LogInteractiveMicrosoftOAuth();
return await oauthHandler.AuthenticateInteractively(_parameters, context.CancellationToken);
return await oauthHandler.AuthenticateInteractively(_codeFlowParameters, context.CancellationToken);
}
}
51 changes: 40 additions & 11 deletions src/XboxAuthNet.Game/OAuth/MicrosoftOAuthBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,36 +27,65 @@ public ISessionSource<string> LoginHintSource
set => _loginHintSource = value;
}

private CodeFlowAuthorizationParameter createCodeFlowParameters()
{
var parameter = new CodeFlowParameterFactory().CreateAuthorizationParameter();
parameter.Prompt = MicrosoftOAuthPromptModes.SelectAccount;
return parameter;
}

private MicrosoftOAuthParameters createParameters() =>
new MicrosoftOAuthParameters(_clientInfo, SessionSource, LoginHintSource);

public ISessionValidator Validator() =>
new MicrosoftOAuthValidator(SessionSource);

public ISessionValidator LoginHintValidator(bool throwWhenInvalid = false) =>
new MicrosoftOAuthLoginHintValidator(throwWhenInvalid, LoginHintSource);

public IAuthenticator Silent() =>
new SilentMicrosoftOAuth(createParameters());

public IAuthenticator Interactive() =>
Interactive(builder => {}, createCodeFlowParameters());
Interactive(builder => {}, new CodeFlowAuthorizationParameter());

public IAuthenticator Interactive(CodeFlowAuthorizationParameter parameters) =>
Interactive(builder => {}, parameters);

public IAuthenticator Interactive(Action<CodeFlowBuilder> builderInvoker) =>
Interactive(builderInvoker, createCodeFlowParameters());
Interactive(builderInvoker, new CodeFlowAuthorizationParameter());

public IAuthenticator Interactive(
Action<CodeFlowBuilder> builderInvoker,
CodeFlowAuthorizationParameter parameters)
CodeFlowAuthorizationParameter parameters) =>
new InteractiveMicrosoftOAuth(createParameters(), builderInvoker, parameters);

public IAuthenticator InteractiveWithSingleAccount() =>
InteractiveWithSingleAccount(builderInvoker => { });

public IAuthenticator InteractiveWithSingleAccount(Action<CodeFlowBuilder> builderInvoker)
{
return new InteractiveMicrosoftOAuth(createParameters(), builderInvoker, parameters);
var authenticator = new FallbackAuthenticator();
authenticator.AddAuthenticatorWithoutValidator(
Interactive(
builderInvoker,
new CodeFlowAuthorizationParameter
{
Prompt = MicrosoftOAuthPromptModes.None
}));
authenticator.AddAuthenticatorWithoutValidator(
Interactive(
builderInvoker,
new CodeFlowAuthorizationParameter
{
Prompt = MicrosoftOAuthPromptModes.Login
}));
return authenticator;
}

public IAuthenticator CodeFlow() =>
CodeFlow(builderInvoker => { });

public IAuthenticator CodeFlow(Action<CodeFlowBuilder> builderInvoker)
{
var authenticator = new FallbackAuthenticator();
authenticator.AddAuthenticatorWithoutValidator(Silent());
authenticator.AddAuthenticator(LoginHintValidator(true), InteractiveWithSingleAccount(builderInvoker));
authenticator.AddAuthenticatorWithoutValidator(Interactive(builderInvoker));
return authenticator;
}

public IAuthenticator Signout()
Expand Down
26 changes: 26 additions & 0 deletions src/XboxAuthNet.Game/OAuth/MicrosoftOAuthLoginHintValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using XboxAuthNet.Game.Authenticators;
using XboxAuthNet.Game.SessionStorages;
using XboxAuthNet.OAuth;

namespace XboxAuthNet.Game.OAuth;

public class MicrosoftOAuthLoginHintValidator : ISessionValidator
{
private ISessionSource<string> _loginHintSource;
private readonly bool _throwWhenInvalid = false;

public MicrosoftOAuthLoginHintValidator(bool throwWhenInvalid, ISessionSource<string> loginHintSource)
{
_throwWhenInvalid = throwWhenInvalid;
_loginHintSource = loginHintSource;
}

public ValueTask<bool> Validate(AuthenticateContext context)
{
var loginHint = _loginHintSource.Get(context.SessionStorage);
var loginHintExists = !string.IsNullOrEmpty(loginHint);
if (_throwWhenInvalid && !loginHintExists)
throw new MicrosoftOAuthException("LoginHint was empty.", 0);
return new ValueTask<bool>(loginHintExists);
}
}
6 changes: 4 additions & 2 deletions src/XboxAuthNet.Game/OAuth/SilentMicrosoftOAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ public SilentMicrosoftOAuth(MicrosoftOAuthParameters parameters) : base(paramete
throw new MicrosoftOAuthException("Cached RefreshToken of the user was empty. Interactive microsoft authentication is required.", 0);

context.Logger.LogSilentMicrosoftOAuth();
var parameterFactory = new CodeFlowParameterFactory();
var apiClient = parameters.ClientInfo.CreateApiClientForOAuthCode(context.HttpClient);
return await apiClient.RefreshToken(
parameterFactory.CreateRefreshTokenParameter(session.RefreshToken),
new CodeFlowRefreshTokenParameter
{
RefreshToken = session.RefreshToken
},
context.CancellationToken);
}
}
2 changes: 1 addition & 1 deletion src/XboxAuthNet.Game/XboxAuthNet.Game.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.1" />
<PackageReference Include="XboxAuthNet" Version="3.0.1" />
<PackageReference Include="XboxAuthNet" Version="3.0.2" />
</ItemGroup>

<!-- private assets -->
Expand Down

0 comments on commit 3dcab10

Please sign in to comment.