├── .gitattributes ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config └── steam-auth.php ├── phpunit.xml ├── src ├── SteamAuth.php ├── SteamAuthInterface.php ├── SteamInfo.php └── SteamServiceProvider.php └── tests ├── TestCase.php ├── Unit └── SteamInfoTest.php └── data └── 76561198061912622.json /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invisnik/laravel-steam-auth/94a0ef489932615612ddb745b3be3ff679afa209/.gitattributes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock 4 | composer.phar 5 | .phpunit.result.cache -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | risky: false 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.2' 5 | - '7.3' 6 | 7 | env: 8 | matrix: 9 | - COMPOSER_FLAGS="--prefer-lowest" 10 | - COMPOSER_FLAGS="" 11 | 12 | before_script: 13 | - travis_retry composer self-update 14 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nikita Brytkov 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 | # Steam authentication for Laravel 2 | [![Code Climate](https://codeclimate.com/github/invisnik/laravel-steam-auth/badges/gpa.svg)](https://codeclimate.com/github/invisnik/laravel-steam-auth) 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/invisnik/laravel-steam-auth.svg)](https://packagist.org/packages/invisnik/laravel-steam-auth) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/invisnik/laravel-steam-auth.svg)](https://packagist.org/packages/invisnik/laravel-steam-auth) 5 | [![License](https://img.shields.io/github/license/invisnik/laravel-steam-auth.svg)](https://packagist.org/packages/invisnik/laravel-steam-auth) 6 | 7 | This package is a Laravel 5 service provider which provides support for Steam OpenID and is very easy to integrate with any project that requires Steam authentication. 8 | 9 | ## Requirements 10 | * PHP 7.2+ 11 | * Laravel 5.8+ 12 | 13 | ## Installation 14 | #### Via Composer 15 | ```bash 16 | composer require invisnik/laravel-steam-auth 17 | ``` 18 | 19 | #### Steam API Key 20 | 21 | Add your Steam API key to your `.env` file. You can get your API key [here](http://steamcommunity.com/dev/apikey). 22 | 23 | ``` 24 | STEAM_API_KEY=SomeKindOfAPIKey 25 | ``` 26 | 27 | #### Config Files 28 | 29 | Publish the config file. 30 | 31 | ``` 32 | php artisan vendor:publish --provider="Invisnik\LaravelSteamAuth\SteamServiceProvider" 33 | ``` 34 | ## Usage example 35 | In `config/steam-auth.php`: 36 | ```php 37 | return [ 38 | 39 | /* 40 | * Redirect URL after login 41 | */ 42 | 'redirect_url' => '/auth/steam/handle', 43 | /* 44 | * Realm override. Bypass domain ban by Valve. 45 | * Use alternative domain with redirection to main for authentication (banned by valve). 46 | */ 47 | // 'realm' => 'redirected.com', 48 | /* 49 | * API Key (set in .env file) [http://steamcommunity.com/dev/apikey] 50 | */ 51 | 'api_key' => env('STEAM_API_KEY', ''), 52 | /* 53 | * Is using https? 54 | */ 55 | 'https' => false, 56 | ]; 57 | 58 | ``` 59 | In `routes/web.php`: 60 | ```php 61 | Route::get('auth/steam', 'AuthController@redirectToSteam')->name('auth.steam'); 62 | Route::get('auth/steam/handle', 'AuthController@handle')->name('auth.steam.handle'); 63 | ``` 64 | **Note:** if you want to keep using Laravel's default logout route, add the following as well: 65 | ```php 66 | Route::post('logout', 'Auth\LoginController@logout')->name('logout'); 67 | ``` 68 | In `AuthController`: 69 | ```php 70 | namespace App\Http\Controllers; 71 | 72 | use Invisnik\LaravelSteamAuth\SteamAuth; 73 | use App\User; 74 | use Auth; 75 | 76 | class AuthController extends Controller 77 | { 78 | /** 79 | * The SteamAuth instance. 80 | * 81 | * @var SteamAuth 82 | */ 83 | protected $steam; 84 | 85 | /** 86 | * The redirect URL. 87 | * 88 | * @var string 89 | */ 90 | protected $redirectURL = '/'; 91 | 92 | /** 93 | * AuthController constructor. 94 | * 95 | * @param SteamAuth $steam 96 | */ 97 | public function __construct(SteamAuth $steam) 98 | { 99 | $this->steam = $steam; 100 | } 101 | 102 | /** 103 | * Redirect the user to the authentication page 104 | * 105 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 106 | */ 107 | public function redirectToSteam() 108 | { 109 | return $this->steam->redirect(); 110 | } 111 | 112 | /** 113 | * Get user info and log in 114 | * 115 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 116 | */ 117 | public function handle() 118 | { 119 | if ($this->steam->validate()) { 120 | $info = $this->steam->getUserInfo(); 121 | 122 | if (!is_null($info)) { 123 | $user = $this->findOrNewUser($info); 124 | 125 | Auth::login($user, true); 126 | 127 | return redirect($this->redirectURL); // redirect to site 128 | } 129 | } 130 | return $this->redirectToSteam(); 131 | } 132 | 133 | /** 134 | * Getting user by info or created if not exists 135 | * 136 | * @param $info 137 | * @return User 138 | */ 139 | protected function findOrNewUser($info) 140 | { 141 | $user = User::where('steamid', $info->steamID64)->first(); 142 | 143 | if (!is_null($user)) { 144 | return $user; 145 | } 146 | 147 | return User::create([ 148 | 'username' => $info->personaname, 149 | 'avatar' => $info->avatarfull, 150 | 'steamid' => $info->steamID64 151 | ]); 152 | } 153 | } 154 | 155 | ``` 156 | 157 | Should you wish to use a login redirection URL that is differant from the one you specified in the config 158 | 159 | ```php 160 | // Inside your controller login method 161 | $this->steam->setRedirectUrl(route('login.route')); 162 | 163 | ... 164 | 165 | return $this->steam->redirect(); 166 | ``` 167 | 168 | If you need another steamID you can use another package to convert the given steamID64 to another type like [xPaw/SteamID](https://github.com/xPaw/SteamID.php). 169 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "invisnik/laravel-steam-auth", 3 | "description": "Laravel Steam Auth", 4 | "keywords": ["steam", "laravel", "auth"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Nikita Brytkov", 9 | "email": "invis.nik@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.2|^8.0", 14 | "guzzlehttp/guzzle": "^6.3|^7.0.1", 15 | "illuminate/http": "^5.8|^6.0|^7.0|^8.0|^9.0", 16 | "illuminate/routing": "^5.8|^6.0|^7.0|^8.0|^9.0", 17 | "illuminate/support": "^5.8|^6.0|^7.0|^8.0|^9.0" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^7.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Invisnik\\LaravelSteamAuth\\": "src" 25 | } 26 | }, 27 | "autoload-dev": { 28 | "psr-4": { 29 | "Invisnik\\LaravelSteamAuth\\Tests\\": "tests" 30 | } 31 | }, 32 | "extra": { 33 | "laravel": { 34 | "providers": [ 35 | "Invisnik\\LaravelSteamAuth\\SteamServiceProvider" 36 | ] 37 | } 38 | }, 39 | "scripts": { 40 | "test": "vendor/bin/phpunit" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/steam-auth.php: -------------------------------------------------------------------------------- 1 | '/', 9 | 10 | /* 11 | * Realm override. Bypass domain ban by Valve. 12 | * Use alternative domain with redirection to main for authentication (banned by valve). 13 | */ 14 | //'realm' => 'redirected.com', 15 | 16 | /* 17 | * API Key (set in .env file) [http://steamcommunity.com/dev/apikey] 18 | */ 19 | 'api_key' => env('STEAM_API_KEY', ''), 20 | 21 | /* 22 | * Is using https ? 23 | */ 24 | 'https' => false, 25 | ]; 26 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | ./tests/Feature 22 | 23 | 24 | ./tests/Unit 25 | 26 | 27 | 28 | 29 | src/ 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/SteamAuth.php: -------------------------------------------------------------------------------- 1 | request = $request; 84 | 85 | $redirect_url = Config::get('steam-auth.redirect_url'); 86 | $this->authUrl = $this->buildUrl( 87 | url($redirect_url, [], Config::get('steam-auth.https')) 88 | ); 89 | 90 | $this->guzzleClient = new GuzzleClient(); 91 | } 92 | 93 | /** 94 | * Validates if the request object has required stream attributes. 95 | * 96 | * @return bool 97 | */ 98 | private function requestIsValid(): bool 99 | { 100 | return $this->request->has(self::OPENID_ASSOC_HANDLE) 101 | && $this->request->has(self::OPENID_SIGNED) 102 | && $this->request->has(self::OPENID_SIG); 103 | } 104 | 105 | /** 106 | * Checks the steam login. 107 | * 108 | * @return bool 109 | * @throws GuzzleException 110 | */ 111 | public function validate(): bool 112 | { 113 | if (! $this->requestIsValid()) { 114 | return false; 115 | } 116 | 117 | $requestOptions = $this->getDefaultRequestOptions(); 118 | $customOptions = $this->getCustomRequestOptions(); 119 | 120 | if (! empty($customOptions) && is_array($customOptions)) { 121 | $requestOptions = array_merge($requestOptions, $customOptions); 122 | } 123 | 124 | $response = $this->guzzleClient->request('POST', self::OPENID_URL, $requestOptions); 125 | 126 | $results = $this->parseResults($response->getBody()->getContents()); 127 | 128 | $this->parseSteamID(); 129 | $this->parseInfo(); 130 | 131 | return $results->is_valid == 'true'; 132 | } 133 | 134 | /** 135 | * Get param list for openId validation. 136 | * 137 | * @return array 138 | */ 139 | public function getParams(): array 140 | { 141 | $params = [ 142 | 'openid.assoc_handle' => $this->request->get(self::OPENID_ASSOC_HANDLE), 143 | 'openid.signed' => $this->request->get(self::OPENID_SIGNED), 144 | 'openid.sig' => $this->request->get(self::OPENID_SIG), 145 | 'openid.ns' => self::OPENID_NS, 146 | 'openid.mode' => 'check_authentication', 147 | ]; 148 | 149 | $signedParams = explode(',', $this->request->get(self::OPENID_SIGNED)); 150 | 151 | foreach ($signedParams as $item) { 152 | $value = $this->request->get('openid_'.str_replace('.', '_', $item)); 153 | $params['openid.'.$item] = $value; 154 | } 155 | 156 | return $params; 157 | } 158 | 159 | /** 160 | * Parse openID response to fluent object. 161 | * 162 | * @param string $results openid response body 163 | * 164 | * @return Fluent 165 | */ 166 | public function parseResults($results): Fluent 167 | { 168 | $parsed = []; 169 | $lines = explode("\n", $results); 170 | 171 | foreach ($lines as $line) { 172 | if (empty($line)) { 173 | continue; 174 | } 175 | 176 | $line = explode(':', $line, 2); 177 | $parsed[$line[0]] = $line[1]; 178 | } 179 | 180 | return new Fluent($parsed); 181 | } 182 | 183 | /** 184 | * Validates a given URL, ensuring it contains the http or https URI Scheme. 185 | * 186 | * @param string $url 187 | * 188 | * @return bool 189 | */ 190 | private function validateUrl($url): bool 191 | { 192 | if (! filter_var($url, FILTER_VALIDATE_URL)) { 193 | return false; 194 | } 195 | 196 | return true; 197 | } 198 | 199 | /** 200 | * Build the Steam login URL. 201 | * 202 | * @param string|null $return A custom return to URL 203 | * 204 | * @return string 205 | */ 206 | private function buildUrl($return = null): string 207 | { 208 | if (is_null($return)) { 209 | $return = url('/', [], Config::get('steam-auth.https')); 210 | } 211 | if (! is_null($return) && ! $this->validateUrl($return)) { 212 | throw new RuntimeException('The return URL must be a valid URL with a URI scheme or http or https.'); 213 | } 214 | 215 | $realm = Config::get('steam-auth.realm', $this->request->server('HTTP_HOST')); 216 | $params = [ 217 | 'openid.ns' => self::OPENID_NS, 218 | 'openid.mode' => 'checkid_setup', 219 | 'openid.return_to' => $return, 220 | 'openid.realm' => (Config::get('steam-auth.https') ? 'https' : 'http').'://'.$realm, 221 | 'openid.identity' => 'http://specs.openid.net/auth/2.0/identifier_select', 222 | 'openid.claimed_id' => 'http://specs.openid.net/auth/2.0/identifier_select', 223 | ]; 224 | 225 | return self::OPENID_URL.'?'.http_build_query($params, '', '&'); 226 | } 227 | 228 | /** 229 | * Set the url to return to. 230 | * 231 | * @param string $url Full URL to redirect to on Steam login 232 | * 233 | * @return void 234 | */ 235 | public function setRedirectUrl($url): void 236 | { 237 | $this->authUrl = $this->buildUrl($url); 238 | } 239 | 240 | /** 241 | * Returns the redirect response to login. 242 | * 243 | * @return RedirectResponse 244 | */ 245 | public function redirect(): RedirectResponse 246 | { 247 | return redirect($this->getAuthUrl()); 248 | } 249 | 250 | /** 251 | * Parse the steamID from the OpenID response. 252 | * 253 | * @return void 254 | */ 255 | public function parseSteamID(): void 256 | { 257 | preg_match( 258 | '#^https?://steamcommunity.com/openid/id/([0-9]{17,25})#', 259 | $this->request->get('openid_claimed_id'), 260 | $matches 261 | ); 262 | $this->steamId = is_numeric($matches[1]) ? $matches[1] : 0; 263 | } 264 | 265 | /** 266 | * Get user data from steam api. 267 | * 268 | * @return void 269 | * @throws GuzzleException 270 | */ 271 | public function parseInfo(): void 272 | { 273 | if (is_null($this->steamId)) { 274 | return; 275 | } 276 | 277 | if (empty(Config::get('steam-auth.api_key'))) { 278 | throw new RuntimeException('The Steam API key has not been specified.'); 279 | } 280 | 281 | $response = $this->guzzleClient->request( 282 | 'GET', 283 | sprintf(self::STEAM_INFO_URL, Config::get('steam-auth.api_key'), $this->steamId) 284 | ); 285 | $json = json_decode($response->getBody(), true); 286 | 287 | $this->steamInfo = new SteamInfo($json['response']['players'][0]); 288 | } 289 | 290 | /** 291 | * Returns the login url. 292 | * 293 | * @return string|null 294 | */ 295 | public function getAuthUrl(): ?string 296 | { 297 | return $this->authUrl; 298 | } 299 | 300 | /** 301 | * Returns the SteamUser info. 302 | * 303 | * @return SteamInfo 304 | */ 305 | public function getUserInfo(): SteamInfo 306 | { 307 | return $this->steamInfo; 308 | } 309 | 310 | /** 311 | * Returns the steam id. 312 | * 313 | * @return string|null 314 | */ 315 | public function getSteamId(): ?string 316 | { 317 | return $this->steamId; 318 | } 319 | 320 | /** 321 | * @return array 322 | */ 323 | public function getDefaultRequestOptions(): array 324 | { 325 | return [ 326 | RequestOptions::FORM_PARAMS => $this->getParams(), 327 | ]; 328 | } 329 | 330 | /** 331 | * If you need to set additional guzzle options on request, 332 | * set them via this method. 333 | * 334 | * @param $options 335 | * 336 | * @return void 337 | */ 338 | public function setCustomRequestOptions($options): void 339 | { 340 | $this->customRequestOptions = $options; 341 | } 342 | 343 | /** 344 | * @return array 345 | */ 346 | public function getCustomRequestOptions(): array 347 | { 348 | return $this->customRequestOptions; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/SteamAuthInterface.php: -------------------------------------------------------------------------------- 1 | attributes['steamID64'] = $steamID; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/SteamServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPublishableResources(); 17 | } 18 | 19 | /** 20 | * Register the service provider. 21 | * 22 | * @return void 23 | */ 24 | public function register(): void 25 | { 26 | $this->app->singleton('steamauth', function () { 27 | return new SteamAuth($this->app->get('request')); 28 | }); 29 | } 30 | 31 | /** 32 | * @return void 33 | */ 34 | private function registerPublishableResources(): void 35 | { 36 | $this->publishes([ 37 | $this->getConfigurationPath() => config_path($this->getShortPackageName().'.php'), 38 | ], $this->getPackageName().' (configuration)'); 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | private function getConfigurationPath(): string 45 | { 46 | return __DIR__.'/../config/'.$this->getShortPackageName().'.php'; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | private function getShortPackageName(): string 53 | { 54 | return 'steam-auth'; 55 | } 56 | 57 | /** 58 | * @return string 59 | */ 60 | private function getPackageName(): string 61 | { 62 | return 'laravel-steam-auth'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | getSteamInfoData(); 13 | $steamInfo = new SteamInfo($data); 14 | 15 | $this->assertInstanceOf(SteamInfo::class, $steamInfo); 16 | } 17 | 18 | public function testSteamid() 19 | { 20 | $data = $this->getSteamInfoData(); 21 | $steamInfo = new SteamInfo($data); 22 | 23 | $this->assertArrayNotHasKey('steamid', $steamInfo); 24 | $this->assertArrayHasKey('steamID64', $steamInfo->toArray()); 25 | $this->assertEquals($data['steamid'], $steamInfo['steamID64']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/data/76561198061912622.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "players": [ 4 | { 5 | "steamid": "76561198061912622", 6 | "communityvisibilitystate": 3, 7 | "profilestate": 1, 8 | "personaname": "Gummibeer", 9 | "lastlogoff": 1523196527, 10 | "profileurl": "https://steamcommunity.com/id/gummibeer/", 11 | "avatar": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/1f/1fde1e42ba3b684f6ff6e80bcc3fc947cd9a3d48.jpg", 12 | "avatarmedium": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/1f/1fde1e42ba3b684f6ff6e80bcc3fc947cd9a3d48_medium.jpg", 13 | "avatarfull": "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/1f/1fde1e42ba3b684f6ff6e80bcc3fc947cd9a3d48_full.jpg", 14 | "personastate": 1, 15 | "realname": "Tom", 16 | "primaryclanid": "103582791429521408", 17 | "timecreated": 1334330552, 18 | "personastateflags": 512, 19 | "loccountrycode": "DE", 20 | "locstatecode": "04", 21 | "loccityid": 13004 22 | } 23 | ] 24 | } 25 | } --------------------------------------------------------------------------------