Bài viết này mình sẽ hướng dẫn cách cơ bản nhất để có thể đăng nhập Google Oauth2 trong ứng dụng web Flask bằng gói request-oauthlib cho OAuth 2.0 và jar-sqlalchemy .
Để bắt đầu, trước tiên chúng ta phải tạo một dự án trong Google Developers của Google để lấy được client key và client secret của người dùng.
1. Tạo Google project
Đầu tiên, hãy truy cập Google Developers Console . Đăng nhập bằng thông tin đăng nhập Google của bạn nếu bạn chưa có có thể đăng ký. Sẽ có một danh sách các dự án (nếu trước đây bạn đã tạo với bất ký dự án nào của mình).
Tạo 1 dự án mới.
Bạn điền đầy đủ tên mà bạn muốn đặt.
Bây giờ đi đến trang dự án. Nhấp vào API và Auth -> Thông tin xác thực trong thanh bên trái.
Sau đó, đi đến màn hình đồng ý OAuth. Cung cấp Tên sản phẩm (bạn cũng có thể cung cấp các chi tiết khác nhưng chúng là tùy chọn). Tên sản phẩm là những gì người dùng nhìn thấy khi họ đăng nhập vào ứng dụng của bạn bằng Google.
Bây giờ bấm vào phần Thông tin xác thực . Sau đó bấm vào Thêm thông tin xác thực và sau đó chọn ID khách hàng OAuth 2.0.
Chọn Loại ứng dụng Web Application, Cung cấp tên, nguồn gốc Javascript được ủy quyền và URI chuyển hướng được ủy quyền và nhấp vào Tạo. Trong quá trình phát triển, chúng ta sẽ sử dụng localhost làm URL của chúng ta.
Sau bước trên, bạn sẽ thấy một hộp thoại có client ID và client secret. Sao chép cả hai chuỗi và lưu trong một tệp văn bản vì chúng ta sẽ cần những chuỗi này.
2. Tạo bảng User trong Database
Chúng ta sẽ sử dụng flask-sqlalchemy
để xử lý các hoạt động DB.
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> <span class="token class-name">User</span><span class="token punctuation">(</span>db<span class="token punctuation">.</span>Model<span class="token punctuation">)</span><span class="token punctuation">:</span> __tablename__ <span class="token operator">=</span> <span class="token string">"users"</span> <span class="token builtin">id</span> <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>Integer<span class="token punctuation">,</span> primary_key<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> email <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>String<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">,</span> unique<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">,</span> nullable<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span> name <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>String<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullable<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> avatar <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>String<span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">)</span> active <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>Boolean<span class="token punctuation">,</span> default<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span> tokens <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>Text<span class="token punctuation">)</span> created_at <span class="token operator">=</span> db<span class="token punctuation">.</span>Column<span class="token punctuation">(</span>db<span class="token punctuation">.</span>DateTime<span class="token punctuation">,</span> default<span class="token operator">=</span>datetime<span class="token punctuation">.</span>datetime<span class="token punctuation">.</span>utcnow<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> |
3. Tạo config cho ứng dụng.
Nếu sử dụng flask-login
để quản lý phiên người dùng, chúng ta có thể kiểm tra xem người dùng có đăng nhập hay không. Nếu không đăng nhập, chúng ta chuyển hướng người dùng đến trang đăng nhập có chứa liên kết đến thông tin đăng nhập Google. Tạo 1 file config.py
để cấu hình các thông số liên quan của Google OAuth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <span class="token keyword">import</span> os basedir <span class="token operator">=</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>abspath<span class="token punctuation">(</span>os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>dirname<span class="token punctuation">(</span>__file__<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">Auth</span><span class="token punctuation">:</span> CLIENT_ID <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token string">'688061596571-3c13n0uho6qe34hjqj2apincmqk86ddj'</span> <span class="token string">'.apps.googleusercontent.com'</span><span class="token punctuation">)</span> CLIENT_SECRET <span class="token operator">=</span> <span class="token string">'JXf7Ic_jfCam1S7lBJalDyPZ'</span> REDIRECT_URI <span class="token operator">=</span> <span class="token string">'https://localhost:5000/gCallback'</span> AUTH_URI <span class="token operator">=</span> <span class="token string">'https://accounts.google.com/o/oauth2/auth'</span> TOKEN_URI <span class="token operator">=</span> <span class="token string">'https://accounts.google.com/o/oauth2/token'</span> USER_INFO <span class="token operator">=</span> <span class="token string">'https://www.googleapis.com/userinfo/v2/me'</span> <span class="token keyword">class</span> <span class="token class-name">Config</span><span class="token punctuation">:</span> APP_NAME <span class="token operator">=</span> <span class="token string">"Test Google Login"</span> SECRET_KEY <span class="token operator">=</span> os<span class="token punctuation">.</span>environ<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"SECRET_KEY"</span><span class="token punctuation">)</span> <span class="token operator">or</span> <span class="token string">"somethingsecret"</span> <span class="token keyword">class</span> <span class="token class-name">DevConfig</span><span class="token punctuation">(</span>Config<span class="token punctuation">)</span><span class="token punctuation">:</span> DEBUG <span class="token operator">=</span> <span class="token boolean">True</span> SQLALCHEMY_DATABASE_URI <span class="token operator">=</span> <span class="token string">'sqlite:///'</span> <span class="token operator">+</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>join<span class="token punctuation">(</span>basedir<span class="token punctuation">,</span> <span class="token string">"test.db"</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">ProdConfig</span><span class="token punctuation">(</span>Config<span class="token punctuation">)</span><span class="token punctuation">:</span> DEBUG <span class="token operator">=</span> <span class="token boolean">True</span> SQLALCHEMY_DATABASE_URI <span class="token operator">=</span> <span class="token string">'sqlite:///'</span> <span class="token operator">+</span> os<span class="token punctuation">.</span>path<span class="token punctuation">.</span>join<span class="token punctuation">(</span>basedir<span class="token punctuation">,</span> <span class="token string">"prod.db"</span><span class="token punctuation">)</span> config <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">"dev"</span><span class="token punctuation">:</span> DevConfig<span class="token punctuation">,</span> <span class="token string">"prod"</span><span class="token punctuation">:</span> ProdConfig<span class="token punctuation">,</span> <span class="token string">"default"</span><span class="token punctuation">:</span> DevConfig <span class="token punctuation">}</span> |
REDIRECT_URI là những gì chúng ta Google Developers Console dành cho nhà phát triển của Google
AUTH_URI là nơi người dùng được đưa đến để đăng nhập Google
TOKEN_URI được sử dụng để trao đổi mã thông báo tạm thời cho access_token
USER_INFO là URL được sử dụng để truy xuất thông tin người dùng như tên , email , v.v … sau khi xác thực thành công.
SCOPE là loại thông tin người dùng mà chúng ta sẽ truy cập sau khi người dùng xác thực ứng dụng. Google OAuth2 Playground có một danh sách các phạm vi có thể được thêm vào.
URL routes for login and callback
1 2 3 4 5 6 7 | app <span class="token operator">=</span> Flask<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span> app<span class="token punctuation">.</span>config<span class="token punctuation">.</span>from_object<span class="token punctuation">(</span>config<span class="token punctuation">[</span><span class="token string">'dev'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> db <span class="token operator">=</span> SQLAlchemy<span class="token punctuation">(</span>app<span class="token punctuation">)</span> login_manager <span class="token operator">=</span> LoginManager<span class="token punctuation">(</span>app<span class="token punctuation">)</span> login_manager<span class="token punctuation">.</span>login_view <span class="token operator">=</span> <span class="token string">"login"</span> login_manager<span class="token punctuation">.</span>session_protection <span class="token operator">=</span> <span class="token string">"strong"</span> |
requests_oauthlib.OAuth2Session helper
1 2 3 4 5 6 7 8 | <span class="token keyword">def</span> <span class="token function">get_google_auth</span><span class="token punctuation">(</span>state<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">,</span> token<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">if</span> token<span class="token punctuation">:</span> <span class="token keyword">return</span> OAuth2Session<span class="token punctuation">(</span>Auth<span class="token punctuation">.</span>CLIENT_ID<span class="token punctuation">,</span> token<span class="token operator">=</span>token<span class="token punctuation">)</span> <span class="token keyword">if</span> state<span class="token punctuation">:</span> <span class="token keyword">return</span> OAuth2Session<span class="token punctuation">(</span> Auth<span class="token punctuation">.</span>CLIENT_ID<span class="token punctuation">,</span> state<span class="token operator">=</span>state<span class="token punctuation">,</span> redirect_uri<span class="token operator">=</span>Auth<span class="token punctuation">.</span>REDIRECT_URI<span class="token punctuation">)</span> oauth <span class="token operator">=</span> OAuth2Session<span class="token punctuation">(</span> Auth<span class="token punctuation">.</span>CLIENT_ID<span class="token punctuation">,</span> redirect_uri<span class="token operator">=</span>Auth<span class="token punctuation">.</span>REDIRECT_URI<span class="token punctuation">,</span> scope<span class="token operator">=</span>Auth<span class="token punctuation">.</span>SCOPE<span class="token punctuation">)</span> <span class="token keyword">return</span> oauth |
Root URL:
1 2 3 4 5 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span> @login_required <span class="token keyword">def</span> <span class="token function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> render_template<span class="token punctuation">(</span><span class="token string">'index.html'</span><span class="token punctuation">)</span> |
Callback URL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | @app<span class="token punctuation">.</span>route<span class="token punctuation">(</span><span class="token string">'/gCallback'</span><span class="token punctuation">)</span> <span class="token keyword">def</span> <span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># Redirect user to home page if already logged in.</span> <span class="token keyword">if</span> current_user <span class="token keyword">is</span> <span class="token operator">not</span> <span class="token boolean">None</span> <span class="token operator">and</span> current_user<span class="token punctuation">.</span>is_authenticated<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> redirect<span class="token punctuation">(</span>url_for<span class="token punctuation">(</span><span class="token string">'index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token string">'error'</span> <span class="token keyword">in</span> request<span class="token punctuation">.</span>args<span class="token punctuation">:</span> <span class="token keyword">if</span> request<span class="token punctuation">.</span>args<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'error'</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'access_denied'</span><span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">'You denied access.'</span> <span class="token keyword">return</span> <span class="token string">'Error encountered.'</span> <span class="token keyword">if</span> <span class="token string">'code'</span> <span class="token operator">not</span> <span class="token keyword">in</span> request<span class="token punctuation">.</span>args <span class="token operator">and</span> <span class="token string">'state'</span> <span class="token operator">not</span> <span class="token keyword">in</span> request<span class="token punctuation">.</span>args<span class="token punctuation">:</span> <span class="token keyword">return</span> redirect<span class="token punctuation">(</span>url_for<span class="token punctuation">(</span><span class="token string">'login'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">else</span><span class="token punctuation">:</span> <span class="token comment"># Execution reaches here when user has successfully authenticated our app.</span> google <span class="token operator">=</span> get_google_auth<span class="token punctuation">(</span>state<span class="token operator">=</span>session<span class="token punctuation">[</span><span class="token string">'oauth_state'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">try</span><span class="token punctuation">:</span> token <span class="token operator">=</span> google<span class="token punctuation">.</span>fetch_token<span class="token punctuation">(</span> Auth<span class="token punctuation">.</span>TOKEN_URI<span class="token punctuation">,</span> client_secret<span class="token operator">=</span>Auth<span class="token punctuation">.</span>CLIENT_SECRET<span class="token punctuation">,</span> authorization_response<span class="token operator">=</span>request<span class="token punctuation">.</span>url<span class="token punctuation">)</span> <span class="token keyword">except</span> HTTPError<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token string">'HTTPError occurred.'</span> google <span class="token operator">=</span> get_google_auth<span class="token punctuation">(</span>token<span class="token operator">=</span>token<span class="token punctuation">)</span> resp <span class="token operator">=</span> google<span class="token punctuation">.</span>get<span class="token punctuation">(</span>Auth<span class="token punctuation">.</span>USER_INFO<span class="token punctuation">)</span> <span class="token keyword">if</span> resp<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">:</span> user_data <span class="token operator">=</span> resp<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span> email <span class="token operator">=</span> user_data<span class="token punctuation">[</span><span class="token string">'email'</span><span class="token punctuation">]</span> user <span class="token operator">=</span> User<span class="token punctuation">.</span>query<span class="token punctuation">.</span>filter_by<span class="token punctuation">(</span>email<span class="token operator">=</span>email<span class="token punctuation">)</span><span class="token punctuation">.</span>first<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> user <span class="token keyword">is</span> <span class="token boolean">None</span><span class="token punctuation">:</span> user <span class="token operator">=</span> User<span class="token punctuation">(</span><span class="token punctuation">)</span> user<span class="token punctuation">.</span>email <span class="token operator">=</span> email user<span class="token punctuation">.</span>name <span class="token operator">=</span> user_data<span class="token punctuation">[</span><span class="token string">'name'</span><span class="token punctuation">]</span> user<span class="token punctuation">.</span>tokens <span class="token operator">=</span> json<span class="token punctuation">.</span>dumps<span class="token punctuation">(</span>token<span class="token punctuation">)</span> user<span class="token punctuation">.</span>avatar <span class="token operator">=</span> user_data<span class="token punctuation">[</span><span class="token string">'picture'</span><span class="token punctuation">]</span> db<span class="token punctuation">.</span>session<span class="token punctuation">.</span>add<span class="token punctuation">(</span>user<span class="token punctuation">)</span> db<span class="token punctuation">.</span>session<span class="token punctuation">.</span>commit<span class="token punctuation">(</span><span class="token punctuation">)</span> login_user<span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token keyword">return</span> redirect<span class="token punctuation">(</span>url_for<span class="token punctuation">(</span><span class="token string">'index'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">'Could not fetch your information.'</span> |
Run serve.py để thử nghiệm.