Skip to content

Commit

Permalink
fix: OAuth login flow (#1707)
Browse files Browse the repository at this point in the history
* fix: OAuth login flow

* simply flow

* lint

* add docs

* add images
  • Loading branch information
dpgaspar authored Oct 25, 2021
1 parent 4f7ca85 commit a1a4f80
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 65 deletions.
Binary file added docs/images/oauth_login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/oauth_login_one_provider.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 22 additions & 9 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,9 @@ You can give FlaskAppBuilder roles based on LDAP roles (note, this requires AUTH
Authentication: OAuth
---------------------

This method will authenticate the user's credentials against an OAUTH provider.
This method will authenticate the user's credentials against an OAuth provider.

WARNING: To use OAuth you need to install `Python AuthLib <https://authlib.org>`_.

By using this method it is possible to use the OAUTH provider's APIs, this is because you're requesting the user to give
permission to manage the user's account on the provider.
Therefore, you can send tweets, post on the users Facebook, retrieve the user's LinkedIn profile etc.
Take a look at the `example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/oauth>`_
to get an idea of a simple use for this.
.. note:: To use OAuth you need to install `Python AuthLib <https://authlib.org>`_.

Specify a list of OAUTH_PROVIDERS in **config.py** that you want to allow for your users::

Expand Down Expand Up @@ -269,11 +263,30 @@ To customize the userinfo retrieval, you can create your own method like this::
else:
return {}

On Flask-AppBuilder 3.4.0 the login page has changed.

With one provider:

.. image:: ./images/oauth_login_one_provider.png
:width: 100%

With multiple providers:

.. image:: ./images/oauth_login.png
:width: 100%

Note that on 3.3.X the user would automatically be sent to the provider allow page.

Decorate your method with the SecurityManager **oauth_user_info_getter** decorator.
Your method should return a dictionary with the userinfo, the keys having the same column names as the User Model.
Your method should return a dictionary with the userinfo, with the keys having the same column names as the User Model.
Your method will be called after the user authorizes your application on the OAuth provider.
Take a look at the `example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/oauth>`_

You can also use the OAuth provider APIs.
Therefore, you can send tweets, post on the users Facebook, retrieve the user's LinkedIn profile etc.
Take a look at the `example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/oauth>`_
to get an idea of a simple use for this.

Role based
----------

Expand Down
22 changes: 7 additions & 15 deletions flask_appbuilder/security/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,24 +595,19 @@ class AuthOAuthView(AuthView):
@expose("/login/")
@expose("/login/<provider>")
@expose("/login/<provider>/<register>")
def login(
self, provider: Optional[str] = None, register: Optional[str] = None
) -> WerkzeugResponse:
def login(self, provider: Optional[str] = None) -> WerkzeugResponse:
log.debug("Provider: {0}".format(provider))
if g.user is not None and g.user.is_authenticated:
log.debug("Already authenticated {0}".format(g.user))
return redirect(self.appbuilder.get_url_for_index)

if provider is None:
if len(self.appbuilder.sm.oauth_providers) > 1:
return self.render_template(
self.login_template,
providers=self.appbuilder.sm.oauth_providers,
title=self.title,
appbuilder=self.appbuilder,
)
else:
provider = self.appbuilder.sm.oauth_providers[0]["name"]
return self.render_template(
self.login_template,
providers=self.appbuilder.sm.oauth_providers,
title=self.title,
appbuilder=self.appbuilder,
)

log.debug("Going to call authorize for: {0}".format(provider))
state = jwt.encode(
Expand All @@ -621,9 +616,6 @@ def login(
algorithm="HS256",
)
try:
if register:
log.debug("Login to Register")
session["register"] = True
if provider == "twitter":
return self.appbuilder.sm.oauth_remotes[provider].authorize_redirect(
redirect_uri=url_for(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,13 @@
{% block content %}

<script type="text/javascript">

var baseLoginUrl = "{{appbuilder.get_url_for_login}}";
var baseRegisterUrl = "{{appbuilder.get_url_for_login}}";
var next = "?next={{request.args.get('next', '')}}"

var currentSelection = "";

function set_openid(pr) {
$('.provider-select').removeClass('fa-black');
$('#' + pr).addClass('fa-black');
currentSelection = pr;
}


function signin() {
if (currentSelection != "") {
window.location.href = baseLoginUrl + currentSelection + next;
}
}

function register() {
if (currentSelection != "") {
window.location.href = baseRegisterUrl + currentSelection + '/register' + next;
}
function signin(provider) {
window.location.href = baseLoginUrl + provider + next;
}


</script>

<div class="container">
Expand All @@ -40,31 +20,21 @@
<div class="panel-title">{{ title }}</div>
</div>
<div style="padding-top:30px" class="panel-body">

<div class="help-block">{{_("Please choose one of the following providers:")}}</div>
<div class="center-block btn-group btn-group-lg" role="group">
<center>
{% for pr in providers %}
<a class="btn btn-primary" href="javascript:set_openid('{{pr.name}}');">
<i id="{{pr.name}}" class="provider-select fa {{pr.icon}} fa-3x"></i>
</a>
{% endfor %}
</center>
</div>
<div>
<br></br>
<a onclick="signin();" class="btn btn-primary btn-block" type="submit">{{_('Sign In')}}</a>
{% if appbuilder.sm.auth_user_registration %}
<a onclick="register();" class="btn btn-block btn-primary" data-toggle="tooltip" rel="tooltip"
title="{{_('If you are not already a user, please register')}}">
{{_('Register')}}
{% for provider in providers %}
<a
onclick="signin('{{ provider.name }}');"
class="btn btn-primary btn-block"
type="submit"
>
{{_('Sign In with ')}}{{ provider.name }}
<i id="{{provider.name}}" class="provider-select fa {{provider.icon}} fa-1x"></i>
</a>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
</div>


{% endblock %}

0 comments on commit a1a4f80

Please sign in to comment.