7

Developing a Flask app (Python3/Heroku) for internal company use and successfully implemented Google Login (Oauth2) based on brijieshb42's article which uses requests_oauthlib.

Research has indicated that if I pass parameter "hd" (hosted domain) in my authorization url it should do the trick. E.g.

https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=OUR_CLIENT_ID&redirect_uri=https%3A%2F%2FOUR_APP.herokuapp.com%2Fconnect&scope=profile+email&state=STATE&hd=our_google_apps_domain.com&access_type=offline

My understanding based is that this parameter should provide client-side restriction and only allow logins from emails from our google apps domain (server-side I'll handle after this!) based on Google Documentation, this mailing list post and these stackoverflow posts: post1, post2.

However, though my code generates the authorization URL I pasted above -- I can still login with my personal gmail account (@gmail.com vs @our apps domain.com).

Can anyone shed some light as to why this isn't working? Or provide a different approach? Basically would prefer preventing non-employees from logging in.

I can share code as needed, but pretty much pasted from the brijeshb42 article and essentially looks like this:

OAuth2Session(
  OUR_CLIENT_ID,
  redirect_uri=https://OUR_APP.herokuapp.com/connect,
  scope=['profile', 'email']).authorization_url(
      https://accounts.google.com/o/oauth2/auth,
      hd='our_google_apps_domain.com',
      access_type='offline')

Which returns the auth url I pasted above!

Community
  • 1
  • 1
danchow
  • 105
  • 1
  • 7
  • If I understood correctly: before generating the authorization URL, your app should check if the e-mail is on the list of employees e-mails. If it isn't, then you don't want them to log in. – Cătălin Matei Dec 12 '15 at 11:12
  • Correct me if I'm wrong, but my understanding of the oauth2 flow is that you don't actually receive the email until they've logged in/been authenticated because they enter the email into the Google Sign-In page and you receive a response based on the auth flow with the email e.g. (sorry for shitty formatting from brijesh article/same as how I did) `google = get_google_auth(token=token)` `resp = google.get(Auth.USER_INFO)` `if resp.status_code == 200:` `user_data = resp.json()` `email = user_data['email']` – danchow Dec 12 '15 at 16:40
  • You are right, you don't know the e-mail beforehand. My advice is to check on your app too the user's e-mail anyway, regardless of what google says. You have to match the USER_INFO with an user in your app anyway, so if you don't have an active user to match it then authentication should fail. – Cătălin Matei Dec 13 '15 at 09:45
  • The hd param is still useful because Google's auth system can then automatically pick the right user, and skip a step in the auth process. – Chris May 02 '17 at 13:47
  • Hrm, I would need to retest -- I used the hd parameter before (1.5 years ago and it did nothing) -- maybe it works now? – danchow May 03 '17 at 13:59

2 Answers2

2

After successful authentication, you have to check the provided email yourself. I have added the code snippet from the my article that you have referenced. I have added the extra check required in after comment.

@app.route('/gCallback')
def callback():
    # Redirect user to home page if already logged in.
    if current_user is not None and current_user.is_authenticated():
        return redirect(url_for('index'))
    if 'error' in request.args:
        if request.args.get('error') == 'access_denied':
            return 'You denied access.'
        return 'Error encountered.'
    if 'code' not in request.args and 'state' not in request.args:
        return redirect(url_for('login'))
    else:
        # Execution reaches here when user has
        # successfully authenticated our app.
        google = get_google_auth(state=session['oauth_state'])
        try:
            token = google.fetch_token(
                Auth.TOKEN_URI,
                client_secret=Auth.CLIENT_SECRET,
                authorization_response=request.url)
        except HTTPError:
            return 'HTTPError occurred.'
        google = get_google_auth(token=token)
        resp = google.get(Auth.USER_INFO)
        if resp.status_code == 200:
            user_data = resp.json()
            email = user_data['email']
            """
            Your Domain specific check will come here.
            """
            if email.split('@')[1] != 'domain.com':
                flash('You cannot login using this email', 'error')
                return redirect(url_for('login'))
            user = User.query.filter_by(email=email).first()
            if user is None:
                user = User()
                user.email = email
            user.name = user_data['name']
            print(token)
            user.tokens = json.dumps(token)
            user.avatar = user_data['picture']
            db.session.add(user)
            db.session.commit()
            login_user(user)
            return redirect(url_for('index'))
        return 'Could not fetch your information.'
brijeshb42
  • 182
  • 3
  • 10
  • Thanks brijesh -- that's what I've gone with for now. Probably can accept this as an answer. But I'll leave it unanswered for a week or two because now my question is: 1. What does the hd parameter do if not client-side domain restriction 2. Is there any way to do client side restriction. To clarify on #2 -- it's not that I want to replace the server side check with a client check (obviously not secure), but it'd be great to have a gating mechanism to avoid the situation where people can login in with a personal email, give permissions then get denied login. Not for security, but user exp. – danchow Jan 07 '16 at 19:10
1

When you create the authorization URL, you can append optional parameters; appending hd= ... will do the trick:

auth_url, state = google.authorization_url(AUTH_URI, access_type='offline', hd='savv.ch')

This has many benefits. For example Google will then automatically pick the right account (if it matches the domain), which potentially saves a step in the Auth process, if the user is logged into multiple accounts.

http://requests-oauthlib.readthedocs.io/en/latest/api.html#requests_oauthlib.OAuth2Session.authorization_url

Chris
  • 1,475
  • 1
  • 20
  • 35
  • Hrm, you are saying that the hd parameter will restrict say gmail domains logging in vs your corp domain? It didn't before. I ended up fixing this issue by checking the domain and rejecting auth because the hd parameter seemingly did nothing. I will need to test this again to verify. – danchow May 03 '17 at 14:02
  • I haven't tested it with hd='gmail.com' (which is not really that useful, as anybody can create a gmail account), but with hd='corpdomain.com' it _will_ restrict logins only that domain). You should probably still check the domain on the server side, as sit may be that somebody could fake the oauth process from a gmail account. – Chris May 04 '17 at 11:33