Integrating OAuth and Social Login Into Wordpress

Post on 22-Mar-2017

97 views 1 download

transcript

INTEGRATING OAUTH AND

SOCIAL LOGIN INTO WORDPRESS

William Tam, WNET/Thirteen

Who am I?• Built my first web page in 1993, and have been doing so ever

since, mostly for non-profit orgs. I work for WNET/Thirteen now and freelance a little

• First touched WordPress in 2012 while freelancing• Playing with OAuth since 2013 when I built a YouTube upload/edit

system for the PBS NewsHour website.

What is ‘Social Login’?• Social login is NOT social sharing. If you just need social sharing,

AddThis is excellent!• If you’re just looking for a good plugin and want to skip the details,

the ‘WordPress Social Login’ plugin seems to be a good choice.• If you want to understand how it all works, stick around to hear

about OAuth!

What do we mean by OAuth? • OAuth is a way for users to approve an app or website to have access to

a resource without sharing their passwords with the app/website• OAuth (1) (originally 'Twitter OAuth‘) Uses digital signatures which can be

hard to implement. Few providers other than Twitter still suppport it• OAuth2 uses SSL instead of signatures. Flexible (perhaps too flexible) to

implement, easy to implement insecurely! Very little standardization• OpenID Connect (OIDC) is a specification that uses OAuth2 with

standardized secure implementation details, particularly the ‘id_token’, that make it much harder to mess up. Google, Yahoo, Microsoft, Amazon, and some others support it, but Facebook doesn’t support OIDC (yet)

HOW DOES OAUTH WORK?

OAuth in a nutshell• User visits your website, clicks on a link that sends them to a

provider like Google or Facebook where you've registered some configuration details in advance.

• If the details you registered match the arguments in the query string of that link, AND the user logs in to that provider, the user is redirected back to a special URL on your website

• Your website uses data it reads from that special URL to get an access token

• Your website can then use that token to get access to stuff on the provider

Step 1: Register an ‘app’ to store config• Developer or site admin registers an 'app' with an identity provider

such as Google, Facebook, Yahoo, or the like • Google Developer Console: https://console.developers.google.com/• Facebook for Developers: https://developers.facebook.com• All of the ID providers have some sort of console

Configuring the ‘app’• Allowed Origin Domain(s)• Allowed Redirect URI(s)• Scope(s)

Configuring the ‘app’• Consent screen• Identity provider assigns and returns a client id and secret for that

specific ‘app’.

Step 2: Get your token(s)There are two “flows” for OAuth to get an access token. • Implicit Flow:

• Client-only, can be done with only HTML and JS, easy to code• Not very secure • Authorization expires when the access token expires – typically 1 hour

• Code Exchange Flow:• Requires server coding, much more complex• More secure, uses server-to-server communication• Enables long-term/offline authorization using refresh tokens

Tokens are just stringsImplicit flow puts it as a ‘fragment’ appended to the Redirect URI:https://somesite.com/oauthcallback#access_token=1/fFBGRNJru1FQd44AzqT3Zg&token_type=Bearer&expires_in=3600&nonce=DgkRrHXmyu3KLd0KDdfq

Authorization code flow returns JSON (usually):{ "access_token":"1/fFAGRNJru1FTz70BzhT3Zg", "expires_in":3920, "token_type":"Bearer", "refresh_token":"1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"}

Implicit flow

Example: Get Google OAuth Token function requestGoogleOAuthToken() { var popupurl = 'https://accounts.google.com/o/oauth2/auth?scope=profile email openid&client_id=' + CLIENT_ID + '&redirect_uri=' + REDIRECT_URI + '&response_type=token'; var win = window.open(popupurl, "googleauthwindow", 'width=800, height=600'); var pollTimer = window.setInterval(function() { try { if (win.document.URL.indexOf(REDIRECT_URI) != -1) { window.clearInterval(pollTimer); google_access_token = win.document.URL.match( /access_token=(.*)\&/); win.close(); getGoogleUserInfo(google_access_token[1]); } } catch(e) {} }, 500); }

Example: Get Google OAuth Token function requestGoogleOAuthToken() { var popupurl = 'https://accounts.google.com/o/oauth2/auth?scope=profile email openid&client_id=' + CLIENT_ID + '&redirect_uri=' + REDIRECT_URI + '&response_type=token'; var win = window.open(popupurl, "googleauthwindow", 'width=800, height=600'); var pollTimer = window.setInterval(function() { try { if (win.document.URL.indexOf(REDIRECT_URI) != -1) { window.clearInterval(pollTimer); google_access_token = win.document.URL.match( /access_token=(.*)\&/); win.close(); validateGoogleAccessToken(google_access_token[1]); } } catch(e) {} }, 500); }

Example: Validate Access Tokenfunction validateGoogleAccessToken(GoogleAccessToken) { $.ajax({ url: 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + GoogleAccessToken, data: null, dataType: "jsonp", success: function(responseText){ if (responseText.error || responseText.audience !== CLIENT_ID) { console.log(responseText); } else { getGoogleUserInfo(GoogleAccessToken); } } }); }

Example: Get Google UserInfo function getGoogleUserInfo(google_access_token) { $.ajax({ url: 'https://www.googleapis.com/plus/v1/people/me/openIdConnect', data: { access_token: google_access_token }, success: function(user) { $('#googleUserName').text('You are logged in as ' + user.name); }, dataType: "jsonp" }); }

Security checklist• Validate the access token against the tokeninfo endpoint• The state parameter – whatever you put into it you should get

back unchanged. This can be a nonce, or encrypted hash, which leads us to…

• JSON Web Tokens – the standard for representing ‘claims’. https://jwt.io/ for more info

• Limit the scopes – least privilege principle

Authorization code flow

The Refresh Token is MAGIC!• Refresh token doesn’t give access to resources, just new access tokens• Refresh token is used to get (a) new access token. It requires the client id and

client secret to retrieve• The user does not need to be present• The refresh token never (or rarely) expires on its own – some expire after 2

weeks, or a year. Google’s refresh tokens never expire• Some OAuth implementations allow multiple use refresh tokens, others expire

them immediately. Others rate-limit the number of access tokens provided• Changing account passwords do not invalidate a refresh token• Revoking refresh tokens is different for different identity providers. Provide

means to do so!

Example: Get/Update OAuth Tokenfunction set_google_oauth2_token($grantCode, $grantType) { $settings = get_option('foo_oauth_demo_settings', true); $clienttoken_post = array( "client_id" => $settings['google_app_client_id'],"client_secret" => $settings['google_app_client_secret'] ); if ($grantType === "auth_code"){ $clienttoken_post["code"] = $grantCode; $clienttoken_post["redirect_uri"] = $settings['google_app_redirect_uri']; $clienttoken_post["grant_type"] = "authorization_code"; } if ($grantType === "refresh_token"){ $clienttoken_post["refresh_token"] = get_option('foo_google_refresh_token', true); $clienttoken_post["grant_type"] = "refresh_token"; } $postargs = array( 'body' => $clienttoken_post ); $response = wp_remote_post(‘https://accounts.google.com/o/oauth2/token’, $postargs );

Example: Get/Update OAuth Tokenfunction set_google_oauth2_token($grantCode, $grantType) { $settings = get_option('foo_oauth_demo_settings', true); $clienttoken_post = array( "client_id" => $settings['google_app_client_id'],"client_secret" => $settings['google_app_client_secret'] ); if ($grantType === "auth_code"){ $clienttoken_post["code"] = $grantCode; $clienttoken_post["redirect_uri"] = $settings['google_app_redirect_uri']; $clienttoken_post["grant_type"] = "authorization_code"; } if ($grantType === "refresh_token"){ $clienttoken_post["refresh_token"] = get_option('foo_google_refresh_token', true); $clienttoken_post["grant_type"] = "refresh_token"; } $postargs = array( 'body' => $clienttoken_post ); $response = wp_remote_post(‘https://accounts.google.com/o/oauth2/token’, $postargs );

Example: Get/Update OAuth Tokenfunction set_google_oauth2_token($grantCode, $grantType) { $settings = get_option('foo_oauth_demo_settings', true); $clienttoken_post = array( "client_id" => $settings['google_app_client_id'],"client_secret" => $settings['google_app_client_secret'] ); if ($grantType === "auth_code"){ $clienttoken_post["code"] = $grantCode; $clienttoken_post["redirect_uri"] = $settings['google_app_redirect_uri']; $clienttoken_post["grant_type"] = "authorization_code"; } if ($grantType === "refresh_token"){ $clienttoken_post["refresh_token"] = get_option('foo_google_refresh_token', true); $clienttoken_post["grant_type"] = "refresh_token"; } $postargs = array( 'body' => $clienttoken_post ); $response = wp_remote_post(‘https://accounts.google.com/o/oauth2/token’, $postargs );

Example: Get/Update OAuth Token (cnt’d) $authObj = json_decode(wp_remote_retrieve_body( $response ), true); if (isset($authObj[refresh_token])){ $refreshToken = $authObj[refresh_token]; $success = update_option('foo_google_refresh_token', $refreshToken, false); } if ($success) { $success = update_option('foo_google_access_token_expires', strtotime("+" . $authObj[expires_in] . " seconds")); } if ($success) { $success = update_option('foo_google_access_token', $authObj[access_token], false); if ($success) { $success = $authObj[access_token]; } } if (!$success) { $success=false; } return $success; }

Example: Get/Update OAuth Token (pt3)function get_google_access_token() { $expiration_time = get_option('foo_google_access_token_expires', true); if (! $expiration_time) { return false; } // Give the access token a 5 minute buffer (300 seconds) $expiration_time = $expiration_time - 300; if (time() < $expiration_time) { return get_option('foo_google_access_token', true); } // at this point we have an expiration time but it is in the past or will be very soon return $this->set_google_oauth2_token(null, 'refresh_token'); }

SOCIAL LOGIN

The ‘Create WordPress User’ approach• Integrates with WordPress internals• Negates most caching • Use a plugin! This stuff is hard and it is easy to get wrong• I recommend the ‘WordPress Social Login’ plugin

• open source• well supported • excellent integration capacity• I am not connected with it!

• https://wordpress.org/plugins/wordpress-social-login/

The ‘Client-only’, ‘no WP users’ approach• I have over 100k members who could be logging in. I can’t create/maintain that

many WP user accounts, can’t afford all of those database hits, must have caching • I only need to provide access to external APIs/resources• I do login etc in JavaScript in the browser, store the tokens and userinfo in cookies• I use the authorization code flow• Endpoints built into my plugin:

• Oauthcallback endpoint for initial login and then cookie setting• Each pageview triggers a JavaScript/Ajax call to the Authentication endpoint, reads/sets

cookies• Tokens are in encrypted cookies. Encryption is easy….to get WRONG• No critical user data is exposed to JavaScript

Resources• These slides have notes -- http://tinyurl.com/tamw-oauthpreso• I blog at ieg.wnet.org/author/tamw with detailed posts AND

SOURCE CODE • Google’s developer documentation is an excellent guide to OAuth -

https://developers.google.com/identity/protocols/OAuth2• JSON Web Tokens - https://jwt.io/• InfoSec StackExchange - http://security.stackexchange.com/

QUESTIONS?