Advanced Topics

Linking User Identities

In this guide we will walk through some required steps in your integration of Pinn into your backend:

  • linking created Pinn users with the corresponding user entries in your system's database
  • using these links to tie successful Pinn authentications back to your system's users
  • querying the Pinn API to retrieve authentication information about the users who have enrolled with Pinn.

As soon as a Pinn user is created, your system needs to link that Pinn user with the corresponding user object in your system. Because Pinn does not retain any Personally Identifiable Information (PII) about your users, without this bidirectional mapping, your backend would not be able to verify which particular user has successfully authenticated with Pinn. We will show you how to create this mapping as well how to use it to verify authentications and retrieve information about your users' Pinn enrollment status.

Pinn User IDs

When you create a Pinn user (at or prior to the time your end user enrolls their palms), Pinn will create a new Pairwise Pseudonymous Identifier (PPID) for that user. This Pinn User ID is generated such that, even if someone gains access to your organization's assigned Pinn secret_key, they would not be able to personally identify any user without also having direct access to your system's database. This keeps your users' private data safe on your own servers, but at the same time it places the burden on your developers to properly associate each Pinn user with your system's record for that user.

Linking

Let's take a look at some typical database ORM code and show how your system may want to perform this association. Before integration with Pinn, your backend's user model might look something like this:

class User(db.Model):
    id = db.Column(db.String, primary_key=True)
    email = db.Column(db.String, unique=True, nullable=False)
    name = db.Column(db.String, nullable=True
        

At this point each user has a unique ID id, an email address email and a full name name. Let's now add a column to associate each user in your database with a user in the Pinn database.

class User(db.Model):
    id = db.Column(db.String, primary_key=True)
    pinn_user_id = db.Column(db.String, unique=True)
    email = db.Column(db.String, unique=True, nullable=False)
    name = db.Column(db.String, nullable=True)
        

This new column holds references to Pinn users in the form of a Pinn User ID.

Now let's see how the backend code would handle the registration of a brand-new user account. You'll add a row to this table for each new user. But before that, you'll need to create a Pinn user and then, with the returned Pinn User ID, you can create a user model object and save it to the database. This will look something like this:

pinn_user = pinn.User.create()
system_user = User(id=generate_id(prefix='usr'),
                   pinn_user_id=pinn_user.user_id,
                   email=request.form['email'],
                   name=None)
db.session.add(system_user)
db.session.commit()
        

Here, pinn.User.create represents a function that you need to implement to make a REST call to Pinn's CreateUser endpoint.

The code above handles the simultaneous creation of a user row in both your system's database and the Pinn database for brand-new user accounts. In the case of your pre-existing users for whom you want to enable Pinn authentication, you would need to retrieve the particular user model from your database, modify the object to hold the Pinn User ID for the new Pinn user object and then save it back. Something like this:

pinn_user = pinn.User.create()
system_user = User.query.filter_by(id=request.form['user_id']).first()
system_user.pinn_user_id = pinn_user.user_id
db.session.commit()
        

Verification

Now that you have completed the user mapping, whenever a user authenticates with Pinn, your system can look up and identify the end user based on their Pinn User ID. You need to do this every time that your backend receives a Pinn ID Token so that you can verify it and extract the Pinn User ID from it. Only after verification should your system authorize the user login or other sensitive action.

Say that on your web app, you want to replace password login with Pinn's Web-Initiated Auth; e.g. users would log in by scanning a QR code with your mobile app. Continuing with our ORM example above, the implementation would look something like this:

@bp.route('/login', methods=['GET', 'POST'])
def login(**kwargs):
    if request.method == 'POST':
        # Step 1: verify the ID Token and extract its claims
        if 'id_token' not in request.form:
            raise errors.BadRequest('`id_token` missing from form')
        try:
            claims = pinn.IDToken.verify(request.form['id_token'],
                                         amr=['device', 'either_palm'])
        except pinn.errors.IDTokenVerificationError:
            raise errors.Unauthorized('Invalid `id_token` provided')

        # Step 2: look up your system's user object
        try:
            user = User.query.filter_by(pinn_user_id=claims['sub']).first()
        except User.NotFound:
            raise errors.Unauthorized('`id_token` belongs to unknown user')

        # Step 3: give the user a session
        login_user(user)
        

Notice how the User model in your system is fetched based on the Pinn User ID. This association is the glue between your system and Pinn's services.

Step 2 above may be different depending on your situation. Say that, instead of implementing a web login flow where Pinn handles all the authentication, you want to add Pinn authentication as a second step in the login flow only after the user has already typed a verified password. In that case, you wouldn't need to look up your system's user object by the Pinn User ID; you would have already looked up a user object based on the user's login email address. So you would then just dereference the pinn_user_id in your user object and check that it equals the pinn_user_id extracted from the ID Token.

Retrieving a Pinn User object

We've shown looking up your system's user model given a Pinn User ID. And we've discussed looking a Pinn User ID given your system's user model. Another useful operation is retrieving an entire Pinn User object from Pinn's REST API given your system's user model.

Let's add a property method to your user model so that you can conveniently retrieve the Pinn user object:

class User(db.Model):
    id = db.Column(db.String, primary_key=True)
    pinn_user_id = db.Column(db.String, unique=True)
    email = db.Column(db.String, unique=True, nullable=False)
    name = db.Column(db.String, nullable=True)

    @property
    def pinn_user(self):
        return pinn.User.retrieve(self.pinn_user_id)
        

Here, pinn.User.retrieve represents a function that you need to implement to make a REST call to Pinn's RetrieveUser endpoint with the pinn_user_id.

Now, you can use this property method to access authentication properties that are managed by Pinn for a particular user. For example:

# Get an instance of the system user model
>>> user = User.query.filter_by(email='john@example.com').first()

# Now you can access properties of the Pinn user model
>>> user.pinn_user.left_palm_enrolled
True
        

?

Questions?

We are here to help! Contact us with any development related questions at dev@pinn.ai and we'll reach back in a timely manner.