简体   繁体   中英

Redirect to previous URL after OAuth login is completed (flask-dance)

I am developing a Flask application that allows the user to login using OAuth (with Github as a provider), and the flask-dance library. For some reason I am not able to redirect, after a successful login, to the page from which I sent the user to the login page.

When the user tries to connect to, eg, http://localhost:6675/examples/tutorial.first/ , the user is redirected to the login page, showing in the URL the page we should redirect to ( http://localhost:6675/login?next=%2Fexamples%2Ftutorial.first%2F )

The problem is that after I manage to login using Github, the application just goes back to the homepage.

I was checking Flask-dance documentation and the documentation for the make_github_blueprint() function mentions the parameters redirect_to and redirect_url , but when I try using them I cannot even complete the login step. Furthermore, it seems that it would work only with static addresses, while ideally I would like to jump back to the page I was before logging in. I also checked this SO question , but the problem there seems to be different.

Are there any examples on how to properly do redirection after logging in with Flask dance?

Here some code snippets which could be relevant. In the init .py file:

bp_github = make_github_blueprint(
    client_id="...",
    client_secret="...",
)
login_manager = LoginManager()
login_manager.login_github_view = 'github.login'
login_manager.login_view = 'login'

And in the app.py file:

@app.route("/login", methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return flask.redirect(flask.url_for('/'))
    return flask.render_template('login.html')

@app.route("/logout")
@login_required
def logout():
    logout_user()
    flask.flash("You have logged out")
    return flask.redirect(flask.url_for("login"))

@oauth_authorized.connect_via(bp_github)
def logged_in(blueprint, token):
    """
    create/login local user on successful OAuth login with github
    :param blueprint:
    :param token:
    :return:
    """
    if not token:
        flask.flash("Failed to log in.", category="error")
        return False

    session = blueprint.session

    resp = session.get("/user")

    if not resp.ok:
        msg = "Failed to fetch user info."
        flask.flash(msg, category="error")
        return False

    user_id = str(info["id"])

    # Find this OAuth token in the database, or create it
    query = OAuth.query.filter_by(
        provider=blueprint.name,
        provider_user_id=user_id,
    )
    try:
        oauth = query.one()
    except NoResultFound:
        oauth = OAuth(
            provider=blueprint.name,
            provider_user_id=user_id,
            token=token,
        )

    if oauth.user:
        login_user(oauth.user)
        flask.flash("Successfully signed in.")

    else:
        # Create a new local user account for this user
        name = info['login']

        user = User(
            email=info["email"],
            name=name,
            provider=provider
        )
        # Associate the new local user account with the OAuth token
        oauth.user = user
        # Save and commit our database models
        db.session.add_all([user, oauth])
        db.session.commit()
        # Log in the new local user account
        login_user(user)
        flask.flash("Successfully signed in.")

    # Disable Flask-Dance's default behavior for saving the OAuth token
    return False

My answer might be a bit late since you have mentioned that your company decided to allow users only registered on your domain. However, I am still posting this for other users who might have this question.

To solve this problem, you will need to have a basic login page that first stores the next_url that the user should be redirected to once the login is complete via your domain login or the OAuth login. Flask adds the next parameter in the URL when you use @login_required on the URL which should be allowed only for logged in users. You need to add session['next_url'] = request.args.get('next') in your login route to capture the next parameter and store it in the session as next_url . Now, in case of the OAuth login, once the user is redirected to the default redirect_url after successful login, you will read the next_url value from the session and redirect the user to that URL if it exists or else render the default page (usually the home page.)

I am assuming 2 things, 1. that your default URL after OAuth login is successful is /home . This is set using the redirect_url of the make_provider_blueprint function in your case it is make_github_blueprint . Secondly, I am assuming your login page is the landing page for the user when they are not logged and it displays the GitHub login button which starts the OAuth login procedure. You might have to modify this flow slightly for your case to capture the next_url accordingly.

I have shared the minimal code required to handle this situation below. Note that I pop the next_url value from the session before redirecting. This is so that in case the user tries to access the home page after the login, they should not be always redirected to the next_url if it exists in the session.

bp_github = make_github_blueprint(
    client_id="...",
    client_secret="...",
    redirect_url="/home"
)

@app.route("/login", methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return flask.redirect(flask.url_for('/'))
    session['next_url'] = request.args.get('next')
    return flask.render_template('login.html')

@auth.route("/")
@auth.route("/home")
@login_required
def home():
    if session.get('next_url'):
        next_url = session.get('next_url')
        session.pop('next_url', None)
        return redirect(next_url)
    return render_template('home.html')

Let me know if this was helpful or you need any other information. I will be happy to help.

I had the same problem and I think what you are trying to accomplish is not really possible by passing the next url as arguments. If you want to use the "next" request argument, you need to go in the developer console and add every possible redirect url that the user could have (which is not really a good solution for you). What I would suggest if you really want to make it dynamic is to pass the next url in the state parameter (see this for more details about the state object. According to this documentation:

You can use this parameter for several purposes, such as directing the user to the correct resource in your application, sending nonces, and mitigating cross-site request forgery.

Unfortunately, Flask-Dance does not support this out the box so you will need to dig a little bit in the library. I would suggest taking a look at the OAuth2ConsumerBlueprint object and try to implement a custom blueprint that would work for you.

In my case, I wanted to redirect the user to another application on my domain after the successful OAuth authentication. What I do with Flask-Dance is pass the other application url in the "next" parameter like so

http://api.mycompany.com/auth/google?next=https://myotherapp.mycompany.com

and I make sure to add the complete authorized url in the google console like such:

http://api.mycompany.com/auth/google/authorized?next=https%3A%2F%2Fmyotherapp.mycompany.com

With this technique you can redirect to the desired urls without using the redirect_to parameter (which can only take one value) in flask-dance

I was having a similar problem recently and came across this question and thought I'd post my solution so others can benefit. There are many good explanations in the other answers of what needs to be done to overcome the issue so I shall not repeat the details. The main idea behind the solution is the same, which is to save your current route and then redirect back to it after successful authorisation.

The authorisation server in my example was an internal one to my company, but it should be easy to replace OAuth2ConsumerBlueprint with a provider of your choice.

from flask import Flask, redirect, url_for, session, redirect, request
from flask_dance.consumer import OAuth2ConsumerBlueprint, oauth_authorized
from functools import wraps

app = Flask(__name__)

oauth_blueprint = OAuth2ConsumerBlueprint(
    "oauth",
    __name__,
    client_id="W7n...JD6",
    client_secret="QoU...pXEX",
    base_url='https://the.oauth.server.com',
    token_url='https://the.oauth.server.com/token/',
    authorization_url='https://the.oauth.server.com/authorize/',
    scope="openid profile",
)
app.register_blueprint(oauth_blueprint, url_prefix="/login")


def auth_required(func):
    @wraps(func)
    def check_authorization(*args, **kwargs):
        """check if authorized or token expired and authenticate if necessary"""

        token = oauth_blueprint.session.token
        if not oauth_blueprint.session.authorized or token["expires_in"] < 0:
            # store current route before redirecting so we can return after successful auth
            session["next_url"] = request.path
            return redirect(url_for("oauth.login"))
        return func(*args, **kwargs)

    return check_authorization


@oauth_authorized.connect
def redirect_to_next_url(blueprint, token):
    """
    allows the the callback url to be dynamic without having to register
    each protected endpoint as a redirect with OAuth2ConsumerBlueprint.
    """
    blueprint.token = token
    next_url = session["next_url"]
    return redirect(next_url)


@app.route('/my_protected_resource')
@auth_required
def my_protected_resource():
    token = oauth_blueprint.session.token

    # add your endpoint's logic here ...
    # decode token['id_token'] for user information
    # use token['access_token'] to access protected resources
    return

I know this is an old question, but i'd just like to pass on how I overcome this issue with flask-dance and the oauth2 redirect.

I propose you store the next page in a session variable like so.

Below is your flask dance route for authentication, here you can grab the referring page and store it into a session variable.

@main.route("/googleLogin")
def googleLogin():
    # here you store the page the user has come from with the referrer value
    session["next"] = request.referrer  


    # then do your login stuff
    if not google.authorized:
        return redirect(url_for("google.login")):
    else:
        return redirect(request.referrer, code=302)  #redirect if needed

@main.route("/yourAuthorisedLoginRoute") # probably / or /home
def authed_route():
    #you can add this section of code to 1) grab the next url if 
    # its there. If it is then destroy it so the user 
    #isn't redirect there next time they navigate to 
    #this page. or 2) skip if the next value doesn't exist.

    redirecter = session.get("next",None)
    if not redirecter is None:
        session.pop("next")
        return redirect(redirecter, code=302)

    #HERE IS YOUR NORMAL PAGE CODE.
    #i.e 
    return render_template("home.html")

My solution is go to OIDAuthorizationService.m and comment the code bellow

if (!_pendingauthorizationFlowCallback) {

        [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow

                    format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil];

    }

to:

// if (!_pendingauthorizationFlowCallback) {

    //    [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow

    //                format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil];

    //}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM