Notification based flows
Notification based flows are flows that rely on the push notification mechanism to notify the Smart-ID app. In these flows, the RP website or app shows a verification code (VC) which the user must compare to the VC displayed on Smart-ID to match the authentication or signature session.
To force the user to perform the verification, the RP can request the Smart-ID app to display three VCs (one correct code and two random codes) and the user is required to select the correct code which is displayed by the RP website or app. In case the user doesn’t choose the correct code, the Smart-ID app aborts the request.
This can be requested in the Smart-ID RP API call using the confirmationMessageAndVerificationCodeChoice
interaction type. For details of the interactions
object, see Interactions page.
NOTE
In case of same-device use cases where the recommended App2App or Web2App device link flow cannot be used for some reason, then in notification flows it is recommended to include a small delay after showing the verification code to user and before starting the transaction on the RP API. This way, the user has additional time to look at the verification code on the RP’s app before the push notification arrives. |
Below, sequence diagrams are given along with explanations of the differences.
Flow diagrams
The following diagram describes the notification based flow for the authentication session.
The following sequence diagram illustrates the notification based signing flow.
The following sequence diagram illustrates the device link based certificate choice flow being linked to a notification based signing flow.
Signature protocols
There are separate signing protocols for authentication and signature requests (see signature protocols for details).
Verification codes for authentication requests
In RP API v3 notification based authentication requests, RP must compute verification codes (VC) for each authentication request, so the user can bind together the session on the browser or RP app and the authentication request on the Smart-ID app.
The VC is computed as follows:
Calculate SHA-256
from the rpChallenge
, extract 2 rightmost bytes from the result, interpret them as a big-endian unsigned integer and take the last 4 digits in decimal form for display. SHA-256
must be used to calculate the VC.
Please keep in mind that the rpChallenge is the real rpChallenge byte value (for example, the byte array returned from the SecureRandom()
or equivalent method), not the Base64 encoded form used for transport or the popular hexadecimal representation.
The VC value must be displayed to the user in the browser together with a message asking the end user to verify the code matches with the one displayed on their mobile device. The user must not proceed if these don’t match.
integer(SHA-256(rpChallenge)[-2:-1]) mod 10000
-
Java
-
PHP
-
Python
-
Go
-
Rust
-
Kotlin
-
C#
-
Node.js
-
Ruby
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
public class NotificationVerificationCode {
public static void main(String[] args) throws Exception {
String rpChallengeBase64 = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==";
byte[] rpChallengeBytes = Base64.getDecoder().decode(
rpChallengeBase64
);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(rpChallengeBytes);
byte[] sha256Hash = md.digest();
BigInteger LAST_2_BYTES_BITMASK = new BigInteger("65535");
BigInteger result = new BigInteger(sha256Hash)
.and(LAST_2_BYTES_BITMASK)
.mod(new BigInteger("10000"));
String verificationCode = String.format("%04d", result);
System.out.println(verificationCode);
}
}
<?php
$rpChallengeBase64 = base64_decode('GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==');
$sha256Hash = hash('sha256', $rpChallengeBase64, true);
$result = hexdec(bin2hex(substr($sha256Hash, -2))) % 10000;
$verificationCode = sprintf('%04d', $result);
echo $verificationCode;
?>
import base64
import hashlib
rp_challenge_base64 = b"GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg=="
rp_challenge_bytes = base64.b64decode(rp_challenge_base64)
sha256_hash = hashlib.sha256(rp_challenge_bytes).digest()
result = int.from_bytes(sha256_hash[-2:], byteorder='big') % 10000
verification_code = f"{result:04d}"
print(verification_code)
package main
import (
"crypto/sha256"
"encoding/base64"
"fmt"
)
func main() {
var rpChallengeBase64 string = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg=="
var rpChallengeBytes, err = base64.StdEncoding.DecodeString(rpChallengeBase64)
if err != nil {
panic(err)
}
var sha256Hash [32]byte = sha256.Sum256(rpChallengeBytes)
var result int = int(sha256Hash[len(sha256Hash)-2])<<8 + int(sha256Hash[len(sha256Hash)-1])
result %= 10000
var verificationCode string = fmt.Sprintf("%04d", result)
fmt.Printf("%s\n",verificationCode)
}
use base64::decode;
use sha2::{Sha256, Digest};
fn main() {
let rp_challenge_base64: &str = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==";
let rp_challenge_bytes = decode(rp_challenge_base64).unwrap();
let sha256_hash = Sha256::digest(&rp_challenge_bytes);
let result = (((sha256_hash[30] as u16) << 8) + (sha256_hash[31] as u16)) % 10000;
let verification_code: String = format!("{:04}", result);
println!("{}", verification_code);
}
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.util.Base64
fun main() {
val rpChallengeBase64: String = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg=="
val rpChallengeBytes: ByteArray = Base64.getDecoder().decode(rpChallengeBase64)
val sha256Hash: ByteArray = MessageDigest
.getInstance("SHA-256")
.digest(rpChallengeBytes)
val LAST_2_BYTES_BITMASK: BigInteger = BigInteger("65535")
val result: BigInteger = BigInteger(sha256Hash)
.and(LAST_2_BYTES_BITMASK)
.mod(BigInteger("10000"))
val verificationCode = "%04d".format(result)
println(verificationCode)
}
using System;
using System.Security.Cryptography;
public class NotificationVerificationCode
{
public static void Main(string[] args)
{
string rpChallengeBase64 = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==";
byte[] rpChallengeBytes = Convert.FromBase64String(rpChallengeBase64);
using (SHA256 sha256 = SHA256.Create())
{
byte[] sha256Hash = sha256.ComputeHash(rpChallengeBytes);
int result = ((sha256Hash[30] << 8) + (sha256Hash[31])) % 10000;
string verificationCode = result.ToString("D4");
Console.WriteLine(verificationCode);
}
}
}
const crypto = require('crypto');
const rpChallenge = Buffer.from('GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg==', 'base64');
const sha256 = crypto.createHash('sha256');
sha256.update(rpChallenge);
const sha256Hash = sha256.digest();
const result = sha256Hash.readUInt16BE(30) % 10000
.toString()
.padStart(4, '0');
console.log(result);
require 'base64'
require 'digest'
rp_challenge = "GYS+yoah6emAcVDNIajwSs6UB/M95XrDxMzXBUkwQJ9YFDipXXzGpPc7raWcuc2+TEoRc7WvIZ/7dU/iRXenYg=="
sha256_hash = Digest::SHA256.digest(Base64.decode64(rp_challenge))
last_2_bytes = sha256_hash[-2..-1].unpack('n*').first
result = (last_2_bytes % 10_000).to_s.rjust(4, '0')
puts result
7180
Verification codes for signature requests
In RP API v3 notification based signature requests, verification codes (VC) are not generated by RP based on the DTBS (data-to-be-signed), but are returned from the server to RP for security reasons. Verification codes are only relevant for the notification based flows.
Currently, there is only one supported verification code type:
-
numeric4
The returned verification code consists of 4 numbers. It must be shown to the user as it is received from the server.