Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OAuth and VueJS

John Tran
January 01, 2020

OAuth and VueJS

Implementing "Sign-In with Facebook, Google, Slack" and more using OAuth in your Vue.js app.

John Tran

January 01, 2020
Tweet

Other Decks in Programming

Transcript

  1. OAuth & Vue.js Sign In with Facebook, Google, Slack, and

    More Presenter : John Tran pixabay.com
  2. OAuth (v2.0) in a Nutshell runkeeper.com Runkeeper
 (or your app)

    Facebook facebook.com/v5.0/dialog/oauth • runkeeper.com/loginsuccess? code=abc123 • graph.facebook.com/v5.0/oauth/access_token? code=abc123 • • runkeeper.com/login_success
 { "name": "John Tran",
 "email": "[email protected]",
 "avatar": " ",
 "access_token": "xyz789" } Username: …
 Password: … ✅ • • Optional:
 graph.facebook.com/v5.0/friends_list? access_token=xyz789 * Most websites will provide basic scope {name, email, avatar} without much fuss. Additional scopes (e.g. friends_list) may require further permissions.
  3. OAuth vs. Native Login? • Prevents storing sensitive information on

    your DB… implementing reliable login (encryption, etc.) is hard! • Avoids "yet another password to remember" problem • Many websites (Facebook, Google) have more secure login mechanisms than can be hand- rolled: 2-factor authentication, IP geolocation, etc… • Requires the user to already have (or to create) a FB, Google, etc. account • If 3rd-party website’s passwords get hacked, you got hacked • What happens if someone closes their Facebook account? • TLDR; must weigh potential business use cases for OAuth vs. Native Login PROS CONS
  4. 1. Create a Developer Account • Register for a developer

    account with your OAuth provider website of choice (Facebook, Google, etc.) — we will use Slack as the running example for this demo • After registering, click on "create a new app/ project" or something similar • This will give you three key pieces of information: Client (or Consumer/App) ID Client (or Consumer/App) Secret 
 - Never store the Client Secret in frontend JS 
 code, this should be stored on the backend Redirect (or Website) URL
 - Something you define explicitly, which they will 
 redirect back to on successful login api.slack.com
  5. 1. Create a Developer Account • Same is true for

    Facebook, Google, etc. They just might use slightly different terms. developers.facebook.com console.developers.google.com/apis/dashboard
  6. 2. Wire Up the First Hop • Create an <a

    href> tag disguised as a button, with the following URL parameters:
 scope: the type of data you want to fetch
 client_id: gotten from Step 1
 state: an (optional) random string to prevent man-in-the-middle attacks (e.g. CRSF)
 redirect_uri: the path back to your app to process their response, must be URL escaped (e.g. space = '%20') • Style the button as you wish (e.g. "Sign in with Slack"); there are also button generators out there • When the user clicks on this button, it redirects them to Slack’s OAuth Portal <a href="https://slack.com/oauth/authorize ?scope=identity.basic%20identity.email%20identity.avatar &client_id=<your_client_id> &state=<some_optional_random_hash> &redirect_uri=https%3A%2F%2Fmycoolapp.com%2Flogin "> Your App Slack slack.com/oauth/authorize Slack OAuth Portal
 slack.com/oath/authorize
  7. 3. Process the Second Hop • After the user confirms

    and allows Slack to integrate with your app, Slack will send the request back to your redirect URL with the following URL parameters:
 code: a temporary code (which expires in 10 mins) to be swapped for a more permanent access_token
 state: the same random string you submitted before, check that the two strings match to prevent middleman attacks • * Pro Tip: The state param can contain whatever you’d like, not just a random string. Sometimes it’s useful to rig this piece of info with something else — a JWT, a secondary redirect_url…
 Just saying mycoolapp.com/login? ?code=abc123 &state=<same_random_hash_that_was_sent> Your App Slack mycoolapp.com/login
  8. 4. Submit the Third Hop • Now with this magical

    piece of data, the code, send this back to Slack along with the client_id and client_secret via your backend and not your frontend << very important. Whatever backend you use (Node.js, .NET, Java, Ruby, Python, Go) use the backend to submit a request with those URL params and return the resulting JSON response to the frontend. • While it’s possible, with some CORS sidesteps, to wire everything up via the frontend-only, it is highly not recommended, because you cannot hide your client_secret effectively on client side browser code — even using Webpack minify/ uglify, gzipping, Vue CLI, and other mangling techniques. slack.com/api/oauth.access? ?code=abc123 &client_id=<your_client_id>
 &client_secret=<your_client_secret> Your App’s
 Backend Slack slack.com/api/oauth.access // Login.vue
 
 export default {
 name: 'Login',
 computed: {
 code() { return this.$route.query.code; },
 state() { return this.$route.query.state; },
 },
 created() { this.submitLogin(); },
 methods: {
 async submitLogin() {
 const { data } = 
 await axios.post('http://api.mycoolapp.com',
 { code: this.code, state: this.state }
 );
 console.log(data); /* { "name": "John Tran",
 "email": "[email protected]",
 "avatar": " ",
 "access_token": "xyz789" } */
 // Hooray! Now what? breakpoint
 },
 },
 }; * Secrets are only truly secret on the server, not the browser Your App // backend.py import env, curl
 @api_route('http://api.mycoolapp.com') def fetch_slack_oauth_data(request):
 resp = curl.get(f’
 https://slack.com/api/oauth.access
 ?code={request.params['code']}
 &client_id={env.CLIENT_ID}
 &client_secret={env.CLIENT_SECRET}
 ')
 if resp.ok:
 return Response(resp.json(), 200)
 return Response(resp.reason, 500) Python Example
  9. 4. Submit the Third Hop (Aside) Docs Lies! https://cli.vuejs.org/guide/mode-and-env.html Try

    grepping the resulting JS bundle and discover that "secret" or "my_p@55w0rd" is stored in plain-text. Not so secret.
 
 TLDR; Don’t store secrets on the frontend, like VUE_APP_SECRET. Use your backend’s ecosystem (DB, Vault, etc.) to manage secrets
  10. 5. Process Backend Response • Next Steps (optional): • Store

    user info in browser (e.g. cookie/local storage) so user can reuse session without logging in again (if they close tab, navigate elsewhere) • Save info to DB if app manages additional user- specific data other than name, email, avatar (e.g. roles/permissions, last login time) • Utilize the access_token granted to you to fetch additional app-specific data (e.g. Facebook friends, GitHub stars) if you requested additional scopes
 - The access_token serves as the user’s identity in lieu of their username/password // Store.vue
 
 export default new Vuex.Store({
 state: {
 user: { name: '', email: '', avatar: '', token: '' },
 },
 mutations: {
 logIn(state, data) {
 state.user.name = data.name;
 state.user.email = data.email;
 state.user.avatar = data.avatar;
 state.user.token = data.access_token;
 },
 actions: {
 async submitLogin(context, payload) {
 const { data } = 
 await axios.post('http://api.mycoolapp.com',
 { code: payload.code, state: payload.state }
 ); context.commit('logIn', data);
 },
 },
 }); Your App’s Backend Slack api.mycoolapp.com { "name": "John Tran",
 "email": "[email protected]",
 "avatar": " ",
 "access_token": "xyz789" } Your App // Login.vue
 
 export default {
 name: 'Login',
 computed: {
 code() { return this.$route.query.code; },
 state() { return this.$route.query.state; },
 },
 created() {
 this.$store.dispatch('submitLogin',
 { code: this.code, state: this.state }
 );
 // We are now logged in!
 this.$router.push('/home');
 },
 };
  11. The backend portion was confusing or I’m not a Backend

    Engineer
 Can I pretty please stick to frontend?
  12. Configure OAuth Client-Side • Short Answer: Yes, but you need

    to configure HTTPS on your localhost 
 - Typically done by configuring an NGINX SSL reverse proxy
 - Must create a self-signed certificate (letsencrypt.com, openssl.com)
 - Talk with backend or devops engineer if this is unfamiliar territory • Long Answer: Many providers (Google, Facebook) provide a <script /> tag to embed in the HTML directly, and by registering several callback functions via client-side code, you can get OAuth up and running. However, many of them also require the redirect_url to point to an https:// domain — simply using Webpack devServer 127.0.0.1:5000 won’t work ☹.
 
 P.S. I also don’t know how they hide their client_secret in the <script /> libraries, that’s their secret sauce.
 *Note: Slack doesn’t require https, but also doesn’t provide the simpler <script /> tag methodology.
  13. • Hook up callbacks for login/logout buttons:
 // Login.vue
 


    { ...
 methods: { 
 onSignIn(googleUser) {
 const profileInfo = googleUser
 .getBasicProfile();
 const authResp = gooleUser
 .getAuthResponse();
 const data = {
 googleId: profileInfo.getId(),
 name: profileInfo.getName(),
 avatar: profileInfo.getImageUrl(),
 email: profileInfo.getEmail(),
 token: authResp.id_token,
 };
 this.$store.commit('logIn', data);
 this.$router.push('/home');
 },
 async signOut() {
 const authInstance = gapi.auth2
 .getAuthInstance();
 await authInstance.signOut();
 this.$store.commit('logOut');
 },
 },
 ... }, Configure OAuth Client-Side • We’ll use Google as the running example.* • *Add Google’s script tag to base HTML template (public/index.html):
 <script src="https://apis.google.com/ js/platform.js" async defer></script> 
 - This script will inject some global variables (gapi, g-signin2, etc.) into your code • Add a meta tag to the <head> of your HTML template that declares your Client ID:
 <meta name="google-signin-client_id" content="<client_id>.apps.googleuserc ontent.com"> • In your Vue login page (e.g. Login.vue) add some pre-built Google buttons for Login/Logout:
 <div class="g-signin2" data- onsuccess="onSignIn" />
 
 <button @click="signOut">Log Out</ button> * Facebook’s is slightly more complicate — same idea, just more callbacks