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 23 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
136 changes: 136 additions & 0 deletions Samples/1a.Authentication/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System;
using System.Text.Json;
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
// Note that we are using RefreshToken for logging on here
steamUser.LogOn( new SteamUser.LogOnDetails
{
Username = pollResponse.AccountName,
AccessToken = pollResponse.RefreshToken,
} );

// This is not required, but it is possible to parse the JWT access token to see the scope and expiration date.
ParseJsonWebToken( pollResponse.AccessToken, nameof( pollResponse.AccessToken ) );
ParseJsonWebToken( pollResponse.RefreshToken, nameof( 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 );
}



// This is simply showing how to parse JWT, this is not required to login to Steam
void ParseJsonWebToken( string token, string name )
{
// You can use a JWT library to do the parsing for you
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to pull in System.IdentityModel.Tokens.Jwt instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it for a minute, and getting info out of it is more effort than I'm willing to spend.

var tokenComponents = token.Split( '.' );

// Fix up base64url to normal base64
var base64 = tokenComponents[ 1 ].Replace( '-', '+' ).Replace( '_', '/' );

if ( base64.Length % 4 != 0 )
{
base64 += new string( '=', 4 - base64.Length % 4 );
}

var payloadBytes = Convert.FromBase64String( base64 );

// Payload can be parsed as JSON, and then fields such expiration date, scope, etc can be accessed
var payload = JsonDocument.Parse( payloadBytes );

// For brevity we will simply output formatted json to console
var formatted = JsonSerializer.Serialize( payload, new JsonSerializerOptions
{
WriteIndented = true,
} );
Console.WriteLine( $"{name}: {formatted}" );
Console.WriteLine();
}
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
Loading