Correlating Users Between Passage and Your Database

Introduction

Once you’ve added Passage authentication to your application, you need to be able to retrieve data about or perform actions on your users all while ensuring they are properly authenticated. In this guide, we will walk through the easiest way to correlate a Passage User with a user of your application.

Authenticating Users

Each Passage SDK has an validateJwt or validate_jwt function that is used to verify Passage Auth Tokens in your backend application before allowing requests to proceed. Most commonly, this is done in a middleware function for all requests that require authentication. Learn more about the tokens.

The validateJwt function in the Passage SDK returns the Passage User ID, which should be set in the request context so that it can be accessed throughout the code processing the request.

Node.js

// Passage middleware
const passageAuthMiddleware = (() => {
    return async (req, res, next) => {
        // Get Passage JWT from the request's Authorization header
        const authHeader = req.headers['Authorization'];
        // Drop the 'Bearer' prefix
        const token = authHeader.split(' ')[1];
 
        // Validate JWT using Passage
        try {
            const userId: string = await passage.auth.validateJwt(token);
            res.userId = userId;
            next();
        } catch (err) {
            // Token is invalid
        }
    };
})();
 
// authenticated route that uses middleware
app.get('/auth', passageAuthMiddleware, async (req, res) => {
    const userId = res.userId;
    // proceed
});

Learn more about the Node.js Complete SDK

Go

// example using Chi router
func (a *API) Handler() http.Handler {
	r := chi.NewRouter()
 
	r.Use(passageMiddleware)
	r.Route("/auth", exampleHandler)
 
	return r
}
 
// middleware that authenticates requests and sets
// passage user id in context
func (a *API) passageMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Get Passage JWT from the request's Authorization header
        authHeader := r.Header.Get("Authorization")
        token := strings.TrimPrefix(authHeader, "Bearer ")
 
        // Validate JWT using Passage
        var userID string
        var err error
        userID, err = psg.Auth.ValidateJWT(token)
        if err != nil {
            // Token is invalid
        }
 
		ctx := context.WithValue(r.Context(), ctxPassageUserID, userID)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}
 
// example handler that uses passage middleware
func exampleHandler(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value(ctxPassageUserID).(string)
}

Learn more about the Go Complete SDK

Python

# example using Flask
@auth.before_request
def before_request():
    # Get Passage JWT from the request's Authorization header
    auth_header = req.headers["Authorization"]
    token = auth_header.removeprefix("Bearer").strip()
 
    # Validate jwt using Passage
    try:
        user_id: str = passage.auth.validate_jwt(token)
    except Exception:
        # Token is invalid
        return render_template("unauthorized.html")
 
 
@auth.route("/authenticated", methods=["GET"])
def auth():
    # user_id will be set to the Passage user id here
    # ... add logic here ...

Learn more about the Python Complete SDK

Ruby

# example using Rails
class ApplicationController < ActionController::Base
    before_action :authenticate_request
 
    def authenticate_request
        # Get Passage JWT from the request's Authorization header
        auth_header = request.env['HTTP_AUTHORIZATION']
        token = auth_header.delete_prefix('Bearer').strip
 
        # Validate JWT using Passage
        begin
            user_id = passage.auth.validate_jwt(jwt: token)
        rescue Exception => e
            # Token is invalid
        end
    end
 
    def authenticated_route
        # user_id will be set to the Passage user id here
        # ... add logic here ...
    end
end

Learn more about the Ruby Complete SDK

PHP

// example using Laravel
 
// generate a new middleware
class AuthenticateRequest
{
    public function handle(Request $request, Closure $next)
    {
        // Get Passage JWT from the request's Authorization header
        $authHeader = $request->headers->get('Authorization');
        // Drop the 'Bearer' prefix
        $token = explode(' ', $authHeader)[1];
 
        try {
            $userId = $passage->auth->validateJwt($token);
            $request->attributes->set('passageUserId', $userId);
        } catch (\Exception $err) {
            // Token is invalid
        }
 
        return $next($request);
    }
}
 
// apply middleware to routes
protected $routeMiddleware = [
    // Other route-specific middleware...
    'authenticated_route' => \App\Http\Middleware\AuthenticateRequest::class,
];

Learn more about the PHP Complete SDK

Creating Users in Your Database

When users register for your application, Passage will create an account for them. You can choose to just use the middleware above and access user data on demand from Passage.

More likely, you will will want a record of the user in your database when they are created so that you can reference a Passage user by ID at any time. Let’s say you have a database model that looks like this, where a user has a “name” field and a “passage_id” field that corresponds to their Passage ID.

models.py
class User(db.Model):
   id = db.Column(db.Integer, primary_key = True)
   name = db.Column(db.String(100))
   passage_id = db.Column(db.String(32), primary_key = True)

You will then add a create_user endpoint that will add a user to your database.

views.py
auth.route("/user", methods=["POST"])
def create_user():
    # g.user will be set to the Passage user id
    u = User()
    u.passage_id = g.user
 
    # commit to database
    db.session.add(u)
    db.session.commit()
 
    return jsonify({"result": 200})

Then create an onSuccess callback on your Passage Register element that will send user data to the above endpoint once a user has been created successfully. Add the following code to a script tag just after the Passage Register element.

<passage-register app-id='{{psg_app_id}}'></passage-register>
<script src='https://cdn.passage.id/passage-elements/v2.x/passage-elements.js'></script>
<script>
    const onSuccess = (authResult) =>{
      document.cookie = 'psg_auth_token=' + authResult.authToken + ';path=/';
      const urlParams = new URLSearchParams(window.location.search)
      const magicLink = urlParams.has('psg_magic_link') ? urlParams.get('psg_magic_link') : null
      if (magicLink !== null) {
        new Promise((resolve) => {
          setTimeout(resolve, 3000)
        }).then( ()=>{ window.location.href = authResult.redirectURL;})
        return;
      }
      $.ajax({
        type: 'POST',
        async: false,
        url: '{{api_url}}/user',
        data: JSON.stringify({}), // could include other data here
        contentType: 'application/json',
        dataType: 'json'
      });
      window.location.href = authResult.redirectURL;
    }
</script>

A common use case for this is customizing registration data, which you can find an example of here.

Accessing User Data

In any functions that use the Passage middleware above, you can use the Passage User ID to refer to the user. We recommend storing the Passage User ID in your own users table to be able to reference at any time. You can use the user ID to get and update user information (e.g. email address) from your application.

An example User model might look like this, where a user has a “name” field and a “passage_id” field that corresponds to their Passage ID.

class User(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   name = db.Column(db.String(100))
   passage_id = db.Column(db.String(24), index=True)

To access data about a user, you can query the User table by passage_id in any route that is authenticated with Passage and you can use the Passage SDK to query for any Passage-specific user data (like email address or phone number).

@auth.route("/dashboard", methods=["GET"])
def dashboard():
    # g.user should be set here to the Passage user ID
 
    # use Passage user ID to get user record in DB
    user = User.query.filter_by(passage_id=g.user).first()
 
    # use Passage SDK to get info stored in Passage (email)
    psg_user = psg.user.get(g.user)
 
    return render_template("dashboard.html", name=user.name, email=psg_user.email)