Skip to main content

Full Stack Integration

With a full stack integration it is possible to gain all of the benefits of OwnID even if your website uses an identity platform that does not have a pre-built OwnID integration. In a full stack integration, the OwnID server communicates with your website's server by sending requests to a set of API endpoints that have been added by your team. This article provides the specification for these APIs, allowing you to build the endpoints that are accessed by the OwnID server.

Supported Languages

In a full stack integration, you will:

  • Build API endpoints into your backend
  • Create an OwnID application
  • Integrate the OwnID widget into your frontend

Step 1: Create Database Field#

The OwnID server uses an ownIdData object to store OwnID authentication information in a website's database. The structure of this String is unimportant to the website's backend --- the backend just needs to include a database field that stores the JSON blob when it is received from the OwnID server and be able to retrieve it when the OwnID server requires it.

If your database requires you to create a field before storing the ownIdData object that is received from the OwnID server, do it now. For a relational database (RDBMS), be sure the field meets the following requirements:

  • A character datatype e.g., VARCHAR
  • A minimum length of 5000 chars

Step 2: Build Endpoints#

OwnID requires your endpoints to conform to the following specifications. These endpoints must share a common base URL that is unique.

POST /setOwnIDDataByLoginId#

As a user interacts with OwnID, the OwnID server sends a POST /setOwnIDDataByLoginId request to store authentication data in the website's backend. When the backend receives this request, it must associate the specified user with the authentication data (ownIdData) and a lookup field for that data (loginId) so the OwnID server can retrieve the data with future API calls. As an example, when a website's backend receives the following request it must associate the user identified by sol@testmail.com with the provided OwnID authentication data.

curl --location --request POST 'https://bazco.company/setOwnIDDataByLoginId/' \
--header 'Content-Type: application/json' \
--data-raw '{
"loginId": "sol@testmail.com",
"ownIdData":"<String>"
}'

Request Parameters

When the OwnID server sends a POST /setOwnIDDataByLoginId request, it encodes parameters as JSON with a Content-Type of application/json. The following parameters must be supported:

ParameterTypeIs required?Description
loginIdstringTrueThe unique identifier of a user in the database, usually email or phone.
ownIdDatastringTrueThe OwnID authentication data object.

Expected Response

If the website's backend successfully receives and processes the POST /setOwnIDDataByLoginId request, it should return a 204 HTTP status code with an empty response body.

{ }

Code Example

router.post('/setOwnIDDataByLoginId', async (req, res) => {
const email = req.body.loginId; //The unique id of a user in your database, usually email or phone
const ownIdData = req.body.ownIdData; //OwnID authentication information as string
const user = await User.findOne({ email: email }).exec();
user.ownIdData = ownIdData;
await user.save();
return res.sendStatus(204);
});

POST /getOwnIDDataByLoginId#

OwnID sends a POST /getOwnIDDataByLoginId request to retrieve OwnID authentication data about a user. The request finds the user within the identify platform based on the user's loginId, which is usually the user's email or phone number. This loginId uniquely identifies the user within the database. For example, the following request should retrieve the OwnID authentication data about a user whose unique identifier is an email address:

curl --location --request POST 'https://bazco.company/getOwnIDDataByLoginId' \
--header 'Content-Type: application/json' \
--data-raw '{
"loginId":"sol@testmail.com"
}'

Request Parameters

When the OwnID server sends a POST /getOwnIDDataByLoginId request, it encodes parameters as JSON with a Content-Type of application/json. The following parameter must be supported:

ParameterTypeIs required?Description
loginIdstringTrueThe unique identifier of a user in the database, usually email or phone.

Expected Response

When the OwnID server makes a POST /getOwnIDDataByLoginId request, it expects to receive back the ownIdData object that was stored in the website's database the last time the OwnID server made a POST /setOwnIdDataByLoginId request. This response has the following structure:

{
"ownIdData": "<String>"
}

If the user exists but doesn't have the ownIdData object (this is the use case of an existing user going passwordless for the first time), we expect a response with an empty ownIdData:

{
"ownIdData": ""
}

Error handling when user doesn't exist

When the user (loginID) doesn't exist in your database, you must return a standard HTTP code with the following response body:

{
"errorCode": 404,
"errorMessage": "User not found"
}

Code Example

router.post('/getOwnIDDataByLoginId', async (req, res) => {
const email = req.body.loginId; //The unique id of a user in your database, usually email or phone
const user = await User.findOne({ email: email }).exec();
if (!user) { return res.json({ errorCode: 404 }) } //Error code when user doesn't exist
res.json({ ownIdData: user.ownIdData }) //OwnID authentication information as string
});

POST /getSessionByLoginId#

The OwnID servers sends a POST /getSessionByLoginId request after the user has successfully logged in with OwnID. The backend receiving the request is expected to return a JSON object that will be used to authenticate the user on the client side. Often times, the backend returns a JWT for this purpose, but it can also be an arbitrary JSON string. Once it receives the JSON object, the OwnID server passes it to the client-side app using a JavaScript event.

curl --location --request POST 'https://bazco.company/getSessionByLoginId' \
--header 'Content-Type: application/json' \
--data-raw '{
"loginId":"sol@testmail.com",
"sessionType":"browser",
}'

Request Parameters

When the OwnID server sends a POST /getSessionByLoginId request, it encodes parameters as JSON with a Content-Type of application/json. The following parameters must be supported:

ParameterTypeIs required?Description
loginIdstringTrueThe unique identifier of a user in the database, usually email or phone.
sessionTypestringFalseSession type, browser or mobile.

Expected Response

When the OwnID makes a POST /getSessionByLoginId request, it expects to receive back a response with the following structure, where token is a JSON object, often a JWT:

{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}

Code Example

router.post('/getSessionByLoginId', async (req, res) => {
const sign = require('jwt-encode');
const email = req.body.loginId; //The unique id of a user in your database, usually email or phone
const user = await User.findOne({ email: email }).exec();
const jwt = sign({ email: user.email }, 'secret');
return res.json({ token: jwt });
});

Step 3: Create OwnID Application#

Each website integrating with OwnID must have an OwnID application. To create an OwnID application:

  1. Open the OwnID Console and create an account or log in to an existing account.
  2. Select Create Application.
  3. Select the language you are using to build the endpoints required by the OwnID server.
  4. Confirm that you have built the API endpoints by selecting Next.
  5. Enter the base URL that is shared by the API endpoints.
  6. Select your frontend framework and complete the integration steps. These steps are discussed below in more detail.

Step 4: Add OwnID to Your Frontend#

Installing the OwnID SDK#

If a website uses a framework like Angular or ReactJS, it must install the OwnID SDK before adding the OwnID widget to its registration and login flows. To install the OwnID SDK for a framework, use the CLI to run:

npm install @ownid/react

or

yarn add @ownid/react

Initializing the OwnID SDK#

Regardless of whether a website is using a framework or javascript, the OwnID SDK needs to be included and initialized with the appId of the website's OwnID application. This unique appId can be obtained by opening the application in the OwnID Console.

Import the OwnIDInit component to your App component and configure it with your appId:

import { OwnIDInit } from '@ownid/react';
...
<OwnIDInit config={{appId:'9s8d7f9s87g98s7dgMyAppID'}}/>

Registration Page#

Allowing users to register with OwnID involves frontend integration along with a modification to how your backend handles the user's information when they submit their registration details.

Registration Diagram

Frontend Modifications: As you modify your website's frontend to integrate OwnID into the registration page, you will:

  • Render the OwnID widget by adding it to your form.
  • Define a variable to store the OwnID authentication payload returned by the onRegister function. This payload is an ownIdData object.
  • Modify the function that is executed when the registration form is submitted (for example, onSubmit) so it adds the ownIdData object to the user's registration data.

Add references to required elements, define onRegister function, and modify the function that is called when the form is submitted:

function RegisterComponent() {
const emailField = useRef(null);
const passwordField = useRef(null);
const [ownIdDataString, setOwnIDData] = useState(null);
// Stores ownIdData
function onRegister(event) {
setOwnIDData(event.data);
}
function onSubmit(userData) {
// Call your existing registration logic in the backend
return register({ โ€ฆuserData, ownIdData: ownIdDataString});
}
return (
<form onSubmit={onSubmit}>
<input ref={emailField} type="email" name="email" />
<input ref={passwordField} type="password" name="password" />
<button type="submit">Register</button>
<OwnID type={'register'}
loginIdField={email.current}
passwordField={password.current}
onError={(error) => console.error(error)}
onRegister={onRegister} />
</form>
);
}

Backend Modifications: When the user submits their registration details, your onSubmit function eventually calls a method in the backend that completes the registration. You must modify this backend registration logic so it stores the ownIdData field of the userData object into the database field designed for this purpose. This is the same database field used to store ownIdData objects sent from the OwnID server to the backend's /setOwnIDDataByLoginId endpoint.

Login Page#

Adding the OwnID widget to a login page allows users who have registered with OwnID to log into the website. The widget's onLogin function, which is triggered when the user logs in with OwnID, returns an object that is used to set the session in the frontend. It is the same object that was sent from your backend to the OwnID server in response to a /getSessionByLoginId request. Since your backend determines what object was sent to the OwnID server, your frontend code knows what object it is receiving from the onLogin function. This object can be a JWT, but can also be an arbitrary string generated by your backend. In addition to setting the session, the onLogin function should redirect the user to an authorized page (e.g., a profile page).

Login Diagram

Add the following code to render the login widget:

Add references to elements and define the widget's onLogin function:

function LoginComponent() {
const emailField = useRef(null);
const passwordField = useRef(null);
function onLogin(data) {
//setting user session
localStorage.setItem('data', JSON.stringify({ token: data.token }));
//redirecting user to the account page
window.location.href = '/account';
}
return (
<form>
<input ref={emailField} type="email" name="email" />
<input ref={passwordField} type="password" name="password" />
<button type="submit">Log In</button>
<OwnID type='login'
passwordField={passwordField}
loginIdField={emailField}
onError={(error) => console.error(error)}
onLogin={onLogin} />
</form>
);
}

Customizing the UI#

The default code added to initialize the OwnID SDK has a single parameter: appId. In addition to this required parameter, there are optional parameters that can be added to the initialization code to modify the look of the OwnID button. Each parameter is added to the ownid function of the initialization code as a key/value pair of the JSON object. See how to customize the OwnID widget in this page.

Security#

IP allowlisting#

OwnID enforces IP allowlisting to make server-to-server calls to your servers. In order to allowlist OwnID calls, please consider the following IP addresses:

  • 18.213.107.140
  • 35.175.77.229

Validating Signatures#

Requests from the OwnID server include two headers that can be used to ensure the request has not been tampered with. The first one, ownid-signature, is a hash value that the OwnID server generates from a timestamp and the body of the request. The second one, ownid-timestamp can be used by your backend to calculate the signature that is based on the timestamp and request body, and then compare the result to the value of ownid-signature. If both signatures do not match, the request has been altered.

Because the signatures are generated using an HMAC with the SHA256 hash function, the OwnID server and your backend must use the same cryptographic key when calculating the hash value. You can obtain this key from the OwnID Console, and then add the code generates a hash and compares it to the signature in your backend.

Obtaining the HMAC Key

Before the backend can generate the HMACSHA256 value, you must obtain the secret cryptographic key used in the calculation. Simply open your OwnID application in the OwnID Console and copy the value from MyApp > Shared Secret.

Request Verification

Now that you have the cryptographic key, the backend can verify requests by generating each request's expected signature and compare it to the one generated by the OwnID server. The backend code must:

  • Step 1: Extract the ownid-signature and ownid-timestamp headers from the request. These headers provide the HMAC code generated by the OwnID server and the timestamp it used to generate it.

  • Step 2: Create the data string that will be used as an input to the hash function. To create it you need to concatenate:

    • The request body (in a JSON string format)
    • The character .
    • The timestamp (from the ownid-timestamp header)
  • Step 3: Use HMAC with SHA256 to calculate a hashed value from the body-timestamp data string. The cryptographic key used in the calculation is the shared secret for your OwnID application.

  • Step 4: Compare the hash value generated by your backend with the signature extracted from the ownid-signature header.

The following code snippets show how the backend might accomplish these steps:

const crypto = require('crypto');
let key = "<your-shared-secret>"; // This is the shared secret from OwnID Console
let keyBuffer = Buffer.from(key, 'base64');
let body = JSON.stringify(req.body);
let ownIdSignature = req.headers['ownid-signature'];
let ownIdTimestamp = req.headers['ownid-timestamp'];
let dataToSign = `${body}.${ownIdTimestamp}`;
const hmac = crypto.createHmac('sha256', keyBuffer);
hmac.update(dataToSign);
let signature = hmac.digest('base64');
if (signature !== ownIdSignature) {
// The request has been tampered with
}

Using Content-Security-Policy#

When implementing Content-Security-Policy response headers in your site, you must be sure to include *.ownid.com in the rule definitions for specific policies for them to function properly.

An example rule set would look similar to the following:

<meta http-equiv="content-security-policy" content="script-src 'unsafe-eval' 'self' *.ownid.com;">