Authentication Flow: Javascript Library

This guide provides detailed instructions for using the Authentication Flow on a server with our Javascript library.

Table of content

1.0 Installation

Create a file .npmrc in your project root directory and add the following line:

registry=https://npm.pantherx.org

Install the library with npm or pnpm:

npm install @ones.dev/common
npm install @ones.dev/backend-modules

Important: To use deviceIdentitySigningFunction you need to register your device with IDP first, and make sure that px-device-identity-service is running. See here.

2.0 Device Authentication

Access px-device-identity service to sign the device JWT.

import { AuthJwtProperties, TokenIntrospectionResponse, unixTimeInSeconds } from '@ones.dev/common'
import { DeviceAuthentication, deviceIdentitySigningFunction, loadSystemConfig } from '@ones.dev/backend-modules'

// Load system config; defaults to /etc/px-device-identity/device.yml
const {
    deviceId,
    clientId,
    authServerUrl
} = loadSystemConfig()

// Initialize the device authentication
const auth = new DeviceAuthentication({
    name: 'device-auth',
    idpBaseUrl: authServerUrl,
    deviceId: deviceId,
    deviceClientId: clientId,
    functions: {
        // Sign using px-device-identity service
        signingFunction: deviceIdentitySigningFunction
    },
    deviceIdentityUrl: "http://127.0.0.1:8000/sign",
    authJwtProperties: new AuthJwtProperties({
        scope: 'openid offline_access ones',
        bindingMessage: `Authorize login to Example.com`,
        resources: ['https://example.com'],
    }),
})

// Get the device JWT
const deviceJwt = await auth.makeDeviceJwt()

// Get the device access token
const { access_token, expires_in, token_type } = await auth.makeDeviceAccessToken()

// Do access token introspection
const { active, sub, ... } = await auth.tokenIntrospection(access_token)

3.0 CIBA

Implement CIBA flow on server side, for OnesID users to authenticate with their username (OnesID).

3.1 Make CIBA Request

On your backend, make a CIBA request to authenticate the user.

// Reuse the device authentication from the previous example
const auth = new DeviceAuthentication({ ... })

// Make the CIBA request
const {
    auth_req_id,
    experies_in,
    interval,
} = await auth.makeCibaRequest({
    loginHint: {
        kind: loginHintKind,
        value: userIdentifier,
    }
})

3.1.1 Example

On a NestJS backend, this looks something like this:

@Public()
@Post('/api/auth/oidc/bc-authorize')
async cibaLoginRequest(@Body() body: CibaAuthRequestFrontendDto): Promise<CibaAuthRequestResponseDto> {
    return auth.makeCibaRequest({...})
}

3.1.2 Frontend usage

If your CIBA request endpoint is /api/auth/oidc/bc-authorize, then you can use the following code on the frontend:

import { makeCibaRequestClient } from '@ones.dev/common'

const host = 'https://example.com'
const userIdentifier = 'user.name@OnesID1'
const { auth_req_id, expires_in, interval } = await makeCibaRequestClient(host, userIdentifier)

3.2 Check CIBA Status

Poll the CIBA status in 3s interval until it is approved, or denied.

// Reuse the device authentication from the previous example
const auth = new DeviceAuthentication({ ... })

try {
    const result = await auth.checkCibaStatus(auth_req_id)
    return result
} catch (err) {
    const axiosErr = err as AxiosError
    // Usually indicates that the request is pending
    if (axiosErr?.response?.status === 400) {
        throw new BadRequestException(axiosErr.response.data)
    } else {
        throw new NotFoundException()
    }
}

// Validate returned JWT (access_token, id_token)
const {
    sub,
    iss,
    aud,
    exp,
    iat,
    ...
} = await auth.validateJwt(access_token)

3.2.1 Example

On a NestJS backend, this looks something like this:

@Public()
@Post('/api/auth/oidc/token')
async tokenEndpoint(
    @Body() body: CibaAuthStatusRequestFrontendDto,
): Promise<CibaAuthStatusResponseDto> {
    try {
        const result = await this.service.cibaStatus(body.authRequestId)
        return result
    } catch (err) {
        const axiosErr = err as AxiosError
        this.logger.error(axiosErr)
        if (axiosErr?.response?.status === 400) {
            throw new BadRequestException(axiosErr.response.data)
        } else {
            this.logger.error(axiosErr?.response)
            throw new NotFoundException()
        }
    }
}

3.2.2 Frontend usage

If your CIBA status endpoint is /api/auth/oidc/token, then you can use the following code on the frontend:

import { checkCibaStatusClient } from '@ones.dev/common'

const host = 'https://example.com'
const authReqId = '...'
const { access_token, id_token, expires_in, token_type } = await checkCibaStatusClient(host, authReqId)

3.3 Refresh Access Token

// Reuse the device authentication from the previous example
const auth = new DeviceAuthentication({ ... })

const refreshResult = await auth.refreshAccessToken(reqBody.refresh_token)

4.0 QR

Implement QR flow on server side, for OnesID users to authenticate using a QR code.

Available from v0.17.102.

Here’s what this looks like:

  1. Backend: Generate a new QR session
  2. Frontend: Show a QR code to the user
  3. OnesID application: User scans QR code
  4. Backend: Generate a new CIBA request from callback -> Standard CIBA flow
    • This is automated, as long as you use DeviceAuthentication

4.1 Make QR Request

On your backend, create a QR auth session from frontend request (GET):

// Reuse the device authentication from the previous example
const auth = new DeviceAuthentication({ ... })

// Capture IPv4 from where the request originated
// Example: You want to show a QR code to a user in the browser, and capture the user IP address on your server endpoint
const originOriginIPv4 = req.headers['x-forwarded-for'] || req.socket.remoteAddress

// Create a QR auth session on your server
const {
    cbUrl,
    sessionId,
    expIn,
    exp,
    interval
} = await auth.makeQrAuthSessionIdp()

return {
    cbUrl,
    sessionId,
    exp,
    interval
}

4.1.1 Example

On a NestJS backend, this might look like this:

@Public()
@Post('/api/auth/qr/make')
async makeQrAuthSession(): Promise<QrAuthSessionIdp> {
    return auth.makeQrAuthSessionIdp()
}

4.1.2 Frontend usage

If your QR session endpoint is /api/auth/qr/make, then you can use the following code on the frontend:

import { makeQrSessionClientV2 } from '@ones.dev/common'

const host = 'https://example.com'
const { sessionId, cbUrl, expIn } = await makeQrSessionClientV2(host)

4.2 Generate QR code from QR Request

On the frontend, generate a QR code and show it to the user:

// Generate a QR code and show it to the user
const newQrCode: AuthenticationQRCodeContent {
    cbUrl: session.cbUrl,
    sessionId: session.sessionId,
}

4.3 Poll QR Session Status

On your backend, start polling the QR session status, and wait for the user to approve the request:

// Start polling the session status
const {
    sessionId,
    exp,
    // once authRequestId is set, you can switch to the CIBA flow
    authRequestId
} = await auth.verifyQrAuthSessionIdp(sessionId)

Once the authRequestId (auth_req_id) is set, you should return it to your frontend, and switch to the CIBA flow: 3.2 Check CIBA Status.

Key points:

  • The QR session exists only, to learn about the encoded user identifier, to start a CIBA request
  • The QR session is deleted once the user has approved the request
  • As soon as your receive the authRequestId (set by makeCibaRequestWithOptionalQrSession), you can switch to the CIBA flow

4.4.1 Example

On a NestJS backend, this might look like this:

@Public()
@Post('/api/auth/qr/status')
async verifyQrAuthSession(
    @Body() body: {
        sessionId: string
    },
): Promise<QrAuthSessionIdp> {
    const qrSession = await auth.verifyQrAuthSessionIdp({
        sessionId: body.sessionId,
        ...
    })
    if (!qrSession) {
        throw new NotFoundException('Session not found')
    }
    return qrSession
}

4.4.2 Frontend usage

If your QR session endpoint is /api/auth/qr/status, then you can use the following code on the frontend:

import { qrSessionStatusClientV2 } from '@ones.dev/common'

const host = 'https://example.com'
const sessionId = '...'
const { authRequestId } = await qrSessionStatusClientV2(
    host,
    sessionId
)

Request user consent for personal data.

Available from v0.17.88.

5.1 Supported properties

You may request any of the following properties:

import { PERSONAL_DATA_CONSENT_PROPERTIES } from "@ones.dev/common"

export enum PERSONAL_DATA_CONSENT_PROPERTIES {
    FIRST_NAME = 'firstName',
    LAST_NAME = 'lastName',
    LOCALIZED_FIRST_NAME = 'localizedFirstName',
    LOCALIZED_LAST_NAME = 'localizedLastName',
    IDENTITY_DOCUMENT_NUMBER = 'identityDocumentNumber',
    IDENTITY_DOCUMENT_ISSUE_DATE = 'identityDocumentIssueDate',
    IDENTITY_DOCUMENT_EXPIRY_DATE = 'identityDocumentExpiryDate',
    DATE_OF_BIRTH = 'dateOfBirth',
    EMAIL = 'email',
    PHONE_NUMBER = 'phoneNumber',
    LOCALIZED_FULL_NAME = 'localizedFullName',
    FULL_NAME = 'fullName',
}

Make a consent request to obtain user’s personal data.

// Initialize, or re-use the device authentication
const auth = new DeviceAuthentication({ ... })

// Obtain the subject from the JWT via auth.validateJwt(jwt), sub field
const subject = 'UUID of target user'
const resource = 'https://example.com'
const content = [
    {
        name: PERSONAL_DATA_CONSENT_PROPERTIES.EMAIL,
    },
    {
        name: PERSONAL_DATA_CONSENT_PROPERTIES.PHONE_NUMBER,
    }
]        
const reason = 'Need email and phone number'

const request = await auth.makeConsentRequest(
    subject,
    resource,
    content,
    reason
)

At this point the user should approve the consent request on OnesID mobile application.

Poll the consent status in 3s interval until it is approved, or denied.

// Initialize, or re-use the device authentication
const auth = new DeviceAuthentication({ ... })

const {
    status,
    content,
} = await auth.checkConsentStatus(request.id)

If your application relies on the consent flow, use the following approach:

  1. Complete CIBA flow (3.1, 3.2)
  2. Extract the subject from the JWT via auth.validateJwt(access_token), sub field
  3. Make consent request (5.2) and indicate the properties you need
  4. Poll the consent status (5.3)
    • If approved, requested properties are available in content
    • If denied, abort the flow

Keep in mind, that the consent flow involves an additional step for the user, and should be used only for signup, when the user explicitly requests it, or in rare cases, to update the user’s profile.

Contact

Found a problem, or have a question related to Authentication Flow: Javascript Library?

ONES Now Documentation

© 2025 ONES Now Documentation | Author Franz Geffke