Search code examples
pyramididentity

Ident/auth with temporary URLs in Pyramid


I'm building a Pyramid application. In "normal" usage, users have to use typical username/password to login and do much of anything. The Pyramid documentation made it pretty easy to cut and paste and get that going.

However, now I want to extend limited (both in authority and time -- permission expires on a given date) editing ability to people that I don't want to experience any account/password UI. I just want to email them a link that I generate, and when they click on the link I want them to land on the associated page and get identified and authorized to make some limited changes.

All the obvious stuff like generating a link, storing it in the database, associating a username and expiration date, is no problem. It's plugging this into the Pyramid ident/auth framework that I don't know how to do. I made it this far without really understanding their code in depth, and am hoping that someone has a code example of what I want to do lying around that could allow me to continue to not dive into that topic.

Or if the answer is stop being lazy and read the documentation, well, it cost me little to ask. :-)


Solution

  • Create a random number and expiration date and store them in database. Generate a link with this number and send it to the user. Check that when the link is clicked its generated random number matches one in the database. Authenticating Pyramid user by hand:

    from pyramid.security import remember, forget
    
    def authenticate_user(request, user)
    
        if not user.can_login():
            raise AuthenticationFailure('This user account cannot log in at the moment.')
    
        # get_session_token_from_user() is your custom function.
        # It usually returns user.id - it's the key how session backend maps sessions 
        # back to authenticated user.
        token = get_session_token_from_user(user)
    
        headers = remember(request, token)
        # assert headers, "Authentication backend did not give us any session headers"
    
        if not location:
            location = get_config_route(request, 'websauna.login_redirect')
    
        # Send any custom events related to user login your appplication cares of
        e = events.Login(request, user)
        request.registry.notify(e)
    
        # Redirect user to the post-login form location
        return HTTPFound(location=location, headers=headers)
    

    For the specific use case of doing one time email link logins like Slack or Medium please see websauna.magiclogin addon.