Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SteamAuthentication to handle new Steam login flow sessions #1129

Merged
merged 29 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0084f8e
Create SteamAuthentication
xPaw Aug 29, 2022
3c8472d
Add some polling support
xPaw Aug 29, 2022
cf5635a
Add AccessToken to LogOnDetails
xPaw Aug 29, 2022
a127e68
Return tokens when poll returns data
xPaw Aug 29, 2022
b617056
Put send/poll methods on the session object
xPaw Aug 30, 2022
d373630
Add authenticator object and implement 2FA auth
xPaw Aug 30, 2022
a8add89
Sort by preferred auth confirmation
xPaw Aug 30, 2022
7603aa8
Add AuthenticationException
xPaw Aug 31, 2022
6322eb3
Add guard_data; add some comments
xPaw Mar 3, 2023
8c7b10f
Add authentication sample
xPaw Mar 11, 2023
f92bac3
Change enum to IsPersistentSession bool
xPaw Mar 12, 2023
b7d393f
Add cancellation token to polling
xPaw Mar 13, 2023
ec96a73
Make `AuthenticationException` public
JustArchi Mar 15, 2023
3687218
Allow consumers to decide whether they want to poll for device confir…
xPaw Mar 15, 2023
5767a15
Add a sample for authenticating using qr codes
xPaw Mar 15, 2023
1c2cc7a
Add more comments
xPaw Mar 15, 2023
8d10d5a
Cleanup, add qr challenge url change callback, add os type
xPaw Mar 17, 2023
43b634a
Use default WebsiteID in sample
xPaw Mar 17, 2023
f8ec8a9
Add a sample parsing jwt payload
xPaw Mar 17, 2023
44cf6b4
Deprecate login keys
xPaw Mar 17, 2023
b006cd2
Document more website ids
xPaw Mar 17, 2023
2fc013e
Handle incorrect 2FA codes and call the authenticator again
xPaw Mar 17, 2023
20c0b84
Update guard data comment
xPaw Mar 22, 2023
7b266f3
Review
xPaw Mar 22, 2023
2cb4a75
Move authentication stuff to its own namespace
xPaw Mar 22, 2023
2496b06
Suppress LoginKey obsolete warnings in SK codebase
xPaw Mar 22, 2023
9aa61d0
Review
xPaw Mar 23, 2023
79931d7
Change visibility
xPaw Mar 23, 2023
1d41f5f
Added `SteamClient.Authentication`
xPaw Mar 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions Samples/1a.Authentication/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;

using SteamKit2;

if ( args.Length < 2 )
{
Console.WriteLine( "Sample1: No username and password specified!" );
xPaw marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// save our logon details
var user = args[ 0 ];
var pass = args[ 1 ];

// create our steamclient instance
var steamClient = new SteamClient();
// create the callback manager which will route callbacks to function calls
var manager = new CallbackManager( steamClient );

// get the authentication handler, which used for authenticating with Steam
var auth = steamClient.GetHandler<SteamAuthentication>();

// get the steamuser handler, which is used for logging on after successfully connecting
var steamUser = steamClient.GetHandler<SteamUser>();

// register a few callbacks we're interested in
// these are registered upon creation to a callback manager, which will then route the callbacks
// to the functions specified
manager.Subscribe<SteamClient.ConnectedCallback>( OnConnected );
manager.Subscribe<SteamClient.DisconnectedCallback>( OnDisconnected );

manager.Subscribe<SteamUser.LoggedOnCallback>( OnLoggedOn );
manager.Subscribe<SteamUser.LoggedOffCallback>( OnLoggedOff );

var isRunning = true;

Console.WriteLine( "Connecting to Steam..." );

// initiate the connection
steamClient.Connect();

// create our callback handling loop
while ( isRunning )
{
// in order for the callbacks to get routed, they need to be handled by the manager
manager.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) );
}

async void OnConnected( SteamClient.ConnectedCallback callback )
{
Console.WriteLine( "Connected to Steam! Logging in '{0}'...", user );

// Begin authenticating via credentials
var authSession = await auth.BeginAuthSessionViaCredentials( new SteamAuthentication.AuthSessionDetails
{
Username = user,
Password = pass,
IsPersistentSession = false,
Authenticator = new UserConsoleAuthenticator(),
} );

// Starting polling Steam for authentication response
var pollResponse = await authSession.StartPolling();
xPaw marked this conversation as resolved.
Show resolved Hide resolved

// Logon to Steam with the access token we have received
steamUser.LogOn( new SteamUser.LogOnDetails
{
Username = pollResponse.AccountName,
AccessToken = pollResponse.RefreshToken,
} );
}

void OnDisconnected( SteamClient.DisconnectedCallback callback )
{
Console.WriteLine( "Disconnected from Steam" );

isRunning = false;
}

void OnLoggedOn( SteamUser.LoggedOnCallback callback )
{
if ( callback.Result != EResult.OK )
{
Console.WriteLine( "Unable to logon to Steam: {0} / {1}", callback.Result, callback.ExtendedResult );

isRunning = false;
return;
}

Console.WriteLine( "Successfully logged on!" );

// at this point, we'd be able to perform actions on Steam

// for this sample we'll just log off
steamUser.LogOff();
}

void OnLoggedOff( SteamUser.LoggedOffCallback callback )
{
Console.WriteLine( "Logged off of Steam: {0}", callback.Result );
}
18 changes: 18 additions & 0 deletions Samples/1a.Authentication/Sample1a_Authentication.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Company>SteamRE</Company>
<Product>Sample1a_Authentication</Product>
<Authors />
<Description>SteamKit Sample 1a: Authentication</Description>
<Copyright>Copyright © Pavel Djundik 2023</Copyright>
<PackageId>Sample1a_Authentication</PackageId>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\SteamKit2\SteamKit2\SteamKit2.csproj" />
</ItemGroup>

</Project>
113 changes: 113 additions & 0 deletions Samples/1b.QrCodeAuthentication/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using QRCoder;
using SteamKit2;

// create our steamclient instance
var steamClient = new SteamClient();
// create the callback manager which will route callbacks to function calls
var manager = new CallbackManager( steamClient );

// get the authentication handler, which used for authenticating with Steam
var auth = steamClient.GetHandler<SteamAuthentication>();

// get the steamuser handler, which is used for logging on after successfully connecting
var steamUser = steamClient.GetHandler<SteamUser>();

// register a few callbacks we're interested in
// these are registered upon creation to a callback manager, which will then route the callbacks
// to the functions specified
manager.Subscribe<SteamClient.ConnectedCallback>( OnConnected );
manager.Subscribe<SteamClient.DisconnectedCallback>( OnDisconnected );

manager.Subscribe<SteamUser.LoggedOnCallback>( OnLoggedOn );
manager.Subscribe<SteamUser.LoggedOffCallback>( OnLoggedOff );

var isRunning = true;

Console.WriteLine( "Connecting to Steam..." );

// initiate the connection
steamClient.Connect();

// create our callback handling loop
while ( isRunning )
{
// in order for the callbacks to get routed, they need to be handled by the manager
manager.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) );
}

async void OnConnected( SteamClient.ConnectedCallback callback )
{
// Start an authentication session by requesting a link
var authSession = await auth.BeginAuthSessionViaQR( new SteamAuthentication.AuthSessionDetails() );

// Steam will periodically refresh the challenge url, this callback allows you to draw a new qr code
authSession.ChallengeURLChanged = () =>
{
Console.WriteLine();
Console.WriteLine( "Steam has refreshed the challenge url" );

DrawQRCode( authSession );
};

// Draw current qr right away
DrawQRCode( authSession );

// Starting polling Steam for authentication response
// This response is later used to logon to Steam after connecting
var pollResponse = await authSession.StartPolling();

Console.WriteLine( $"Logging in as '{pollResponse.AccountName}'..." );

// Logon to Steam with the access token we have received
steamUser.LogOn( new SteamUser.LogOnDetails
{
Username = pollResponse.AccountName,
AccessToken = pollResponse.RefreshToken,
} );
}

void OnDisconnected( SteamClient.DisconnectedCallback callback )
{
Console.WriteLine( "Disconnected from Steam" );

isRunning = false;
}

void OnLoggedOn( SteamUser.LoggedOnCallback callback )
{
if ( callback.Result != EResult.OK )
{
Console.WriteLine( "Unable to logon to Steam: {0} / {1}", callback.Result, callback.ExtendedResult );

isRunning = false;
return;
}

Console.WriteLine( "Successfully logged on!" );

// at this point, we'd be able to perform actions on Steam

// for this sample we'll just log off
steamUser.LogOff();
}

void OnLoggedOff( SteamUser.LoggedOffCallback callback )
{
Console.WriteLine( "Logged off of Steam: {0}", callback.Result );
}

void DrawQRCode( SteamAuthentication.QrAuthSession authSession )
{
Console.WriteLine( $"Challenge URL: {authSession.ChallengeURL}" );
Console.WriteLine();

// Encode the link as a QR code
var qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode( authSession.ChallengeURL, QRCodeGenerator.ECCLevel.L );
var qrCode = new AsciiQRCode( qrCodeData );
var qrCodeAsAsciiArt = qrCode.GetGraphic( 1, drawQuietZones: false );

Console.WriteLine( "Use the Steam Mobile App to sign in via QR code:" );
Console.WriteLine( qrCodeAsAsciiArt );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Company>SteamRE</Company>
<Product>Sample1b_QrCodeAuthentication</Product>
<Authors />
<Description>SteamKit Sample 1b: Authentication using QR codes</Description>
<Copyright>Copyright © Pavel Djundik 2023</Copyright>
<PackageId>Sample1b_QrCodeAuthentication</PackageId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="QRCoder" Version="1.4.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\SteamKit2\SteamKit2\SteamKit2.csproj" />
</ItemGroup>

</Project>
58 changes: 43 additions & 15 deletions Samples/Samples.sln
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2003
# Visual Studio Version 17
VisualStudioVersion = 17.5.33424.131
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample1_Logon", "1.Logon\Sample1_Logon.csproj", "{CEF39496-576D-4A70-9A06-16112B84B79F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample1a_Authentication", "1a.Authentication\Sample1a_Authentication.csproj", "{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample1b_QrCodeAuthentication", "1b.QrCodeAuthentication\Sample1b_QrCodeAuthentication.csproj", "{EFC8F224-9441-48D0-8FEE-2FC9F948837C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample2_Extending", "2.Extending\Sample2_Extending.csproj", "{B8D7F87B-DBAA-4FBE-8254-E1FE07D6C7DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample3_DebugLog", "3.DebugLog\Sample3_DebugLog.csproj", "{808EAE9B-B9F6-4692-8F5A-9E2A703BF8CE}"
Expand All @@ -23,7 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample9_AsyncJobs", "9.Asyn
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample10_DotaMatchRequest", "10.DotaMatchRequest\Sample10_DotaMatchRequest.csproj", "{734863D3-4EED-4758-B1E9-7B324C2D8D72}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamKit2", "..\SteamKit2\SteamKit2\SteamKit2.csproj", "{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamKit2", "..\SteamKit2\SteamKit2\SteamKit2.csproj", "{4B2B0365-DE37-4B65-B614-3E4E7C05147D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -155,18 +159,42 @@ Global
{734863D3-4EED-4758-B1E9-7B324C2D8D72}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{734863D3-4EED-4758-B1E9-7B324C2D8D72}.Release|x86.ActiveCfg = Release|Any CPU
{734863D3-4EED-4758-B1E9-7B324C2D8D72}.Release|x86.Build.0 = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|x86.ActiveCfg = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Debug|x86.Build.0 = Debug|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|Any CPU.Build.0 = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|x86.ActiveCfg = Release|Any CPU
{D3E2F2D3-A663-4232-B9C5-A4F81DB665A2}.Release|x86.Build.0 = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Debug|x86.Build.0 = Debug|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|Any CPU.Build.0 = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|x86.ActiveCfg = Release|Any CPU
{4B2B0365-DE37-4B65-B614-3E4E7C05147D}.Release|x86.Build.0 = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|x86.ActiveCfg = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Debug|x86.Build.0 = Debug|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|Any CPU.Build.0 = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|x86.ActiveCfg = Release|Any CPU
{C6FC3BF7-BF99-46FE-98F3-56C7C123BDC5}.Release|x86.Build.0 = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|x86.ActiveCfg = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Debug|x86.Build.0 = Debug|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|Any CPU.Build.0 = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|x86.ActiveCfg = Release|Any CPU
{EFC8F224-9441-48D0-8FEE-2FC9F948837C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace SteamKit2
{
/// <summary>
/// Thrown when <see cref="SteamAuthentication"/> fails to authenticate.
/// </summary>
public class AuthenticationException : Exception
{
xPaw marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// Gets the result of the authentication request.
/// </summary>
public EResult Result { get; private set; }
xPaw marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="result">The result code that describes the error.</param>
public AuthenticationException( string message, EResult result )
: base( $"{message} with result {result}." )
{
Result = result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Threading.Tasks;

namespace SteamKit2
{
/// <summary>
///
xPaw marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public interface IAuthenticator
{
/// <summary>
/// This method is called when the account being logged into requires 2-factor authentication using the authenticator app.
/// </summary>
/// <returns>The 2-factor auth code used to login. This is the code that can be received from the authenticator app.</returns>
public Task<string> ProvideDeviceCode();

/// <summary>
/// This method is called when the account being logged into uses Steam Guard email authentication. This code is sent to the user's email.
/// </summary>
/// <param name="email">The email address that the Steam Guard email was sent to.</param>
/// <returns>The Steam Guard auth code used to login.</returns>
public Task<string> ProvideEmailCode(string email);

/// <summary>
/// This method is called when the account being logged has the Steam Mobile App and accepts authentication notification prompts.
///
/// Return false if you want to fallback to entering a code instead.
/// </summary>
/// <returns>Return true to poll until the authentication is accepted, return false to fallback to entering a code.</returns>
public Task<bool> AcceptDeviceConfirmation();
}
}
Loading