Skip to main content

As independent second-factor

You can use Visual Passcodes to add a strong visual second factor of authentication to an existing login flow where the user has already been authenticated with a primary factor (e.g., username and password).

General Description

This flow is initiated by including the custom scope 2fa in the authorization request. The Visual Passcodes authentication process then acts as the second factor. In this flow, the server will only return an id_token.

Integration Steps

  1. First Factor Authentication: First, authenticate the user with your existing primary method (e.g., username/password). This establishes the user's initial identity.
  2. Construct 2FA Authorization URL: After the first factor is successful, redirect the user's browser to the Visual Passcodes Authorization Endpoint. The request must include:
    • scope: openid 2fa.
    • login_hint: The username or unique identifier of the user who just completed the first factor. This is strongly recommended as it pre-populates Visual Passcodes' context for the authentication challenge.
    • All other required OIDC parameters (client_id, redirect_uri, state, nonce, and PKCE parameters even if it's a confidential client).
  3. Handle the Visual Passcodes Callback: The user completes the Visual Auth challenge at Visual Passcodes and is redirected back to your redirect_uri with an authorization_code.
  4. Exchange Code for ID Token: Your application's backend exchanges this authorization_code at the Visual Passcodes Token Endpoint, just as in a regular flow.
  5. Receive ID Token (Only): Visual Passcodes will validate the request and respond with a JSON object containing only an id_token. Confirm that access_token and refresh_token are absent.
  6. Validate ID Token: Your backend must validate the id_token's signature using Visual Passcodes' public key from the JWKS Endpoint. You must also validate the nonce to prevent replay attacks.
  7. Complete Login: Once the id_token is successfully validated, your application can consider the user fully authenticated with 2FA and proceed with establishing their final session within your system.

Prerequisites

  • Client Registration: The 3rd party developer must register their application with the OpenID Connect provider. This involves providing application details (name, description, etc.) and specifying a redirect URI. The server will issue a client_id and a client_secret.
  • PKCE Implementation: The 3rd party application must implement PKCE.
  • User already authenticated: We assume that the user has been already authenticated with a first factor (e.g., username + Password). That way, the identity of the user (e.g., username) is already known and the second factor is based on this identity.

Authorization Code Flow with PKCE

Authorization Request with 2fa Scope

The 3rd party client application (belonging to the external party) initiates the second factor authorization code flow by redirecting the user to Image-based authentication Identify Provider (Visual Passcodes) server's authorization endpoint. In this request, the scope parameter includes the custom 2fa scope, in addition to other standard scopes like openid. For example:

GET /oidc/authorize/{tenant_id}?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&scope=openid%202fa&code_challenge={code_challenge}&code_challenge_method=S256&nonce={nonce}&state={state}&login_hint={login_hint}&email_hint={login_hint}

The endpoint call also includes the user identifier (username) of the user that needs to be authenticated with the image.

Processing by the Authorization Server

Upon receiving the authorization request with the 2fa scope, Visual Passcodes server's authorization server recognizes that 2FA is required for the external party's user. The server then performs the following steps:

  • Authentication (Treated as 2FA): In this specific implementation, the standard user authentication process at Visual Passcodes server is treated as the de facto second factor of authentication for the external party. The server initiates the authentication process, forcing the external party's user to log in using credentials recognized by this server. This login process serves as the 2FA step.
  • 2FA Challenge: Since the primary authentication is the 2FA, there is no separate 2FA challenge step in this flow.
  • 2FA Verification: The user provides their login credentials. This server verifies these credentials as part of its standard authentication process, effectively fulfilling the 2FA requirement.
  • Token Issuance:
    • If the authentication is successful, this server proceeds to issue an ID token.
    • Critically, in this specific 2FA flow, this server is configured to only return an ID token. This server omits the refresh token and the access token in the response. This is a key characteristic of this 2FA implementation.

Token Response

This server sends the ID token back to the client application (belonging to the external party) via the redirect URI, as defined in the authorization code flow.

HTTP/1.1 302 Found
Location: {redirect_uri}?code={authorization_code}&state={state}

{
"id_token": "{id_token_value}",
"token_type": "Bearer",
"expires_in": 3600
}
  • Important: Note the absence of refresh_token and access_token in the response.

Client Application Handling

The client application (of the external party) receives the ID token.

  • The client application uses the ID token to obtain information about the user, and to confirm that the user has successfully authenticated with 2FA.
  • Since no refresh token is provided, the client application will need to re-authenticate the user (and they will have to go through the 2FA process again with this server) once the ID token expires.
Security Recommendation
  • It is strongly recommended that the client application perform thorough validation of the response received from this server in its backend. This is crucial for maintaining the security of the authentication process.
  • Specifically, the client application should validate the nonce parameter present in the ID token against the nonce that was included in the original authorization request. This validation is essential to mitigate replay attacks, where an attacker might attempt to use a previously obtained, valid ID token.
  • The client application should validate the ID token signature using the server public key. The server uses EdDSA to sign the tokens.

Endpoint Implementation Details

To implement this 2FA flow, the external party's application needs to implement two key endpoints:

Authorization Request Endpoint

  • This endpoint is responsible for initiating the OpenID Connect authorization code flow with this server.
  • The external party's backend (acting as a confidential client) constructs the authorization URL, including the necessary parameters:
    • response_type: Set to code.
    • client_id: The client ID of the external party's application, as registered with this server.
    • redirect_uri: A URI under the control of the external party's application, where this server will redirect the user after authentication.
    • scope: Must include openid and the custom 2fa scope (e.g., openid 2fa).
    • code_challenge and code_challenge_method: To enable PKCE (Proof Key for Code Exchange), the client generates a code_verifier, transforms it into a code_challenge, and includes the code_challenge and code_challenge_method set to S256 in the request.
    • nonce: Include a unique value to mitigate replay attacks.
    • state: Include a unique value to maintain state between the request and callback.
    • login_hint: Strongly recommended: Include the username of the user being authenticated. This binds the authentication request to a specific user.
  • The external party's backend then redirects the user's browser to this constructed URL at this server's authorization endpoint.

Callback Endpoint

  • This endpoint is the redirect_uri specified in the authorization request. This server redirects the user's browser to this endpoint after the user has authenticated (and authorized the application).
  • This server includes the authorization code and the state in the query parameters of the redirect URL.
  • The external party's backend at this endpoint:
    • Retrieves the code and the state from the query parameters.
    • Important: Verify that the state parameter matches the state parameter from the original authorization request. This is crucial for preventing CSRF attacks.
    • Sends a POST request to this server's token endpoint to exchange the code for an ID token. This request MUST include:
      • grant_type: Set to authorization_code.
      • code: The authorization code received from this server.
      • redirect_uri: The same redirect_uri used in the authorization request.
      • client_id: The client ID of the external party's application.
      • client_secret: The client secret for the external party's application (this is why this exchange must happen on the backend, not in the user's browser).
      • code_verifier: The value generated in the Authorization Request Endpoint.
    • This server's token endpoint validates the request (including the code_verifier if PKCE was used) and, upon successful validation, returns an ID token.
    • The external party's backend then validates the ID token:
      • Verifies the signature of the ID token.
      • Validates the claims in the ID token, including issuer, audience, expiration, and nonce. The nonce value MUST match the one sent in the original authorization request.
    • If the ID token is valid, the user is considered successfully authenticated with 2FA.

State Parameter Usage

  • The state parameter is crucial for security and data integrity in the authorization code flow. It allows the external party's application to:
    • Prevent CSRF Attacks: By including a unique, unpredictable value in the authorization request and verifying it in the callback, the application can ensure that the user is not being tricked into authorizing an unintended request.
    • Correlate Request and Response: The state parameter links the authorization request with the corresponding callback, ensuring that the received authorization code is associated with the original request.
  • In the context of this 2FA implementation, the external party's application should use the state parameter to bind it with other session-specific data, such as:
    • The nonce value.
    • The code_verifier value.
    • Any other data relevant to the external party's 2FA handling.
  • This binding can be achieved by:
    • Creating a server-side session or storage associated with the state value.
    • Storing the nonce, code_verifier, and other data in this session or storage, indexed by the state value.
  • Upon receiving the callback from this server, the external party's application retrieves the data associated with the state parameter and uses it to validate the response and proceed with the 2FA flow.