Autentica con Immersive tramite codice: password, MFA e chiavi API
- Tipo non definito
Documentation
Questa guida spiega come un'applicazione chiama direttamente le interfacce di autenticazione RestFrontage, ottenendo un Token immersivo e lo utilizza con WebAPI e OData. Gli esempi non richiedono DLL Immersive.
Varianti HTTP, C#, JavaScript, TypeScript, Python e PowerShell coprono applicazioni .NET, interfacce web, backend e script di exploit. Gli esempi HTTP mostrano il contratto; altre varianti eseguono gli stessi scambi.
In questo articolo
1. Scegli la modalità giusta
| Bisogno | Moda | Risultato |
|---|---|---|
| Effettua l'accesso a un utente quando la sua password è sufficiente secondo la policy del server. | POST /api/Security/Users/Authenticate | Un oggetto che contiene Gettone, oppure un rifiuto se è richiesto un MFA. |
| Accedi a un utente con supporto MFA e la sua prima configurazione. | POST /api/Security/Mfa/VerifyCredentials, poi VerifyTotp se richiesto. | Così sia Gettone Immediatamente, un passo CeremoniToken da completare prima di ottenere il Token. |
| Chiama un'API da un servizio o da un'automazione. | Titoli x-app-name e x-api-key. | La chiamata viene autenticata direttamente; la risposta rimane quella dell'API chiamata. |
| Avere RestFrontage display login e MFA, senza creare queste schermate. | Browser/Autorizzazione e Browser/Token, con PKCE. | Un token dopo il viaggio nel browser; questa modalità è l'argomento dell'articolo dedicato all'interfaccia di connessione ospitata. |
Una chiave API rappresenta l'applicazione con I diritti del conto ad essa associato. Può essere utilizzato senza password o TOTP. Non concede automaticamente diritti di amministratore.
Per un'app utilizzata da una persona, il viaggio ospitato evita di implementare il modulo password, il codice QR e l'input MFA. Questa guida copre le chiamate dirette quando la tua app supporta queste interazioni da sola.
flowchart TD
A{"Type de connexion ?"}
A -->|Clé API| B["Envoyer x-app-name et x-api-key à chaque appel"]
B --> C["Appeler les WebAPI ou OData"]
A -->|Compte utilisateur| D["VerifyCredentials : vérifier l'identifiant et le mot de passe"]
D --> E{"Réponse du serveur"}
E -->|authenticated| F["Token disponible"]
E -->|totp_required| G["Saisir le code du téléphone"]
E -->|totp_enrollment_required| H["Configurer l'application TOTP avec le QR code"]
H --> G
G --> I["VerifyTotp : vérifier le code"]
I -->|Code accepté| F
I -->|Code incorrect, nouvel essai autorisé| G
F --> J["Envoyer Authorization: Bearer TOKEN à chaque appel"]
J --> C
2. Preparazione alle chiamate
2.1 Indirizzo, HTTPS e identità
Fai affidamento, ad esempio, all'origine HTTPS di RestFrontage https://localhost:44333 oppure https://rest.exemple.fr sulla tua rete. Il certificato deve essere riconosciuto dal cliente.
La password e la chiave API provengono dall'input dell'utente o dall'archivio segreto della tua app. Non dovrebbero essere in codice pubblicato, capture, log o URL.
Il client mantiene il token in memoria. Lo invia solo al server che lo ha emesso. Le funzioni sottostanti negano i percorsi verso un altro server e HTTP reindirizza.
2.2 Rispetta i nomi JSON
| Campo | Ruolo |
|---|---|
| AccountName, Password | Credenziali inviate al server. |
| Gettone | Token finale da utilizzare nell'Autorizzazione: Bearer. |
| Stato | Fase del percorso MFA. |
| CeremoniToken | Riferimento temporaneo alla verifica corrente; non è un token di accesso. |
| ProvisioningUri, ManualKey | Informazioni riservate sulla configurazione TOTP, presenti alla registrazione. |
| Codice, Messaggio In caso di errore | Identificatore tecnico dell'errore e testo esplicativo. |
| Codice nella query VerifyTotp | Un codice TOTP di sei cifre, trasmesso come stringa. |
Le risposte di queste API utilizzano il PascalCase. Leggi Gettone, non Gettone, e CeremoniToken, non CerimoniaToken. Alcune risposte classiche possono contenere metadati come $type : I clienti qui sotto li ignorano.
Il Contratto di Connessione Ospitata è un'eccezione: Ritorni Browser/Token access_token, token_type e expires_in. Non mischiare i due formati.
2.3 L'intestazione Origin per MFA
Strade MFA/VerifyCredentials, MFA/VerifyTotp e MFA/Annulla richiedi un corpo JSON e un'origine che sia in MFA: Origini.
| Cliente | Valore dell'origine |
|---|---|
| .http, C#, Python, PowerShell, file JavaScript in Node.js | Invialo esplicitamente. In un test locale, https://localhost:44333 Adatto se questa origine è consentita dal server. |
| JavaScript o TypeScript in un browser | Il browser lo invia automaticamente: è l'origine del sito che esegue il codice. Lo script non può sostituirlo con quello di RestFrontage. |
Per un'applicazione web ospitata su https://application.exemple.fr chi chiama https://rest.exemple.fr, hanno https://application.exemple.fr in Mfa:Origins e nella politica CORS di RestFrontage. I due controlli hanno ruoli separati. Un'origine non ha una barra di percorso o di estremità.
L'origine non è né una chiave API né una prova di identità. In un client non browser, questa intestazione può essere compilata dal programma; il server verifica sempre la password e la MFA. Vedi Origine nel browser e CORS.
2.4 Funzioni comuni degli esempi
Ogni variante prepara il client HTTP utilizzato nelle sezioni successive. SendAsync, invia, send_request e Send-Immersive sono utility in questo articolo, non funzioni fornite da una DLL Immersive.
Per C#, crea un'app console .NET 8 o successivo ; Gli snippet sono usati nello stesso file di istruzioni di livello superiore, con gli usi impliciti abilitati. Per JavaScript, usa un modulo in un browser HTTPS o Node.js 22 o dopo. Le seguenti chiamate JavaScript possono essere utilizzate anche in TypeScript; la scheda TypeScript fornisce una variante tipizzata della preparazione.
Python richiede Python 3.10 o successivo e Richieste. La conversione opzionale di una chiave API in token utilizza anch'essa Crittografia :
python -m pip install requests cryptography
Campioni PowerShell target PowerShell 7, inclusi RSA-OAEP-SHA256. Non prendono di mira PowerShell 5.1 di Windows.
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var server = new Uri("https://localhost:44333");
var origin = server.GetLeftPart(UriPartial.Authority);
using var http = new HttpClient(new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = false
}) { Timeout = TimeSpan.FromSeconds(30) };
async Task<JsonElement> SendAsync(
HttpMethod method, string path, object? body = null,
Dictionary<string, string>? headers = null)
{
if (server.Scheme != "https" || !string.IsNullOrEmpty(server.UserInfo)
|| !path.StartsWith('/') || path.StartsWith("//") || path.Contains('\\'))
throw new ArgumentException("Adresse HTTPS et chemin local attendus.");
var uri = new Uri(server, path);
if (uri.GetLeftPart(UriPartial.Authority) != origin)
throw new ArgumentException("Changement de serveur interdit.");
using var request = new HttpRequestMessage(method, uri);
request.Headers.Accept.ParseAdd("application/json");
if (headers != null)
foreach (var pair in headers) request.Headers.Add(pair.Key, pair.Value);
if (body != null)
request.Content = new StringContent(
JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
using var response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(
"Appel refusé : HTTP " + (int)response.StatusCode,
null, response.StatusCode);
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
return json.RootElement.Clone();
}
string ReadToken(JsonElement response)
{
if (!response.TryGetProperty("Token", out var value)
|| value.ValueKind != JsonValueKind.String
|| string.IsNullOrWhiteSpace(value.GetString()))
throw new InvalidOperationException("Aucun token d'accès dans cette réponse.");
return value.GetString()!;
}
var mfaHeaders = new Dictionary<string, string> { ["Origin"] = origin };
const server = new URL("https://localhost:44333");
const origin = server.origin;
// Hors navigateur seulement ; dans le navigateur, Origin est automatique.
const mfaHeaders = typeof window === "undefined" ? { Origin: origin } : {};
async function send(method, path, body = undefined, headers = {}) {
if (server.protocol !== "https:" || server.username || server.password
|| !path.startsWith("/") || path.startsWith("//") || path.includes("\\"))
throw new Error("Adresse HTTPS et chemin local attendus.");
const url = new URL(path, server);
if (url.origin !== origin) throw new Error("Changement de serveur interdit.");
const response = await fetch(url, {
method, credentials: "omit", redirect: "error", cache: "no-store",
signal: AbortSignal.timeout(30000),
headers: {
Accept: "application/json",
...(body === undefined ? {} : { "Content-Type": "application/json" }),
...headers
},
...(body === undefined ? {} : { body: JSON.stringify(body) })
});
if (!response.ok) throw new Error("Appel refusé : HTTP " + response.status);
return await response.json();
}
function readToken(response) {
if (typeof response.Token !== "string" || !response.Token)
throw new Error("Aucun token d'accès dans cette réponse.");
return response.Token;
}
import requests
from urllib.parse import urljoin, urlsplit
server = "https://localhost:44333"
origin = server
mfa_headers = {"Origin": origin}
def send_request(method, path, body=None, headers=None):
base = urlsplit(server)
if (base.scheme != "https" or base.username or base.password
or not path.startswith("/") or path.startswith("//") or "\\" in path):
raise ValueError("Adresse HTTPS et chemin local attendus.")
url = urljoin(server, path)
if urlsplit(url).netloc != base.netloc:
raise ValueError("Changement de serveur interdit.")
response = requests.request(
method, url, json=body,
headers={"Accept": "application/json", **(headers or {})},
timeout=30, allow_redirects=False
)
if not 200 <= response.status_code < 300:
raise RuntimeError("Appel refusé : HTTP " + str(response.status_code))
return response.json()
def read_token(response):
token = response.get("Token")
if not isinstance(token, str) or not token:
raise RuntimeError("Aucun token d'accès dans cette réponse.")
return token
$server = [Uri]'https://localhost:44333'
$origin = $server.GetLeftPart([UriPartial]::Authority)
$mfaHeaders = @{ Origin = $origin }
function Send-Immersive {
param(
[string]$Method, [string]$Path,
[object]$Body = $null, [hashtable]$Headers = @{}
)
if ($server.Scheme -ne 'https' -or $server.UserInfo -or
-not $Path.StartsWith('/') -or $Path.StartsWith('//') -or $Path.Contains('\')) {
throw 'Adresse HTTPS et chemin local attendus.'
}
$uri = [Uri]::new($server, $Path)
if ($uri.GetLeftPart([UriPartial]::Authority) -ne $origin) {
throw 'Changement de serveur interdit.'
}
$allHeaders = @{ Accept = 'application/json' }
foreach ($key in $Headers.Keys) { $allHeaders[$key] = $Headers[$key] }
$arguments = @{
Uri = $uri; Method = $Method; Headers = $allHeaders
TimeoutSec = 30; MaximumRedirection = 0; ErrorAction = 'Stop'
}
if ($null -ne $Body) {
$arguments.ContentType = 'application/json; charset=utf-8'
$arguments.Body = ConvertTo-Json -InputObject $Body -Depth 8 -Compress
}
Invoke-RestMethod @arguments
}
function Read-ImmersiveToken {
param([object]$Response)
if ([string]::IsNullOrWhiteSpace($Response.Token)) {
throw "Aucun token d'accès dans cette réponse."
}
$Response.Token
}
const server = new URL("https://localhost:44333");
const origin = server.origin;
// Hors navigateur seulement ; dans le navigateur, Origin est automatique.
const mfaHeaders: Record<string, string> = typeof window === "undefined" ? { Origin: origin } : {};
async function send<T = any>(
method: string, path: string, body?: unknown,
headers: Record<string, string> = {}
): Promise<T> {
if (server.protocol !== "https:" || server.username || server.password
|| !path.startsWith("/") || path.startsWith("//") || path.includes("\\"))
throw new Error("Adresse HTTPS et chemin local attendus.");
const url = new URL(path, server);
if (url.origin !== origin) throw new Error("Changement de serveur interdit.");
const response = await fetch(url, {
method, credentials: "omit", redirect: "error", cache: "no-store",
signal: AbortSignal.timeout(30000),
headers: {
Accept: "application/json",
...(body === undefined ? {} : { "Content-Type": "application/json" }),
...headers
},
...(body === undefined ? {} : { body: JSON.stringify(body) })
});
if (!response.ok) throw new Error("Appel refusé : HTTP " + response.status);
return await response.json() as T;
}
function readToken(response: { Token?: unknown }): string {
if (typeof response.Token !== "string" || !response.Token)
throw new Error("Aucun token d'accès dans cette réponse.");
return response.Token;
}
type MfaReply =
| { Status: "authenticated"; Token: string }
| { Status: "totp_required"; CeremonyToken: string;
ProvisioningUri: null; ManualKey: null }
| { Status: "totp_enrollment_required"; CeremonyToken: string;
ProvisioningUri: string; ManualKey: string };
// Pour typer la réponse : await send<MfaReply>("POST", chemin, corps, mfaHeaders)
Queste utility generano un errore per qualsiasi risposta non 2xx e non mostrano i corpi sensibili. Nella tua interfaccia, elabora lo stato HTTP e il Messaggio Errori JSON per visualizzare il testo appropriato. Alcuni rifiuti sono testo o non hanno corpo.
Python utilizza lo store certificati riconosciuto tramite richieste. Se la tua azienda fornisce autorità interna, configura il suo certificato con REQUESTS_CA_BUNDLE. Non disabilitare il controllo TLS. Vedi Richieste di documentazione.
2.5 Fornisci valori specifici per la tua applicazione
Gli estratti per ogni modalità completano la preparazione sopra. Non sono un singolo programma da concatenare completamente: scegli una modalità di connessione e poi la chiamata autenticata.
| Valore | C# / JavaScript / TypeScript | Python | PowerShell |
|---|---|---|---|
| Accesso inserito | accountName | account_name | $accountName |
| Password inserita | Parola d'Ordine | Parola d'Ordine | $password |
| Codice TOTP inserito, come stringa | Codice | Codice | $code |
| Nome dell'applicazione fornito con la chiave API | AppName | app_name | $appName |
| Chiave API fornita dall'amministratore | apiKey | api_key | $apiKey |
Queste variabili devono essere popolate dalla tua interfaccia o archivio segreto prima di eseguire lo snippet. Gli esempi .http contengono valori da sovrascrivere. Tieni questi file di test fuori dal repository se contengono segreti reali.
3. Classic login e password
3.1 Richiesta e risposta
Il ricorso POST /api/Security/Users/Authenticate riceve un oggetto JSON contenente AccountName e Password. Questa non è un'autenticazione HTTP Basic: non è richiesta alcuna intestazione Basic.
@server = https://localhost:44333
@username = compte.exemple
@password = REMPLACER_PAR_LE_MOT_DE_PASSE
### Mot de passe seul, si la politique l'autorise
# @name authenticate
POST {{server}}/api/Security/Users/Authenticate
Content-Type: application/json
Accept: application/json
{
"AccountName": "{{username}}",
"Password": "{{password}}"
}
var authentication = await SendAsync(
HttpMethod.Post, "/api/Security/Users/Authenticate",
new { AccountName = accountName, Password = password });
string token = ReadToken(authentication);
const authentication = await send(
"POST", "/api/Security/Users/Authenticate",
{ AccountName: accountName, Password: password }
);
const token = readToken(authentication);
authentication = send_request(
"POST", "/api/Security/Users/Authenticate",
{"AccountName": account_name, "Password": password}
)
token = read_token(authentication)
$body = @{ AccountName = $accountName; Password = $password }
$authentication = Send-Immersive -Method POST -Path '/api/Security/Users/Authenticate' -Body $body
$token = Read-ImmersiveToken $authentication
La risposta positiva contiene Gettone e le informazioni utente in AuthenticatedUser. Ecco solo i campi utili per questo esempio:
{
"Token": "JWT_IMMERSIVE",
"AuthenticatedUser": {
"AccountName": "compte.exemple"
}
}
Non richiede un campo Stato Su questa risposta classica. Inoltre, non dare per scontato che tutte le proprietà dell'utente siano presenti in tutti gli ambienti.
In un file .http, i caratteri speciali nella password devono essere sfuggiti per formare un JSON valido. In un programma, lascia che il serializer JSON codifichi il valore; non costruire il corpo tramite concatenazione.
3.2 Quando l'MFA è obbligatorio
Con gli identificatori corretti, questa rotta restituisce HTTP 403 Quando è richiesta la MFA per l'account:
{
"Code": "mfa_required",
"Message": "Utilisez un client compatible avec la MFA."
}
Questo non significa che OData non sia disponibile: devi completare una connessione abilitata alla MFA prima di chiamarlo.
La sola password rimane utilizzabile quando la MFA è disabilitata o quando l'account è effettivamente esentabile nella modalità con esenzioni. La presenza di un'applicazione TOTP già configurata non rende di per sé obbligatoria la MFA: è la politica del server a decidere.
Una password vuota segue l'impostazione del server esistente. Il client non dovrebbe modificare questa impostazione né considerare un valore vuoto come modo alternativo di connettersi.
4. Accedi con MFA TOTP
4.1 Scegli lo scopo
Per un nuovo cliente, invia login-with-totp-or-enroll. Questo corso è adatto sia a un utente già registrato sia a un nuovo utente.
| Scopo | Utilizzo |
|---|---|
| login-with-totp-or-enroll | Accesso completo: token immediato se consentito, TOTP se necessario, registrazione iniziale o riregistrazione consentita se necessario. |
| login-with-totp | Accedi senza una schermata di iscrizione. Se è richiesta la MFA, l'account deve già avere la verifica attiva. |
Questi valori indicano la capacità del customer journey; non permettono di disabilitare l'MFA scelto dall'amministratore. login-with-totp può quindi restituire un token immediatamente se la policy non richiede MFA per quell'account.
4.2 Verifica le credenziali
@server = https://localhost:44333
@origin = https://localhost:44333
@username = compte.exemple
@password = REMPLACER_PAR_LE_MOT_DE_PASSE
### Étape 1 : lire Status avant de poursuivre
# @name verifyCredentials
POST {{server}}/api/Security/Mfa/VerifyCredentials
Content-Type: application/json
Accept: application/json
Origin: {{origin}}
{
"Credentials": {
"AccountName": "{{username}}",
"Password": "{{password}}"
},
"Purpose": "login-with-totp-or-enroll"
}
var step = await SendAsync(
HttpMethod.Post, "/api/Security/Mfa/VerifyCredentials",
new {
Credentials = new { AccountName = accountName, Password = password },
Purpose = "login-with-totp-or-enroll"
}, mfaHeaders);
string status = step.GetProperty("Status").GetString()!;
const step = await send(
"POST", "/api/Security/Mfa/VerifyCredentials",
{
Credentials: { AccountName: accountName, Password: password },
Purpose: "login-with-totp-or-enroll"
}, mfaHeaders
);
const status = step.Status;
step = send_request(
"POST", "/api/Security/Mfa/VerifyCredentials",
{
"Credentials": {"AccountName": account_name, "Password": password},
"Purpose": "login-with-totp-or-enroll"
}, mfa_headers
)
status = step["Status"]
$body = @{
Credentials = @{ AccountName = $accountName; Password = $password }
Purpose = 'login-with-totp-or-enroll'
}
$step = Send-Immersive -Method POST -Path '/api/Security/Mfa/VerifyCredentials' -Body $body -Headers $mfaHeaders
$status = $step.Status
4.3 Affrontare le tre possibili risposte
Caso 1 — Collegamento completato. Se il server consente la connessione senza un passaggio aggiuntivo:
{
"Status": "authenticated",
"Token": "JWT_IMMERSIVE"
}
Uso diretto Gettone : ReadToken (step) in C#, readToken(step) in JavaScript/TypeScript, read_token(passo) in Python, Read-ImmersiveToken $step in PowerShell. Non chiamare VerifyTotp : Non è stato emesso alcun CeremonyToken.
Caso 2 — TOTP richiesto. L'account ha già la verifica:
{
"Status": "totp_required",
"CeremonyToken": "REFERENCE_TEMPORAIRE",
"ProvisioningUri": null,
"ManualKey": null
}
Tieni CeremoniToken in memoria e visualizzare l'inserimento del codice telefonico. Non sono ancora stati emessi gettoni di accesso.
Caso 3 — Registrazione richiesta. L'account deve impostare la sua verifica:
{
"Status": "totp_enrollment_required",
"CeremonyToken": "REFERENCE_TEMPORAIRE",
"ProvisioningUri": "otpauth://totp/Immersive:compte.exemple?secret=SECRET_FICTIF&issuer=Immersive&algorithm=SHA1&digits=6&period=30",
"ManualKey": "SECRET_FICTIF"
}
L'URI e il segreto sopra sono illustrativi. Nella tua interfaccia:
- Genera A livello locale il codice QR rappresenta esattamente ProvisioningUri.
- Proposta ManualKey come soluzione di input manuale se la scansione non è possibile.
- L'utente aggiunge l'account in Microsoft Authenticator o in un'altra app abilitata a TOTP.
- Inserisce il primo codice a sei cifre.
- Invia questo codice con il stesso Gettone di Cerimonia per verificare Totp.
Non passare ProvisioningUri a un servizio web di terze parti per la generazione di codici QR. Questo URI contiene il segreto per calcolare codici futuri. Cancella, insieme a ManualKey e al riferimento temporaneo, dopo successo o abbandono.
A TOTP è una password uniuso calcolata a partire da un segreto e dal tempo. Usi immersivi qui Sei cifre, SHA-1 e un periodo di 30 secondi. Il telefono può calcolare il codice senza Internet; devi sempre riuscire a raggiungere RestFrontage per verificarlo. Il centralino è il RFC 6238.
sequenceDiagram
participant U as Utilisateur
participant A as Application cliente
participant R as RestFrontage
participant T as Application TOTP
U->>A: Identifiant et mot de passe
A->>R: VerifyCredentials
alt Connexion immédiatement autorisée
R-->>A: authenticated et Token
else Vérification supplémentaire
R-->>A: CeremonyToken et étape attendue
opt Inscription ou réenregistrement autorisé
A-->>U: QR code et clé manuelle
U->>T: Ajouter le compte
end
T-->>U: Code à six chiffres
U->>A: Saisir le code
A->>R: VerifyTotp avec CeremonyToken et Code
alt Code accepté
R-->>A: authenticated et Token
else Code refusé
R-->>A: HTTP 400 et Message
A-->>U: Corriger la saisie ou attendre le prochain code
end
end
4.4 Conferma il codice e recupera il token
Questo passo è il seguente solo dopo totp_required o totp_enrollment_required. La variabile Codice è una stringa derivata dall'input dell'utente, ad esempio "012345". Non convertirlo in un numero: lo zero iniziale fa parte del codice.
@totp = 012345
### Étape 2 : seulement si l'étape 1 fournit CeremonyToken
# @name authenticateMfa
POST {{server}}/api/Security/Mfa/VerifyTotp
Content-Type: application/json
Accept: application/json
Origin: {{origin}}
{
"CeremonyToken": "{{verifyCredentials.response.body.$.CeremonyToken}}",
"Code": "{{totp}}"
}
if (status is not ("totp_required" or "totp_enrollment_required"))
throw new InvalidOperationException("Aucune vérification TOTP en attente.");
if (code.Length != 6 || code.Any(c => c is < '0' or > '9'))
throw new ArgumentException("Saisissez six chiffres.");
var authentication = await SendAsync(
HttpMethod.Post, "/api/Security/Mfa/VerifyTotp",
new { CeremonyToken = step.GetProperty("CeremonyToken").GetString(), Code = code },
mfaHeaders);
string token = ReadToken(authentication);
if (!["totp_required", "totp_enrollment_required"].includes(status))
throw new Error("Aucune vérification TOTP en attente.");
if (!/^[0-9]{6}$/.test(code)) throw new Error("Saisissez six chiffres.");
const authentication = await send(
"POST", "/api/Security/Mfa/VerifyTotp",
{ CeremonyToken: step.CeremonyToken, Code: code }, mfaHeaders
);
const token = readToken(authentication);
if status not in ("totp_required", "totp_enrollment_required"):
raise ValueError("Aucune vérification TOTP en attente.")
if len(code) != 6 or any(c not in "0123456789" for c in code):
raise ValueError("Saisissez six chiffres.")
authentication = send_request(
"POST", "/api/Security/Mfa/VerifyTotp",
{"CeremonyToken": step["CeremonyToken"], "Code": code}, mfa_headers
)
token = read_token(authentication)
if ($status -notin @('totp_required', 'totp_enrollment_required')) {
throw 'Aucune vérification TOTP en attente.'
}
if ($code -cnotmatch '^[0-9]{6}$') { throw 'Saisissez six chiffres.' }
$body = @{ CeremonyToken = $step.CeremonyToken; Code = $code }
$authentication = Send-Immersive -Method POST -Path '/api/Security/Mfa/VerifyTotp' -Body $body -Headers $mfaHeaders
$token = Read-ImmersiveToken $authentication
Per entrambe le connessioni con Purpose, la risposta efficace è:
{
"Status": "authenticated",
"Token": "JWT_IMMERSIVE"
}
Questo vale anche dopo la conferma di una registrazione avviata con login-with-totp-or-enroll : La registrazione riuscita completa l'accesso e fornisce il token. Non è necessario ricominciare immediatamente con la password.
Uno stato HTTP 200 non è sufficiente per concludere che l'utente sia loggato.
VerifyCredentials può rispondere a HTTP 200 con un passaggio MFA in sospeso. La connessione viene completata solo quando Lo stato è autenticato e un token è presente.
4.5 Scadenza, ritentativo e annullamento
Un assegno è valido Cinque minuti. Il suo riferimento può essere usato con successo solo una volta. Un codice già accettato non può essere riutilizzato per un'altra verifica.
Dopo un codice rifiutato, visualizza l'errore e consente una nuova voce con lo stesso CeremonyToken finché la verifica rimane valida. Non ritentare automaticamente i tentativi di testa. Dopo Cinque codici errati, la verifica dell'account è bloccata per Quindici minuti ; Ricominciare con la password non resetta il timer.
Se il riferimento è scaduto, la politica è cambiata o l'utente è stato cancellato, ricomincia su VerifyCredentials. Dopo che l'amministratore ha reimpostato un telefono, login-with-totp-or-enroll presenta la nuova registrazione purché l'autorizzazione di ri-registrazione sia valida. Non sono necessari codici di recupero aggiuntivi.
Per annullare esplicitamente un controllo in sospeso:
POST {{server}}/api/Security/Mfa/Cancel
Content-Type: application/json
Origin: {{origin}}
{
"CeremonyToken": "{{verifyCredentials.response.body.$.CeremonyToken}}"
}
La risposta è { "Status": "cancellato" }. Poi cancella i dati temporanei dalla tua interfaccia. Cancella non è una disconnessione e non revoca un token finale già emesso.
4.6 Altri scopi non sono le modalità di connessione
| Valore | Scopo | Possibile risultato dopo la verifica |
|---|---|---|
| Iscriviti | Configura la verifica dell'account in un percorso di gestione autorizzato. | Stato: registrato ; Nessun token di accesso. |
| admin | Conferma un'operazione di gestione protetta. | Stato: autorizzato e ProofToken ; questa prova non è un Portatore di Sé stesso. |
| Gestire | Visualizza il metodo di verifica dell'account. | Stato: credenziali e Credenziali ; Nessun token di accesso. |
| elimina | Rimuovere i mezzi di verifica quando consentito dalla politica. | Stato: eliminato ; Rifiutato se l'MFA è ancora richiesto. |
| solo credenziali | Il nome usato dal classico viaggio con password. | Utilizzo Utenti/Autenticazione per questo percorso; non farne uno scopo per il tuo cliente MFA. |
Per ottenere un token con MFA, la tua integrazione utilizza i due scopi di connessione descritti sopra. Non serve il Nome, che non fa più parte della query.
5. Autenticazione della chiave API
5.1 Chiama direttamente le API
Ottieni una chiave valida e è Nome dell'applicazione. La chiave deve essere entro il suo periodo di validità e l'account associato deve essere autorizzato ad accedere. Il nome dell'applicazione viene confrontato in base a case insensitive; il valore della chiave è distinto a maiuscole e minuscoli.
Una chiave API per un'applicazione dovrebbe rimanere in un backend, un servizio o un ambiente controllato. Non incorporarla nei file JavaScript scaricati dai visitatori del sito. Gli esempi JavaScript in questa sezione sono eseguiti lato server, in Node.js.
@server = https://localhost:44333
@appName = Supervision atelier
@apiKey = REMPLACER_PAR_LA_CLE_API
### Authentification directe par clé API
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
x-app-name: {{appName}}
x-api-key: {{apiKey}}
var keyHeaders = new Dictionary<string, string>
{
["x-app-name"] = appName,
["x-api-key"] = apiKey
};
var account = await SendAsync(
HttpMethod.Get, "/api/Security/Mfa/Context", headers: keyHeaders);
string? name = account.GetProperty("AccountName").GetString();
// Node.js uniquement : la clé reste côté serveur.
const keyHeaders = { "x-app-name": appName, "x-api-key": apiKey };
const account = await send(
"GET", "/api/Security/Mfa/Context", undefined, keyHeaders
);
const name = account.AccountName;
key_headers = {"x-app-name": app_name, "x-api-key": api_key}
account = send_request(
"GET", "/api/Security/Mfa/Context", headers=key_headers
)
name = account["AccountName"]
$keyHeaders = @{ 'x-app-name' = $appName; 'x-api-key' = $apiKey }
$account = Send-Immersive -Method GET -Path '/api/Security/Mfa/Context' -Headers $keyHeaders
$name = $account.AccountName
La risposta in questo esempio contiene AccountName, non un token. La chiave ha autenticato direttamente la richiesta. Puoi inviare le stesse due intestazioni alla tua WebAPI o alla rotta OData.
Il Sentiero MFA/Contesto Questo viene usato qui solo per verificare l'account collegato. Questo GET non richiede un codice MFA.
Non mandare uno vecchio nello stesso momento Autorizzazione: Titolare oppure un cookie di autenticazione: utilizzare chiaramente o le intestazioni della chiave API o il portatore risultante. Una chiave API non viene trasmessa scrivendo Permesso: Portatore CLE_API.
6. Utilizzo del token con WebAPI e OData
6.1 Chiamata autenticata
Che sia tramite password, MFA o API key, il token finale viene inviato a Autorizzazione: Titolare.
### Après Users/Authenticate
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
Authorization: Bearer {{authenticate.response.body.$.Token}}
### Après VerifyTotp
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
Authorization: Bearer {{authenticateMfa.response.body.$.Token}}
### Si VerifyCredentials a répondu authenticated directement
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
Authorization: Bearer {{verifyCredentials.response.body.$.Token}}
### Après Token/Renew avec une clé API
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
Authorization: Bearer {{authenticateApiKey.response.body.$.Token}}
var bearerHeaders = new Dictionary<string, string>
{
["Authorization"] = "Bearer " + token
};
var result = await SendAsync(
HttpMethod.Get, "/api/Security/Mfa/Context", headers: bearerHeaders);
string? accountNameFromToken = result.GetProperty("AccountName").GetString();
const result = await send(
"GET", "/api/Security/Mfa/Context", undefined,
{ Authorization: "Bearer " + token }
);
const accountNameFromToken = result.AccountName;
result = send_request(
"GET", "/api/Security/Mfa/Context",
headers={"Authorization": "Bearer " + token}
)
account_name_from_token = result["AccountName"]
$bearerHeaders = @{ Authorization = 'Bearer ' + $token }
$result = Send-Immersive -Method GET -Path '/api/Security/Mfa/Context' -Headers $bearerHeaders
$accountNameFromToken = $result.AccountName
Per OData, mantieni lo stesso header e sostituisci il percorso con una raccolta pubblicata dal tuo server. Nome della collezione è un luogo da sostituire, non una strada fornita da questo articolo:
GET {{server}}/odata/NomDeCollection?$top=10
Accept: application/json
Authorization: Bearer {{authenticateMfa.response.body.$.Token}}
Un token valido dimostra l'autenticazione. I diritti degli account, i filtri organizzativi e i permessi aziendali rimangono applicabili. L'MFA non trasforma un utente ordinario in un amministratore.
6.2 Valida, scadenza e rinnovo
GET /api/Security/Token/Validate, con il Portatore ti permette di controllare la sessione. Il successo è HTTP 202 accettato, con un oggetto che include Oggetto e IsAdministrator. Non testare solo HTTP 200.
GET {{server}}/api/Security/Token/Validate
Accept: application/json
Authorization: Bearer {{authenticateMfa.response.body.$.Token}}
La data di scadenza di un JWT è indicata nel suo campo Esperienza. Leggerlo dal lato client serve a organizzare una riconnessione; non sostituisce mai la validazione del token da parte del server.
Le rotte classiche e MFA in questa guida non restituiscono un token di aggiornamento. Quando una sessione non viene più accettata, riavvia il percorso per la modalità autenticazione.
Il Percorso Storico Token/Rinnova può rinnovare una sessione ancora valida, con lo stesso corpo criptato di prima, ma inviando il Portatore comune invece delle intestazioni della chiave API. Non ti permette di recuperare una sessione scaduta né di bypassare un requisito MFA. La nuova risposta deve sostituire il token che il client detiene.
Un cambiamento nella policy MFA o un reset può rendere una sessione inutilizzabile prima della scadenza. Quindi gestisci sempre i rifiuti del server, anche se l'esperienza indica ancora una data futura.
7. Usa una chiave API con WebAPI e OData
Una chiave API autentica direttamente ogni richiesta su RestFrontage. La tua applicazione passa sempre attraverso le seguenti due intestazioni:
- x-app-name : Il nome dell'applicazione associata alla chiave.
- x-api-key : Il valore della chiave.
Non è necessario alcun accesso o recupero del token precedente. Non si chiamano le modalità di login con password o verifica TOTP.
7.1 Fai chiamate
Gli esempi seguenti eseguono due interrogatorie:
- Una chiamata WebAPI a /api/Security/MFA/Contesto, per verificare il nome dell'account associato alla chiave. Nonostante il nome, questa via non richiede alcun codice MFA.
- Una chiamata OData a /odata/EquipmentInstances?$top=10, per richiedere fino a dieci apparecchiature accessibili a questo account.
Le varianti programmate riutilizzano le funzioni comuni presentate all'inizio di questo articolo. Compila il nome dell'applicazione e della sua chiave con i valori forniti dal tuo amministratore.
@server = https://localhost:44333
@appName = MonApplication
@apiKey = REMPLACER_PAR_LA_CLE_API
### WebAPI : vérifier le compte connecté
GET {{server}}/api/Security/Mfa/Context
Accept: application/json
x-app-name: {{appName}}
x-api-key: {{apiKey}}
### OData : lire jusqu'à dix équipements
GET {{server}}/odata/EquipmentInstances?$top=10
Accept: application/json
x-app-name: {{appName}}
x-api-key: {{apiKey}}
var keyHeaders = new Dictionary<string, string>
{
["x-app-name"] = appName,
["x-api-key"] = apiKey
};
// WebAPI
var account = await SendAsync(
HttpMethod.Get,
"/api/Security/Mfa/Context",
headers: keyHeaders);
// OData
var equipments = await SendAsync(
HttpMethod.Get,
"/odata/EquipmentInstances?$top=10",
headers: keyHeaders);
// Node.js uniquement : conserver la clé côté serveur.
// Cet extrait s'utilise également en TypeScript.
const keyHeaders = {
"x-app-name": appName,
"x-api-key": apiKey
};
// WebAPI
const account = await send(
"GET",
"/api/Security/Mfa/Context",
undefined,
keyHeaders
);
// OData
const equipments = await send(
"GET",
"/odata/EquipmentInstances?$top=10",
undefined,
keyHeaders
);
key_headers = {
"x-app-name": app_name,
"x-api-key": api_key
}
# WebAPI
account = send_request(
"GET",
"/api/Security/Mfa/Context",
headers=key_headers
)
# OData
equipments = send_request(
"GET",
"/odata/EquipmentInstances?$top=10",
headers=key_headers
)
$keyHeaders = @{
'x-app-name' = $appName
'x-api-key' = $apiKey
}
# WebAPI
$account = Send-Immersive -Method GET -Path '/api/Security/Mfa/Context' -Headers $keyHeaders
# OData
$equipments = Send-Immersive -Method GET -Path '/odata/EquipmentInstances?$top=10' -Headers $keyHeaders
Ogni risposta contiene il risultato dell'operazione richiesta: informazioni contabili per la prima chiamata, dati OData per la seconda. Non c'è alcun gettone da estrarre da queste risposte.
Per chiamare una route diversa, mantenere entrambi gli intestamenti e adattare il percorso, il metodo HTTP e il corpo secondo l'operazione. Parametri OData, come $filter, $select oppure $orderby, rimangono utilizzabili quando la collezione li supporta.
7.2 Tieni le intestazioni su ogni richiesta
L'autenticazione tramite chiave API viene effettuata in ogni chiamata. Una prima richiesta riuscita non ti esenta dal passare le intestazioni nelle chiamate successive.
Questo vale anche per le richieste di paging e le chiamate ad altre collezioni sullo stesso server. Verifica che un indirizzo di paging appartenga al tuo server RestFrontage prima di passargli la chiave.
Non aggiungere un'intestazione Autorizzazione: Titolare a queste chiamate e non inviare un cookie corrispondente a un'altra sessione utente.
7.3 I diritti rimangono quelli dell'account associato
La chiave API fornisce accesso alle operazioni consentite per il suo account associato. Le restrizioni organizzative e i permessi aziendali rimangono applicabili.
Una chiave valida non garantisce l'accesso a tutti i dati o alle operazioni. Ad esempio, un account autorizzato a visualizzare l'apparecchiatura non ha automaticamente il diritto di modificarla.
La chiave deve anche rimanere valida e l'account associato deve poter accedere. Una chiave scaduta, disabilitata o eliminata non può più autenticare le chiamate.
Tieni la chiave in uno store segreto adatto alla tua applicazione. Non inserirla in un URL, un log o file JavaScript distribuiti agli utenti di un sito web.
Per un'applicazione web, le chiamate tramite questa chiave devono essere effettuate dal backend.
8. Errori di lettura senza bloccare l'utente
| Risposta | Interpretazione e azione |
|---|---|
| 400 su Utenti/Autenticazione | Richiesta non valida; controlla il corpo e qualsiasi impostazione di password vuota. |
| 401 su Utenti/Autenticazione | Credenziali rifiutate o account non disponibili. Presenta un errore senza tentare automaticamente di riprovare. |
| 403, Codice: mfa_required Sul login classico | Usa il percorso MFA o il login ospitato. |
| 403 su una tratta MFA, a volte senza corpo JSON | Controlla Origin e MFA:Origins. Nel browser, controlla anche CORS. |
| 415 su una tratta MFA | Invia Tipo di contenuto: application/json. |
| 400, Codice: mfa_verification_failed | Messaggio di lettura: Codice errato, già usato, riferimento, blocco o configurazione scaduti, non disponibile. |
| 400, Codice: mfa_enrollment_unavailable | L'autorizzazione di nuova registrazione non è più utilizzabile; chiedi all'amministratore di rinnovare l'azione. |
| 401 con una chiave API | Chiave non valida, fuori validità o token derivato revocato; controlla anche x-app-name. |
| 403 con una chiave API valida | Il suo account collegato può essere assente, disattivato, scaduto o bloccato. |
| 401 su una WebAPI o OData con portatore | Token assente, scaduto, revocato o diventato incompatibile con la policy; riconnettere il client. |
| 403 Su un'attività commerciale | L'identità è riconosciuta ma l'operazione non è autorizzata; verifica i diritti. |
| 503 | Un controllo di sicurezza è temporaneamente non disponibile; mostra l'indisponibilità e programma un nuovo processo differito. |
Non tutti i rifiuti hanno lo stesso formato. Il tuo cliente dovrebbe controllare lo stato HTTP prima di tentare di leggere un JSON.
Se c'è un guasto di rete durante la conferma MFA, non presumere che la richiesta non sia stata eseguita: il server potrebbe aver consumato il riferimento senza che la risposta raggiungesse il client. Riprova la connessione se necessario e attendi il prossimo codice dal telefono.
9. Tour storici e collegamento ospitato
9.1 Sicurezza/Autenticazione e SSO
POST /api/Security/Authenticate è un vecchio ingresso che riceve un stringa JSON cifrata, non l'oggetto in chiaro AccountName/Password mostrato qui. Il risultato di ciò è ancora soggetto alla policy MFA. Per una nuova integrazione diretta con password, usa Utenti/Autenticazione o percorsi MFA.
Il login SSO è un percorso separato, eseguito con il fornitore di identità configurato presso la sede del cliente. Inviare un login e una password agli Utenti/Autenticati non trasforma la chiamata in un SSO. Un token SSO valido mantiene i diritti dell'account; l'MFA di questo percorso è responsabilità del fornitore configurato.
9.2 Riutilizza le schermate del server
Se non vuoi approfondire le interazioni descritte nella sezione TOTP, usa il percorso presentato nell'articolo Integra l'autenticazione immersiva e l'MFA nelle tue applicazioni web e .NET.
Aperture clienti Browser/Autorizza con un indirizzo di ritorno dichiarato, uno stato e un PKCE. Riceve un codice temporaneo e poi lo riscatta tramite Browser/Token. Non chiama VerifyCredentials né VerifyTotp stesso. Il WPF può visualizzare questa connessione nel browser esterno o nella sua finestra WebView2.
Scarica esempi web di WPF e login ospitato
Questo download è per il percorso con l'interfaccia server. Esempi di chiamate dirette con password, MFA e chiave API si trovano nelle schede di questo articolo.
Integra l'autenticazione immersiva
10. Verifica la tua integrazione
Controlla i casi che corrispondono alla tua domanda:
- Un account autorizzato ad accedere solo con password.
- Un account già registrato e soggetto a MFA.
- Un account soggetto a MFA che imposta la verifica per la prima volta.
- Una riregistrazione consentita dopo il reset.
- Un codice TOTP errato, un codice già usato, una cancellazione e una referenza scaduta.
- Una chiave API valida, una chiave scaduta e un account associato disabilitato.
- Una chiamata riuscita, un gettone rifiutato e un'operazione proibita dai diritti.
Il cliente deve distinguere tra una fase MFA in corso, Una connessione terminata e Un'attività commerciale autorizzata. Questi sono tre esiti diversi.