Realtid API (SignalR)

SignalR ger realtidsuppdateringar för autentisering och signering. Perfekt för webbapplikationer som vill visa QR-koder och statusändringar utan polling.

Säker arkitektur
Sessioner startas via REST API (med API-nyckel). SignalR används endast för att prenumerera på uppdateringar med en engångstoken.

Säkerhetsmodell

SignalR-hubben kräver en prenumerationstoken för att förhindra missbruk:

Steg Vem Beskrivning
1 Backend Anropa REST API med API-nyckel → får sessionId + subscriptionToken
2 Backend Skicka sessionId och subscriptionToken till frontend
3 Frontend Anslut till SignalR-hubben
4 Frontend Anropa Subscribe(sessionId, subscriptionToken)
5 Server Validerar token (engångs, 5 min giltig), startar polling, skickar events

Översikt

SignalR-hubben erbjuder:

  • Automatisk QR-kodsuppdatering - Ny QR-kod skickas varje sekund
  • Statusändringar i realtid - Inga polling-anrop behövs
  • WCAG-stöd - Tidsgränser och möjlighet att förlänga session
  • Automatisk orderregenerering - Nya orders var 25:e sekund

Komplett flöde

1. Backend: Starta session via REST API

// Backend (C#) - skyddad av API-nyckel
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "YOUR_API_KEY");

var response = await client.PostAsJsonAsync(
    "https://id.tic.io/api/v1/auth/bankid/start",
    new { endUserIp = userIp });

var result = await response.Content.ReadFromJsonAsync<AuthStartResponse>();

// Returnera till frontend
return new {
    sessionId = result.SessionId,
    subscriptionToken = result.SubscriptionToken,
    autoStartToken = result.AutoStartToken
};

2. Frontend: Anslut och prenumerera

import * as signalR from '@microsoft/signalr';

// Hämta session från din backend
const { sessionId, subscriptionToken, autoStartToken } = await fetch('/api/start-auth')
    .then(r => r.json());

// Anslut till SignalR
const connection = new signalR.HubConnectionBuilder()
    .withUrl('https://id.tic.io/hub/auth')
    .withAutomaticReconnect()
    .build();

// Registrera event handlers INNAN anslutning
connection.on('OnQRCode', qrData => renderQrCode(qrData));
connection.on('OnStatusChanged', data => showStatus(data.message));
connection.on('OnCompleted', data => handleSuccess(data));
connection.on('OnFailed', (code, data) => handleError(data.message));

// Anslut
await connection.start();

// Prenumerera med token (engångs, kan ej återanvändas)
const subscribeResult = await connection.invoke('Subscribe', sessionId, subscriptionToken);
console.log('Prenumererar på session:', subscribeResult.sessionId);

Subscribe

Prenumerera på realtidsuppdateringar för en session.

Metodsignatur

Subscribe(sessionId: Guid, subscriptionToken: string) → SubscribeResponse

Parametrar

Parameter Typ Beskrivning
sessionId Required Guid Session-ID från REST API-svaret
subscriptionToken Required string Prenumerationstoken från REST API-svaret. Engångs, giltig i 5 minuter.

Response

{
    "sessionId": "550e8400-e29b-41d4-a716-446655440000",
    "provider": "bankid",
    "status": "pending",
    "autoStartToken": "7c40b5c9-a3de-4e...",
    "sessionExpiresAt": "2026-01-31T12:05:00Z"
}

Övriga metoder

Metod Beskrivning Returvärde
Cancel() Avbryter pågående session void
GetQRCode() Hämtar aktuell QR-kodsdata (användbart om du missar en uppdatering) string?
ExtendSession() Förlänger sessionens tidsgräns (WCAG). Kan endast anropas en gång per session. { success, error?, newExpiresAt? }
GetStatus() Hämtar aktuell sessionsstatus AuthStatusResponse?

Events (Server → Klient)

Servern skickar dessa events automatiskt efter Subscribe.

// QR-kod uppdateras var 2:a sekund
connection.on('OnQRCode', (qrData) => {
    QRCode.toCanvas(document.getElementById('qr-canvas'), qrData);
});

// Status ändras
connection.on('OnStatusChanged', (data) => {
    document.getElementById('status').textContent = data.message;
});

// Autentisering/signering slutförd
connection.on('OnCompleted', (data) => {
    console.log('Användare:', data.user.givenName, data.user.surname);
    console.log('Personnummer:', data.user.personalNumber);
});

// Misslyckades
connection.on('OnFailed', (hintCode, data) => {
    document.getElementById('error').textContent = data.message;
});

// Avbruten
connection.on('OnCancelled', () => {
    console.log('Session avbruten');
});

// Timeout-varning (60 sekunder kvar)
connection.on('OnTimeoutWarning', (data) => {
    if (data.canExtend) {
        showExtendDialog(data.secondsRemaining);
    }
});

// Order regenererad (var 25:e sekund)
connection.on('OnOrderRegenerated', (data) => {
    console.log(`Order ${data.orderCount}/${data.maxOrders}`);
});

Event-referens

Event Data Beskrivning
OnQRCode string qrData Ny QR-kodsdata. Uppdateras var 2:a sekund.
OnStatusChanged { status, hintCode, message, messageEn } Statusändring med användarmeddelande.
OnCompleted { sessionId, status, user, completedAt } Session slutförd. user innehåller personnummer, namn.
OnFailed hintCode, { message, messageEn } Session misslyckades.
OnCancelled - Session avbruten av användaren.
OnTimeoutWarning { secondsRemaining, canExtend } 60 sekunder kvar (WCAG-krav).
OnOrderRegenerated { orderCount, maxOrders, sessionExpiresInSeconds } Ny BankID-order skapad.

Mobilt flöde (samma enhet)

För autentisering på samma enhet, använd autoStartToken för att öppna BankID-appen.

// autoStartToken kommer från REST API-svaret (via din backend)
const bankIdUrl = `bankid:///?autostarttoken=${autoStartToken}&redirect=null`;

// Öppna BankID-appen
window.location.href = bankIdUrl;

// Events fortsätter att skickas via SignalR

Felhantering

Fel Orsak
Invalid or expired subscription token Token har redan använts eller gått ut (5 min)
Subscription token does not match session ID Token tillhör en annan session
Session not found Sessionen finns inte
Session is not pending Sessionen är redan slutförd eller misslyckad
try {
    await connection.invoke('Subscribe', sessionId, subscriptionToken);
} catch (error) {
    if (error.message.includes('expired')) {
        // Token har gått ut - starta ny session via backend
        const newSession = await fetch('/api/start-auth').then(r => r.json());
        await connection.invoke('Subscribe', newSession.sessionId, newSession.subscriptionToken);
    }
}

Anslutningshantering

WS wss://id.tic.io/hub/auth
const connection = new signalR.HubConnectionBuilder()
    .withUrl('https://id.tic.io/hub/auth')
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Warning)
    .build();

connection.onreconnecting(error => {
    console.log('Återansluter...', error);
});

connection.onreconnected(connectionId => {
    console.log('Återansluten:', connectionId);
    // OBS: Du måste prenumerera igen med en NY token
});

connection.onclose(error => {
    console.log('Anslutning stängd', error);
});
Vid återanslutning
Prenumerationstoken är engångs. Vid återanslutning måste du hämta en ny token från din backend och anropa Subscribe igen.