<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SimpleLogin</title>
    <description>The latest articles on DEV Community by SimpleLogin (@simplelogin).</description>
    <link>https://dev.to/simplelogin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1037%2F72ab7fd4-b7b9-46a0-9d5a-4cbee12ebe41.jpg</url>
      <title>DEV Community: SimpleLogin</title>
      <link>https://dev.to/simplelogin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/simplelogin"/>
    <language>en</language>
    <item>
      <title>Why we left AWS</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Wed, 19 Feb 2020 03:29:21 +0000</pubDate>
      <link>https://dev.to/simplelogin/why-we-left-aws-2g8c</link>
      <guid>https://dev.to/simplelogin/why-we-left-aws-2g8c</guid>
      <description>&lt;p&gt;I've always been using AWS for hosting from simple prototypes to critical B2B systems. Thanks to its incredible catalog of products, almost all needs are covered.&lt;/p&gt;

&lt;p&gt;So naturally the first version of SimpleLogin is hosted on AWS. And as we are based in Paris, the Paris data center is picked for the proximity.&lt;/p&gt;

&lt;p&gt;For past adventures, I mostly use third-party email delivery services like Postmark, SendGrid, SES, etc. Unfortunately their pricing models are based on the number of emails, which are &lt;strong&gt;not compatible&lt;/strong&gt; with the unlimited forwards/sends that SimpleLogin offers. In addition, we want SimpleLogin to be easily self-hosted and its components fit on a single server. For these reasons, we decide to run our MTA (Mail Transfer Agent) on EC2 directly.&lt;/p&gt;

&lt;p&gt;I naively believed that would work as AWS is after all a VPS hosting service and everything can be run on EC2.  As it turns out, we ended up spending way too much time and effort to have our EC2 instances handle email delivery correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Setting up PTR (or &lt;a href="https://en.wikipedia.org/wiki/Reverse_DNS_lookup"&gt;rDNS&lt;/a&gt;) record on AWS is only achievable via a &lt;a href="https://aws.amazon.com/blogs/aws/reverse-dns-for-ec2s-elastic-ip-addresses/"&gt;request ticket&lt;/a&gt; and requires several exchanges. In comparison, on UpCloud (our current cloud provider) this could be done directly on the dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Elastic IP addresses have a &lt;strong&gt;bad&lt;/strong&gt; reputation. We tried to whitelist these IPs but some RBL (&lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System-based_Blackhole_List"&gt;Realtime Blacklist&lt;/a&gt;) just take forever. And their UX/UI is terrible. We needed to move fast and I feel this mundane task is slowing us down. After attempts to whitelist some IPs, we tested other, newer AWS data centers hoping for better results. Unfortunately, all Elastic IPs we tried were blacklisted by several RBL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS suddenly decided to block our port 25 claiming our email server had become an open relay which was simply not the case. Fortunately that was before the beta so only we were affected. It would be a catastrophe if this happened to our users. We speculate that AWS wants to push for using their SES (&lt;a href="https://aws.amazon.com/ses/"&gt;Simple Email Service&lt;/a&gt;). SES is a nice service but as explained earlier, it is not compatible with our goals. SES is used by some of our self-hosting users though. There's a section in our &lt;a href="https://github.com/simple-login/app/blob/master/docs/ses.md"&gt;self-hosting doc&lt;/a&gt; that shows how to plug SES into SimpleLogin.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By our experience, AWS doesn’t have in place a good enough mechanism to stop spammers from using their Elastic IPs, leading to their bad network reputation.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's time to move
&lt;/h3&gt;

&lt;p&gt;Because of the earlier difficulties, we took a step-back and analyzed our architecture to see if it's really dependent on AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;we used RDS to manage the database. RDS is a solid service that saves us from database maintenance stuffs like backups or patching. Its replication is also a killer feature. However SimpleLogin doesn't use the database that much: we basically just need to get the mailbox associated with an alias in order to forward the emails and that's about all. A SQLite database might just as well do the job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we used CloudWatch for monitoring and log management and CloudWatch is a very good solution to centralize and manage logs. Its pricing is also very attractive. However we don't have to be in AWS to use CloudWatch. As the logs are sent asynchronously, using CloudWatch from another cloud doesn't affect performance. In addition some new log services are  quite promising and we'd love to give them a try.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we used S3 to upload files, at the time of writing only for user profile pictures. Writing to S3 is not frequent so same as for CloudWatch, we can use S3 from another cloud. Both S3 and Cloudwatch are disabled when self-hosting SimpleLogin so all components still fit on a single server.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we decided it’s maybe better to make SimpleLogin cloud-agnostic and we'll just manage the cloud servers ourself. That opens several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We could experience first-hand the difficulties of self-hosting SimpleLogin, otherwise speaking "eat your own dog food".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We could set up a true redundancy mechanism with SimpleLogin deployed on 2 (or more) separate cloud providers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This point is not really important but it's just so refreshing to use a simple UI rather than the complex AWS Console.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We studied some popular options like DigitalOcean, OVH (OVH is very popular in France), Linode, etc and decided to give UpCloud a serious try due to several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;They came highly recommended by our friend who has more than 100 cloud servers including some email servers on UpCloud and he seems to be pretty happy with their quality &amp;amp; support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Their cloud servers are not throttled and able to achieve full performance. We haven't done any benchmark but with the same configuration, we feel UpCloud servers are indeed faster than EC2 ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Port 25 is not open by default and unlocking it requires a careful examination which helps to maintain the network reputation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We started by moving our staging environment from AWS to UpCloud. The hardest part was to replace RDS. We decided to take on managing our database ourself using Docker along with some monitoring and backup scripts. Other components were easy to move as they were already based on Docker.&lt;/p&gt;

&lt;p&gt;After extensively testing the staging environment we took the plunge to migrate the rest of our cloud environment. Our entire infrastructure is now running on UpCloud. Despite our cautious expectations that the migration would be a rough journey, in the end, the move was smooth and downtime less than 10 minutes. After deploying all components on UpCloud, the longest step was actually just waiting for the DNS changes to propagate.&lt;/p&gt;

&lt;p&gt;Now our service has run on UpCloud for some time and our users report having much better success with email delivery. Time will tell, but so far we are pretty happy with UpCloud.&lt;/p&gt;

&lt;p&gt;Our next step is to deploy SimpleLogin on another cloud provider for redundancy. Any recommendation is welcome!&lt;/p&gt;




&lt;p&gt;Originally posted on &lt;a href="https://simplelogin.io/blog/we-left-aws/"&gt;https://simplelogin.io/blog/we-left-aws/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>simplelogin</category>
    </item>
    <item>
      <title>Looking for beta testers for open-source email alias service</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Fri, 20 Dec 2019 21:22:14 +0000</pubDate>
      <link>https://dev.to/simplelogin/looking-for-beta-testers-for-open-source-email-alias-service-447a</link>
      <guid>https://dev.to/simplelogin/looking-for-beta-testers-for-open-source-email-alias-service-447a</guid>
      <description>&lt;p&gt;Hi all!&lt;/p&gt;

&lt;p&gt;We are building the first open-source email alias and identity provider service, called &lt;a href="https://simplelogin.io"&gt;SimpleLogin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works in a similar way to other email alias solution (spamex, mailcare, e4ward, etc) with Postfix as MTA and Python script to handle  the email forwarding AND &lt;strong&gt;backwarding&lt;/strong&gt; (we don't know how to call it yet, any idea here?): basically an alias can both receive and send emails.&lt;/p&gt;

&lt;p&gt;The code source for both the server and clients (libraries, browser extension) is available on our Github at &lt;a href="https://github.com/simple-login/"&gt;https://github.com/simple-login/&lt;/a&gt;, feel free to check it out! We spent quite some time on the self-hosting instructions, if you have your own server, would really appreciate if you can try it!&lt;/p&gt;

&lt;p&gt;Please let us know if you have any questions/feedbacks/critics. &lt;br&gt;
Thanks!&lt;/p&gt;

</description>
      <category>contributorswanted</category>
    </item>
    <item>
      <title>Do you prefer one-time fee or subscription?</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Sun, 10 Nov 2019 21:22:01 +0000</pubDate>
      <link>https://dev.to/simplelogin/do-you-prefer-one-time-fee-or-subscription-2l1d</link>
      <guid>https://dev.to/simplelogin/do-you-prefer-one-time-fee-or-subscription-2l1d</guid>
      <description>&lt;p&gt;Currently working on the pricing for my SaaS startup, I wonder about which pricing model to apply. It seems that almost &lt;em&gt;all&lt;/em&gt; SaaS use subscription billing nowadays and I kind of miss the &lt;em&gt;old day&lt;/em&gt; when we can buy a software just once. &lt;/p&gt;

&lt;p&gt;Would like to hear what do you guys think, do you prefer one-time fee or subscription billing for a SaaS product? &lt;/p&gt;

</description>
      <category>discuss</category>
      <category>billing</category>
      <category>startup</category>
    </item>
    <item>
      <title>If you care about user privacy, do NOT use Facebook JS SDK</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Sun, 03 Nov 2019 08:19:19 +0000</pubDate>
      <link>https://dev.to/simplelogin/if-you-care-about-user-privacy-do-not-use-facebook-js-sdk-1j3e</link>
      <guid>https://dev.to/simplelogin/if-you-care-about-user-privacy-do-not-use-facebook-js-sdk-1j3e</guid>
      <description>&lt;p&gt;Social Login buttons like the ubiquitous &lt;em&gt;Login with Facebook/Google/Twitter/...&lt;/em&gt; button is convenient for users as they don't have to go through a lengthy registration process and create yet another username/password. And without a proper password manager (which probably 99% users don't use), they tend to reuse the same password which is bad in terms of security!&lt;/p&gt;

&lt;p&gt;However behind the scene, some SDKs (I'm looking at you Facebook!) inject an iframe in your website to display the &lt;strong&gt;Continue as {MyName}&lt;/strong&gt; or &lt;strong&gt;Login with Facebook&lt;/strong&gt; button. Loading this iframe allows Facebook to know that this specific user is currently on your website. Facebook therefore knows about user browsing behaviour without user's explicit consent. If more and more websites adopt Facebook SDK then Facebook would potentially have user's &lt;strong&gt;full browsing history&lt;/strong&gt;! And as in "With great power comes great responsibility", it's part of our job as developers to protect users privacy even when they don't ask for.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Loading this iframe allows Facebook to know that this specific user is currently on your website&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nbGC7ks1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/g2392w6ky7mex284apn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nbGC7ks1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/g2392w6ky7mex284apn8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The iframe is actually injected in a second script loaded by the &lt;code&gt;https://connect.facebook.net/en_US/sdk.js&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQQJWpT1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/far46mhbj36p51lbknx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQQJWpT1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/far46mhbj36p51lbknx6.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what should we do to provide this &lt;em&gt;Login with Facebook&lt;/em&gt; button to our users? The good news is this is actually easy as Facebook implements OAuth2/OpenID standard so you can use any OAuth2/OpenID library to add the Facebook login button. You can also add other login providers like Google, Github, Apple ... at the same time as those are also OAuth2/OpenID-compliant. &lt;/p&gt;

&lt;p&gt;Here are some ressources to implement OAuth2/OpenID in your app for different languages/frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;JS: &lt;a href="https://adodson.com/hello.js/"&gt;hello.js&lt;/a&gt;, &lt;a href="https://github.com/andreassolberg/jso"&gt;jso&lt;/a&gt;, &lt;a href="https://github.com/IdentityModel/oidc-client-js"&gt;oidc-client-js&lt;/a&gt;. oidc-client-js is used to create some OAuth2/OpenID libraries for frameworks like React, VueJS, Angular, Aurelia as listed on &lt;a href="https://github.com/IdentityModel/oidc-client-js/wiki"&gt;https://github.com/IdentityModel/oidc-client-js/wiki&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python: &lt;a href="https://github.com/requests/requests-oauthlib"&gt;Requests-OAuthlib&lt;/a&gt;, &lt;a href="https://github.com/lepture/authlib"&gt;Authlib&lt;/a&gt;, &lt;a href="https://python-social-auth-docs.readthedocs.io/en/latest/"&gt;Python Social Auth&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NodeJS: &lt;a href="http://passportjs.org/"&gt;PassportJS&lt;/a&gt;, &lt;a href="https://github.com/panva/node-openid-client"&gt;openid-client&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you happen to use Flask (Python), I have written an article on dev.to on how to implement OAuth2/OpenID into a Flask application: &lt;a href="https://dev.to/simplelogin/create-a-flask-application-with-sso-login-f9m"&gt;https://dev.to/simplelogin/create-a-flask-application-with-sso-login-f9m&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you really need Facebook SDK, please ask user consent before loading the SDK or only load the SDK when user clicks on the &lt;code&gt;Login with Facebook&lt;/code&gt; button.&lt;/p&gt;

&lt;p&gt;Update 1: turns out that Google also uses this practice, more info can be found on &lt;a href="https://news.ycombinator.com/item?id=21429482"&gt;https://news.ycombinator.com/item?id=21429482&lt;/a&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>facebook</category>
      <category>openid</category>
      <category>oauth</category>
    </item>
    <item>
      <title>There are better alternatives to Password Manager</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Wed, 18 Sep 2019 21:36:53 +0000</pubDate>
      <link>https://dev.to/simplelogin/there-are-better-alternatives-to-password-manager-1655</link>
      <guid>https://dev.to/simplelogin/there-are-better-alternatives-to-password-manager-1655</guid>
      <description>&lt;p&gt;Re-using the same password for different websites is &lt;strong&gt;bad&lt;/strong&gt; in terms of security as if a hacker got his hand on a website's database, he/she will have access to your other accounts.&lt;/p&gt;

&lt;p&gt;But generating a different password for each website and &lt;strong&gt;remembering&lt;/strong&gt; them is impossible for a human being 😅. That's why &lt;strong&gt;Password Managers&lt;/strong&gt; like &lt;a href="https://www.lastpass.com"&gt;LastPass&lt;/a&gt;, &lt;a href="https://www.dashlane.com"&gt;Dashlane&lt;/a&gt;, &lt;a href="https://1password.com"&gt;1Password&lt;/a&gt;, etc are created. Their principle is simple: there's a &lt;strong&gt;master&lt;/strong&gt; password that allows you to manage all other passwords (should we call the other ones &lt;em&gt;slave passwords&lt;/em&gt; then 😜?). As loosing this master password will open the port to all our secrets, each password manager has their own &lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;multi-factor authentication&lt;/a&gt; (MFA) to protect it, ranging from &lt;a href="https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm"&gt;one-time password&lt;/a&gt; (TOTP) to printing a secret key that you bring with you all the time.&lt;/p&gt;

&lt;p&gt;These tools are gaining more popularity now that users are more and more concerned with privacy but these users are still the minority. One of the reasons is because of the difficult and unusual setup process, especially on phones. But the good news is that now Google and Apple have integrated their own password managers inside &lt;a href="https://passwords.google.com/intro"&gt;Chrome&lt;/a&gt; and iPhone/Mac, making the Password Manager concept more accessible to the general public. &lt;/p&gt;

&lt;p&gt;So all good right? Not exactly because Password Manager is only &lt;strong&gt;half the solution&lt;/strong&gt; to the security issue. Let me explain.&lt;/p&gt;

&lt;p&gt;There are usually two parts to login: the username/email and password. Password Manager only protects the password and not the email. Loosing emails has a less catastrophic effect than the password but if leaked, it can lead to the following consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unsolicited emails, aka spams&lt;/li&gt;
&lt;li&gt;social hack: knowing you are on some websites would provide enough information for a sophisticated social hack.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;So what is the &lt;em&gt;real&lt;/em&gt; solution? For me the solution to both privacy and security is to have different &lt;strong&gt;personas&lt;/strong&gt; online: one for professional work (e.g. Linkedin), one for friends &amp;amp; family (Facebook), one for selfies (Instagram 😄), one for passion (travel, football, etc). These personas are totally independent and knowing one would not reveal the others. The first step to this ideal world is to have different emails &lt;strong&gt;and&lt;/strong&gt; passwords for each website.&lt;/p&gt;

&lt;p&gt;Apple has understood that and released the &lt;a href="https://developer.apple.com/sign-in-with-apple/"&gt;Sign in with Apple&lt;/a&gt; button earlier this year. &lt;a href="https://simplelogin.io"&gt;SimpleLogin&lt;/a&gt; also works on this challenge by starting with the emails: user can create random email-alias that protects their true personal email. But email is only the first step, next would be other personal information like age, gender, phone number, address, etc. (Disclaimer: I happen to be SimpleLogin co-founder.)&lt;/p&gt;

&lt;p&gt;There's also no setup for these SSO buttons: no more additional app to install on the phone and the master password is usually already handled by the browser or the OS directly. &lt;/p&gt;

&lt;p&gt;But the challenge is now &lt;strong&gt;adoption&lt;/strong&gt;. Without developers adopting these alternatives and insist staying with the classic username/password, users still need to create their password or use their Password Managers. So make sure to ease your users's life by implementing one of those &lt;a href="https://en.wikipedia.org/wiki/Social_login"&gt;Social Login&lt;/a&gt; buttons 🙏. &lt;/p&gt;

&lt;p&gt;Please let username/password rest in peace ⚰️.&lt;/p&gt;




&lt;p&gt;Below are some tutorials for adding those social login buttons in different framework/language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/simplelogin/create-a-flask-application-with-sso-login-f9m"&gt;Flask/Python&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simpleisbetterthancomplex.com/tutorial/2016/10/24/how-to-add-social-login-to-django.html"&gt;Django/Python&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.simplelogin.io/docs/passport/"&gt;NodeJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.simplelogin.io/docs/frontend-js/"&gt;Vanilla JS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>simplelogin</category>
    </item>
    <item>
      <title>Why you shouldn't create your own authentication system</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Fri, 13 Sep 2019 18:34:58 +0000</pubDate>
      <link>https://dev.to/simplelogin/why-you-shouldn-t-create-your-own-authentication-system-hjg</link>
      <guid>https://dev.to/simplelogin/why-you-shouldn-t-create-your-own-authentication-system-hjg</guid>
      <description>&lt;p&gt;Implementing &lt;strong&gt;correctly&lt;/strong&gt; an authentication system is &lt;strong&gt;hard&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you start creating your website/application now, please use a &lt;strong&gt;third party authentication&lt;/strong&gt;. Third party authentication can be &lt;strong&gt;social login&lt;/strong&gt; (e.g. the ubiquitous &lt;code&gt;Login with Facebook&lt;/code&gt; button) or &lt;strong&gt;identity management&lt;/strong&gt; tools like &lt;a href="https://auth0.com"&gt;Auth0&lt;/a&gt; or &lt;a href="https://www.okta.com"&gt;Okta&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's not just about storing username/password in the database. In a standard web application, an authentication system needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store password &lt;a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification"&gt;hash&lt;/a&gt; instead of password in cleartext. And each user should have their own &lt;a href="https://en.wikipedia.org/wiki/Salt_(cryptography)"&gt;salt&lt;/a&gt;. And the hash function should be &lt;a href="https://codahale.com/how-to-safely-store-a-password/"&gt;expensive&lt;/a&gt; to compute. For all this, you would need a very good understanding on Rainbow tables, Bcrypt, Hash algorithms and salts. &lt;/li&gt;
&lt;li&gt;have email verification and reset-password features. For that you might need to generate and store a temporary, short-lived token that is used to activate an account or to reset password. And this is the simple part. Making sure that your email doesn't fall into the &lt;strong&gt;Spam&lt;/strong&gt; folder is much harder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the front-end side, the app needs to have a decent UI: a login form that at least validates the email and password, a sign-up form, a reset-password form. Not to mention modern authentication expects support for &lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication"&gt;MFA&lt;/a&gt; and soon (hopefully) &lt;a href="https://en.wikipedia.org/wiki/WebAuthn"&gt;WebAuthn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;More importantly, your users don't want to go through a lengthy registration process and create yet another username/password. Without a proper &lt;em&gt;password manager&lt;/em&gt; (which probably 99% users don't use), they tend to reuse the same password which is bad in terms of security!&lt;/p&gt;

&lt;p&gt;So why choosing the hard path and face the risk of password leaks, database hackings and above all, inferior user experience? Of course some applications need to have their own authentication system (banking app is an example but that's also changing with &lt;a href="https://en.wikipedia.org/wiki/Payment_Services_Directive"&gt;PSD2&lt;/a&gt;) but they are rather minority. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;So which 3rd-party verification should my app use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This really depends on your app. If getting user's Facebook feed can be a big plus to your app, then you should probably go with &lt;strong&gt;Login with Facebook&lt;/strong&gt;. If having access to user's Twitter feed is valuable, then &lt;strong&gt;Login with Twitter&lt;/strong&gt; is more appropriate. If your app only requires user basic information like email, name, avatar picture then I would suggest &lt;a href="https://simplelogin.io"&gt;Login with SimpleLogin&lt;/a&gt;, a developer-friendly login solution. Disclaimer: I happen to be SimpleLogin co-founder so this advice is obviously subjective 😅.&lt;/p&gt;

&lt;p&gt;We should all stop requiring users to choose a username and password. There are better alternatives.&lt;/p&gt;

&lt;p&gt;Please let username/password rest in peace ⚰️.&lt;/p&gt;

</description>
      <category>login</category>
      <category>authentication</category>
      <category>sso</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Create a Flask application with SSO login</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Fri, 13 Sep 2019 00:28:01 +0000</pubDate>
      <link>https://dev.to/simplelogin/create-a-flask-application-with-sso-login-f9m</link>
      <guid>https://dev.to/simplelogin/create-a-flask-application-with-sso-login-f9m</guid>
      <description>&lt;p&gt;It's often needed to have some sort of login functionality in an app so users can save data or create their own profiles. Or maybe only authenticated users can have access to reserved content. In a modern app, users expect to have standard login-related features like email verification, password reset, multi-factor authentication, etc. These features, though necessary, are not easy to get right and usually not the app's main business.&lt;/p&gt;

&lt;p&gt;On the user side, they don't want to go through the lengthy registration process neither as they need to create and remember yet another email/password. Without a proper password manager, users tend to reuse the same password which is terrible in terms of security.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSO (Single sign-on)&lt;/strong&gt;, mostly known to the public by the everywhere &lt;strong&gt;Login with Facebook/Google/Twitter&lt;/strong&gt; buttons, was invented as a solution for this issue. For users, they don't have to go through the painful registration process: one click is all it needs. For developers, removing friction for users is always a huge win and in addition, all login-related features are now &lt;em&gt;delegated&lt;/em&gt; to the &lt;strong&gt;Identity provider&lt;/strong&gt; (i.e. Facebook/Google/Twitter). Your app simply &lt;em&gt;trusts&lt;/em&gt; the Identity provider of doing its job of verifying user identity.&lt;/p&gt;

&lt;p&gt;SSO is usually powered by OIDC (OpenId Connect) or SAML protocol. SAML is used mostly in enterprise application. OIDC is built on top of OAuth2 and used by social identity providers like Facebook, Google, etc. In this post, we'll focus on the OIDC/OAuth2 protocol.&lt;/p&gt;

&lt;p&gt;This post presents a step-by-step guide to add a SSO Login button into a Flask application with SimpleLogin and Facebook as Identity provider. This can be done without using any external library but in order not to worry too much about the OAuth details, we'll use &lt;a href="https://github.com/requests/requests-oauthlib" rel="noopener noreferrer"&gt;Requests-OAuthlib&lt;/a&gt;, a library to integrate OAuth providers. If you are interested in implementing a SSO login from scratch, please check out &lt;a href="https://docs.simplelogin.io/docs/code-flow/" rel="noopener noreferrer"&gt;Implement SSO Login the raw way&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the end of this article, you should have a Flask app that has the following pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home page: that only has the &lt;strong&gt;login&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;User information page: upon successful login, the user will be able to see information such as name, email, avatar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All steps of this tutorial can be found on &lt;a href="https://github.com/nguyenkims/flask-social-login-example" rel="noopener noreferrer"&gt;flask-social-login-example repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A demo is also available at &lt;a href="https://nguyenkims-flask-social-login-example.glitch.me" rel="noopener noreferrer"&gt;https://nguyenkims-flask-social-login-example.glitch.me&lt;/a&gt;, feel free to &lt;strong&gt;remix&lt;/strong&gt; the code on &lt;a href="https://glitch.com/~nguyenkims-flask-social-login-example" rel="noopener noreferrer"&gt;Glitch&lt;/a&gt; 😉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Bootstrap Flask app
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;flask&lt;/code&gt; and  &lt;code&gt;Requests-OAuthlib&lt;/code&gt;. You can also use &lt;code&gt;virtualenv&lt;/code&gt; or &lt;code&gt;pipenv&lt;/code&gt; to isolate the environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pip install flask requests_oauthlib&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create &lt;code&gt;app.py&lt;/code&gt; and the route that displays a login button on the home page:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    &amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Login&amp;lt;/a&amp;gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's run this app and verify everything is working well:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;python app.py&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see this page when opening &lt;a href="http://localhost:5000" rel="noopener noreferrer"&gt;http://localhost:5000&lt;/a&gt;. The full code is on &lt;a href="https://github.com/nguyenkims/flask-social-login-example/blob/master/step1.py" rel="noopener noreferrer"&gt;step1.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo6ydih137sm0cmzz6joc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo6ydih137sm0cmzz6joc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Identity provider credential
&lt;/h2&gt;

&lt;p&gt;There are currently hundreds (if not thousands) identity providers with the most popular ones being Facebook, Google, Github, Instagram, etc. For this post, &lt;a href="https://simplelogin.io" rel="noopener noreferrer"&gt;SimpleLogin&lt;/a&gt; is chosen because of its &lt;strong&gt;developer-friendliness&lt;/strong&gt;. The same code will work with any OAuth2 identity provider (Facebook, Google, etc) though. Disclaimer: I happen to be SimpleLogin co-founder so this choice is obviously subjective.&lt;/p&gt;

&lt;p&gt;As going through the setup of Facebook, Google, Twitter login is a bit complex (everyone uses Google Cloud Console can feel the pain 😅) and requires additional steps that are beyond the scope of this post like setting up SSL, choosing right scopes, etc. the section &lt;strong&gt;Login with Facebook&lt;/strong&gt; is rather provided as an appendix at the end of the post.&lt;/p&gt;

&lt;p&gt;Please head to &lt;a href="https://app.simplelogin.io" rel="noopener noreferrer"&gt;SimpleLogin&lt;/a&gt; and create an account if you do not have one already, then create a new app in &lt;strong&gt;Developer&lt;/strong&gt; tab. &lt;/p&gt;

&lt;p&gt;On app detail page, please copy your AppID and AppSecret and save them into variable environment. In OAuth terminology, &lt;strong&gt;client&lt;/strong&gt; actually means a third-party app, i.e. your app. We can put these values directly in the code but it's a good practice to save credentials into &lt;strong&gt;variable environments&lt;/strong&gt;. This is also the third factor in the &lt;a href="https://12factor.net" rel="noopener noreferrer"&gt;The Twelve Factors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fzeymjg5a5areaoh39dtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fzeymjg5a5areaoh39dtw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;your AppID&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;your AppSecret&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;app.py&lt;/code&gt; please add these lines on top of the file to get &lt;code&gt;client id&lt;/code&gt; and &lt;code&gt;client secret&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Please also add these OAuth URLs on the top of &lt;code&gt;app.py&lt;/code&gt; that are going to be used in the next step. They can also be copied on the &lt;strong&gt;OAuth endpoints&lt;/strong&gt; page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;AUTHORIZATION_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.simplelogin.io/oauth2/authorize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOKEN_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.simplelogin.io/oauth2/token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;USERINFO_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.simplelogin.io/oauth2/userinfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As we don't want to worry about setting up SSL now, let's tell &lt;code&gt;Requests-OAuthlib&lt;/code&gt; that it's OK to use plain HTTP:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# This allows us to use a plain HTTP callback
&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OAUTHLIB_INSECURE_TRANSPORT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As usual, the code for this step is on &lt;a href="https://github.com/nguyenkims/flask-social-login-example/blob/master/step2.py" rel="noopener noreferrer"&gt;step2.py&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Login redirection
&lt;/h2&gt;

&lt;p&gt;When a user clicks on the login button:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The user will be redirected to the &lt;strong&gt;identity login provider&lt;/strong&gt; authorization page asking whether user wants to share their information with your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upon user approval, they will be then redirected back to a page on your app along with a &lt;code&gt;code&lt;/code&gt; in the URL that your app will use to exchange for an &lt;code&gt;access token&lt;/code&gt; that allows you later to get user information from the service provider.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We need therefore two routes: a &lt;code&gt;login&lt;/code&gt; route that redirects the user to the identity provider and a &lt;code&gt;callback&lt;/code&gt; route that receives the &lt;code&gt;code&lt;/code&gt; and exchanges for &lt;code&gt;access token&lt;/code&gt;. The callback route is also responsible for displaying user information.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;simplelogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:5000/callback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simplelogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AUTHORIZATION_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/callback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;simplelogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;simplelogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;TOKEN_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authorization_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;user_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;simplelogin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USERINFO_URL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    User information: &amp;lt;br&amp;gt;
    Name: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;br&amp;gt;
    Email: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;br&amp;gt;
    Avatar &amp;lt;img src=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;avatar_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; &amp;lt;br&amp;gt;
    &amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Home&amp;lt;/a&amp;gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Clicking on &lt;strong&gt;Login&lt;/strong&gt; button should bring you through the following flow. The full code can be found on &lt;a href="https://github.com/nguyenkims/flask-social-login-example/blob/master/step3.py" rel="noopener noreferrer"&gt;Github - step3.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ff23w5cnhuhqbjej12nak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ff23w5cnhuhqbjej12nak.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations 🎉 you have successfully integrated &lt;strong&gt;SSO login&lt;/strong&gt; into a Flask app!&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, this tutorial doesn't mention other OAuth concepts like &lt;a href="https://oauth.net/2/scope/" rel="noopener noreferrer"&gt;scope&lt;/a&gt; and &lt;a href="https://spring.io/blog/2011/11/30/cross-site-request-forgery-and-oauth2" rel="noopener noreferrer"&gt;state&lt;/a&gt;,  important to defend against *&lt;em&gt;Cross Site Request Forgery *&lt;/em&gt; attack. You would also probably need to store the user info in a database which is not covered in this article.&lt;/p&gt;

&lt;p&gt;The app also needs to be served on https on production, which can be quite easily done today with &lt;a href="https://letsencrypt.org" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Happy OAuthing!&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: Login with Facebook
&lt;/h2&gt;

&lt;p&gt;As promised earlier, please find below the steps to integrate &lt;strong&gt;Login with Facebook&lt;/strong&gt; button 🙂. Apart from a quite sophisticated UI, the hardest part about integrating Facebook might be finding a way to serve your web app on https locally as the new version of Facebook SDK doesn't allow local plain HTTP. I recommend using &lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;Ngrok&lt;/a&gt;, a free tool to have a quick https URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Facebook app:
&lt;/h3&gt;

&lt;p&gt;Please head to &lt;a href="https://developers.facebook.com" rel="noopener noreferrer"&gt;https://developers.facebook.com&lt;/a&gt; and create a new app&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpi7i5raswmt9nwqpn01s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fpi7i5raswmt9nwqpn01s.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then choose "Integration Facebook Login" on the next screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn4vvf2y1vq6mi2adboit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn4vvf2y1vq6mi2adboit.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Facebook OAuth credential
&lt;/h3&gt;

&lt;p&gt;Click on "Settings/Basic" on the left and copy the &lt;em&gt;App ID&lt;/em&gt; and &lt;em&gt;App Secret&lt;/em&gt;, they are actually OAuth client-id and client-secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8u6jlnoakwpc2ut28ll1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8u6jlnoakwpc2ut28ll1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the client-id and client-secret&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FB_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;your facebook AppId&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FB_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;your facebook AppSecret&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update the AUTHORIZATION_BASE_URL and TOKEN_URL:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;FB_AUTHORIZATION_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.facebook.com/dialog/oauth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FB_TOKEN_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://graph.facebook.com/oauth/access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The home page&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    &amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/fb-login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Login with Facebook&amp;lt;/a&amp;gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 3: Login and callback endpoints
&lt;/h3&gt;

&lt;p&gt;If the app is served behind &lt;code&gt;ngrok&lt;/code&gt; using &lt;code&gt;ngrok http 5000&lt;/code&gt; command, we need to set the current url to the ngrok url&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Your ngrok url, obtained after running "ngrok http 5000"
&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://abcdefgh.ngrok.io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Please make sure to add the url &lt;a href="https://abcdefgh.ngrok.io/fb-callback" rel="noopener noreferrer"&gt;https://abcdefgh.ngrok.io/fb-callback&lt;/a&gt; to your Facebook Login/Settings, Valid OAuth Redirect URIs setting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvzs5e4lywrq0vasirw0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvzs5e4lywrq0vasirw0x.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to have access to a user email, you need to add &lt;code&gt;email&lt;/code&gt; into &lt;code&gt;scope&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;FB_SCOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/fb-login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;facebook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/fb-callback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FB_SCOPE&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FB_AUTHORIZATION_BASE_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authorization_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;callback&lt;/code&gt; route is a bit more complex as Facebook requires a compliance fix:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib.compliance_fixes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;facebook_compliance_fix&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/fb-callback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;facebook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests_oauthlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FB_SCOPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/fb-callback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# we need to apply a fix for Facebook here
&lt;/span&gt;    &lt;span class="n"&gt;facebook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;facebook_compliance_fix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FB_TOKEN_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FB_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;authorization_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;flask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch a protected resource, i.e. user profile, via Graph API
&lt;/span&gt;
    &lt;span class="n"&gt;facebook_user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;facebook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://graph.facebook.com/me?fields=id,name,email,picture{url}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;facebook_user_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;facebook_user_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;picture_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;facebook_user_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;picture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    User information: &amp;lt;br&amp;gt;
    Name: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;br&amp;gt;
    Email: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;br&amp;gt;
    Avatar &amp;lt;img src=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;picture_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; &amp;lt;br&amp;gt;
    &amp;lt;a href=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Home&amp;lt;/a&amp;gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now when clicking on &lt;strong&gt;Login with Facebook&lt;/strong&gt;, you should be able to go through the whole flow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdo5acziteu8fwxb5oesc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fdo5acziteu8fwxb5oesc.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The full code is on &lt;a href="https://github.com/nguyenkims/flask-social-login-example/blob/master/facebook.py" rel="noopener noreferrer"&gt;facebook.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Integrating &lt;code&gt;Login with Google/Twitter/...&lt;/code&gt; is quite similar. Please let me know in the comment if you would like to have similar sections for these social login providers!&lt;/p&gt;

</description>
      <category>flask</category>
      <category>sso</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Logging: An In-Depth Tutorial</title>
      <dc:creator>Nguyen Kim Son</dc:creator>
      <pubDate>Fri, 26 Jul 2019 20:05:01 +0000</pubDate>
      <link>https://dev.to/simplelogin/python-logging-an-in-depth-tutorial-f15</link>
      <guid>https://dev.to/simplelogin/python-logging-an-in-depth-tutorial-f15</guid>
      <description>&lt;p&gt;As applications become more complex, having &lt;strong&gt;good&lt;/strong&gt; logs can be very useful, not only when debugging but also to provide insight for application issues/performance. Logging is also one of the factor in the famous &lt;a href="https://12factor.net" rel="noopener noreferrer"&gt;The twelve-factor app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Python standard library comes with a &lt;a href="https://docs.python.org/3/library/logging.html" rel="noopener noreferrer"&gt;logging&lt;/a&gt; module that provides most of the basic logging features. By setting it up correctly, a log message can bring a lot of useful information about when and where the log is fired as well as the log context, such as the running process/thread.&lt;/p&gt;

&lt;p&gt;Despite the advantages, the logging module is often overlooked as it takes some time to set up properly. The official doc at &lt;a href="https://docs.python.org/3/library/logging.html" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/logging.html&lt;/a&gt;, although complete, does not give best practices or highlight some logging surprises.&lt;/p&gt;

&lt;p&gt;This Python logging tutorial is not meant to be a complete document on the logging module but rather a &lt;strong&gt;getting started&lt;/strong&gt; guide that introduces some logging concepts as well as some &lt;strong&gt;gotchas&lt;/strong&gt; to watch out for. The post will end with best practices.&lt;/p&gt;

&lt;p&gt;Please note that all code snippets in the post suppose that you have already imported the logging module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Concepts for Python Logging
&lt;/h2&gt;

&lt;p&gt;This section gives an overview of some concepts that are often encountered in the logging module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Levels
&lt;/h3&gt;

&lt;p&gt;The log level corresponds to the &lt;strong&gt;importance&lt;/strong&gt; a log is given: an &lt;code&gt;error&lt;/code&gt; log should be more urgent then than the &lt;code&gt;warn&lt;/code&gt; log, whereas a &lt;code&gt;debug&lt;/code&gt; log should only be enabled when debugging the application.&lt;/p&gt;

&lt;p&gt;There are six log levels in Python; each level is associated with an integer that indicates the log severity: &lt;code&gt;NOTSET=0, DEBUG=10, INFO=20, WARN=30, ERROR=40, CRITICAL=50.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv7n63g8undyvlpomzias.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv7n63g8undyvlpomzias.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatter
&lt;/h3&gt;

&lt;p&gt;The log &lt;code&gt;formatter&lt;/code&gt; enriches a log message by adding context information to it. It can be useful to know when the log is sent, where (Python file, line number, method, etc.), and additional context such as the thread and process id (can be extremely useful when debugging a multi-threaded application).&lt;/p&gt;

&lt;p&gt;For example, when a log &lt;code&gt;hello world&lt;/code&gt; is sent through this log formatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will become&lt;/p&gt;

&lt;p&gt;&lt;code&gt;2018-02-07 19:47:41,864 - a.b.c - WARNING - &amp;lt;module&amp;gt;:1 - hello world&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr79k58z8bfd1gjaeeqrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fr79k58z8bfd1gjaeeqrg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Handler
&lt;/h3&gt;

&lt;p&gt;The log &lt;code&gt;handler&lt;/code&gt; is the component that effectively writes/displays a log: it can display the log in the console via &lt;code&gt;StreamHandler&lt;/code&gt;, write into a file via &lt;code&gt;FileHandler&lt;/code&gt;, or even send you an email via &lt;code&gt;SMTPHandler&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Each log handler has 2 important fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A formatter which adds context information to a log.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A log level that filters out logs whose levels are inferior. For example a log handler with the INFO level will not handle DEBUG logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The standard library provides a handful of handlers that should be enough for common use cases: &lt;a href="https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers" rel="noopener noreferrer"&gt;https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers&lt;/a&gt;. The most common ones are &lt;code&gt;StreamHandler&lt;/code&gt; and &lt;code&gt;FileHandler&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;console_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;file_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logger
&lt;/h3&gt;

&lt;p&gt;Logger is the object that we will interact with the most and which is also the most complex concept. A new logger can be obtained by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;toto_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A logger has three main fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Propagate&lt;/code&gt;: Decides whether a log should be &lt;em&gt;propagated&lt;/em&gt; to the logger’s parent. By default, its value is &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Level&lt;/code&gt;: Like the handler level, the logger level is used to filter out &lt;strong&gt;less important&lt;/strong&gt; logs. Except, unlike the log handler, the level is &lt;strong&gt;only&lt;/strong&gt; checked at the &lt;strong&gt;child&lt;/strong&gt; logger; once the log is propagated to its parents, the level will NOT be checked. This is rather an un-intuitive behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Handlers&lt;/code&gt;: The list of handlers that a log will be sent to when it arrives to a logger. This allows a flexible log handling—for example, you can have a file log handler that logs all DEBUG logs and an email log handler that will only be used for CRITICAL logs. In this regard, the logger-handler relationship is similar to a publisher-consumer one: A log will be broadcasted to all handlers once it passes the logger level check.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgdqab4np63gd1wezrlk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgdqab4np63gd1wezrlk9.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A logger is &lt;strong&gt;unique&lt;/strong&gt; by name, meaning that if a logger with the name &lt;code&gt;toto&lt;/code&gt; has been created, the consequent calls of &lt;code&gt;logging.getLogger("toto")&lt;/code&gt; will return the same object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you might have guessed, loggers have a &lt;strong&gt;hierarchy&lt;/strong&gt;. On top of the hierarchy is the &lt;code&gt;root&lt;/code&gt; logger, which can be accessed via &lt;code&gt;logging.root&lt;/code&gt;. This logger is called when a method is called directly on &lt;code&gt;logging&lt;/code&gt; module, e.g. &lt;code&gt;logging.debug()&lt;/code&gt;. By default, the root log level is &lt;code&gt;WARN&lt;/code&gt;, so every log with lower level , for example &lt;code&gt;logging.info("info")&lt;/code&gt;, will be ignored. &lt;/p&gt;

&lt;p&gt;Another particularity of the root logger is that its default handler will be created the first time a log with a level greater than WARN is logged. Using the root logger directly or indirectly via methods like &lt;code&gt;logging.debug()&lt;/code&gt; is generally &lt;strong&gt;not recommended&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By default, when a new logger is created, its parent will be set to the root logger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a.b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="c1"&gt;# lab's parent is indeed the root logger
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, the logger uses the &lt;strong&gt;dot notation&lt;/strong&gt;, meaning that a logger with the name &lt;code&gt;a.b&lt;/code&gt; will be the child of the logger &lt;code&gt;a&lt;/code&gt;. However, this is only true if the logger &lt;code&gt;a&lt;/code&gt; has been created, otherwise &lt;code&gt;ab&lt;/code&gt; parent is still the &lt;code&gt;root&lt;/code&gt; logger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;la&lt;/span&gt; &lt;span class="c1"&gt;# lab's parent is now la instead of root
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a logger decides whether a log should pass according to the level check (e.g., if the log level is lower than logger level, the log will be ignored), it uses its &lt;strong&gt;effective level&lt;/strong&gt; instead of the actual level, i.e. &lt;code&gt;logger.level&lt;/code&gt;. The effective level is the same as logger level if the level is &lt;strong&gt;NOT&lt;/strong&gt; &lt;code&gt;NOTSET&lt;/code&gt;, i.e., all the values from DEBUG up to CRITICAL; however, if the logger level is &lt;code&gt;NOTSET&lt;/code&gt;, then the effective level will be the first ancestor level that has a non-NOTSET level.&lt;/p&gt;

&lt;p&gt;By default, a new logger has the &lt;code&gt;NOTSET&lt;/code&gt; level, and as the root logger has a &lt;code&gt;WARN&lt;/code&gt; level, the logger’s effective level will be WARN. So even if a new logger has some handlers attached, these handlers will not be called unless the log level exceeds WARN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;toto_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;toto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NOTSET&lt;/span&gt; &lt;span class="c1"&gt;# new logger has NOTSET level
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEffectiveLevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARN&lt;/span&gt; &lt;span class="c1"&gt;# and its effective level is the root logger level, i.e. WARN
&lt;/span&gt;
&lt;span class="c1"&gt;# attach a console handler to toto_logger
&lt;/span&gt;&lt;span class="n"&gt;console_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;console_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# nothing is displayed as the log level DEBUG is smaller than toto effective level
&lt;/span&gt;&lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;toto_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# now you should see "debug message" on screen
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the &lt;code&gt;logger&lt;/code&gt; level will be used to decide if a log passes: If the log level is lower than logger level, the log will be ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python Logging Best Practices
&lt;/h2&gt;

&lt;p&gt;The logging module is indeed very handy, but it contains some quirks that can cause long hours of headache for even the best Python developers. Here are the best practices for using this module in my opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Configure the root logger but never use it in your code, e.g., never call a function like &lt;code&gt;logging.info()&lt;/code&gt;, which calls the root logger behind the scene. Configuring the root logger is useful if you want to catch error messages from used libraries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To use the logging, make sure to create a new logger by using &lt;code&gt;logging.getLogger(logger name)&lt;/code&gt;. I usually use  &lt;code&gt;__name__&lt;/code&gt; as the logger name for individual module or a static name for a whole app/lib, but anything can be used as long as it is consistent. To add more handlers, I usually have a method that returns a logger (you can find the gist on &lt;a href="https://gist.github.com/nguyenkims/e92df0f8bd49973f0c94bddf36ed7fd0" rel="noopener noreferrer"&gt;https://gist.github.com/nguyenkims/e92df0f8bd49973f0c94bddf36ed7fd0&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;logging.handlers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TimedRotatingFileHandler&lt;/span&gt;
&lt;span class="n"&gt;FORMATTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s — %(name)s — %(levelname)s — %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;LOG_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_app.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_console_handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;console_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StreamHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;console_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FORMATTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;console_handler&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_file_handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
   &lt;span class="n"&gt;file_handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TimedRotatingFileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;midnight&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;file_handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FORMATTER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;file_handler&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# better to have too much log than not enough
&lt;/span&gt;   &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_console_handler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_file_handler&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="c1"&gt;# with this pattern, it's rarely necessary to propagate the error up to parent
&lt;/span&gt;   &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propagate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you can create a new logger and use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;my_logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my module name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;my_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a debug message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;RotatingFileHandler&lt;/code&gt; classes, such as the &lt;code&gt;TimedRotatingFileHandler&lt;/code&gt; used in the example instead of &lt;code&gt;FileHandler&lt;/code&gt;, as it will rotate the file for you automatically when the file reaches a size limit or do it every day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use tools like Sentry, Airbrake, Raygun, etc., to catch error logs automatically for you. This is especially useful in the context of a web app, where the log can be very verbose and error logs can get lost easily. Another advantage of using these tools is that you can get details about variable values in the error so you can know what URL triggers the error, which user is concerned, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making sure logging works correctly should be one of the first things to do when setting up a new project as it could save countless hours of debugging in the future. &lt;/p&gt;

</description>
      <category>python</category>
      <category>logging</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
