# Se connecter et créer un client

> Créez un client d'API et connectez-vous avec un mot de passe, un navigateur, un appareil ou un jeton.

Source: https://docs.thalovant.com/fr/developers/sdk-functions/signing-in/

---
{/* Last Edit: Claude Opus 5.5 - 2026-09-28 - Motive: Canadian French translation of developers/sdk-functions/signing-in.mdx; terms per docs/french-glossary.md (AMF, serveur), and review fixes. */}

Fait partie de [Fonctions des SDK](/fr/developers/sdk-functions/). Chaque appel ci-dessous est montré dans chaque langage qui l'offre.

## Créer un client d'API

Faites-le avant la découverte de hubs ou le provisionnement d'identités.

  

```python
from thalovant import ThalovantControlPlane

api = ThalovantControlPlane()
```

  
  

```ts

const api = new ThalovantControlPlane();
```

  
  

```go

api := thalovant.NewDefaultControlPlane("")
```

  
  

```rust
use thalovant::ControlPlane;

let mut api = ControlPlane::default();
```

  

## Se connecter

Connectez-vous avant les actions privées du plan de contrôle, comme la création d'une identité de client.

Les comptes où l'authentification multifacteur (AMF) est activée doivent fournir un code TOTP ou un code de récupération à usage unique. Sans l'un ou l'autre, l'API refuse la connexion avec un HTTP 401 et le code `mfa_required`. Chaque SDK n'envoie les champs d'AMF que lorsqu'ils sont définis.

  

```python
api.login("you@example.com", "password")

# MFA-enabled accounts add a TOTP code or a one-time recovery code:
api.login("you@example.com", "password", otp_code="123456")
api.login("you@example.com", "password", recovery_code="abcd-efgh-ijkl")
```

  
  

```ts
await api.login("you@example.com", "password");

// MFA-enabled accounts add a TOTP code or a one-time recovery code:
await api.login("you@example.com", "password", { otpCode: "123456" });
await api.login("you@example.com", "password", { recoveryCode: "abcd-efgh-ijkl" });
```

  
  

```go
_, err := api.Login(ctx, "you@example.com", "password", "")
if err != nil {
	log.Fatal(err)
}

// MFA-enabled accounts use LoginWithOptions with OTPCode or RecoveryCode:
_, err = api.LoginWithOptions(ctx, "you@example.com", "password", thalovant.LoginOptions{
	OTPCode: "123456",
})
if err != nil {
	log.Fatal(err)
}
```

  
  

```rust
api.login("you@example.com", "password", None).await?;

// MFA-enabled accounts use login_with_options with otp_code or recovery_code:
use thalovant::LoginOptions;

api.login_with_options(
    "you@example.com",
    "password",
    LoginOptions {
        otp_code: Some("123456".into()),
        ..Default::default()
    },
)
.await?;
```

  
  

```kotlin
api.login("you@example.com", "password")

// MFA-enabled accounts add a TOTP code or a one-time recovery code:
api.login("you@example.com", "password", otpCode = "123456")
api.login("you@example.com", "password", recoveryCode = "abcd-efgh-ijkl")
```

  
  

```swift
try await api.login(email: "you@example.com", password: "password")

// MFA-enabled accounts add a TOTP code or a one-time recovery code:
try await api.login(email: "you@example.com", password: "password", otpCode: "123456")
try await api.login(email: "you@example.com", password: "password", recoveryCode: "abcd-efgh-ijkl")
```

  
  

```csharp
await api.LoginAsync("you@example.com", "password");

// MFA-enabled accounts add a TOTP code or a one-time recovery code:
await api.LoginAsync("you@example.com", "password", otpCode: "123456");
await api.LoginAsync("you@example.com", "password", recoveryCode: "abcd-efgh-ijkl");
```

  

Échec courant :

```text
Missing Thalovant API access token
```

Pour le corriger, connectez-vous avant l'appel privé, ou [passez un jeton d'API au client du plan de contrôle](#sauthentifier-avec-un-jeton-dapi). Un HTTP 401 avec le code `mfa_required` signifie que le compte a besoin du code TOTP ou du code de récupération montré plus haut.

## Se connecter sans mot de passe

Utilisez la connexion par appareil quand le code ne doit jamais voir votre mot de passe : outils en ligne de commande, agents, carnets (notebooks) et machines auxquelles vous ne confiez pas entièrement les identifiants de votre compte.

Le SDK affiche une ligne comme `To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXX`, ouvre cette page dans votre navigateur quand il le peut, puis interroge l'API pendant que vous approuvez la connexion avec votre session de navigateur habituelle, y compris la connexion Google ou l'AMF. Une fois l'approbation donnée, le SDK reçoit un jeton d'API révocable, limité à certaines portées, et l'enregistre exactement comme après une connexion par mot de passe.

Pour approuver une connexion par appareil, l'espace de travail doit avoir un forfait payant; avec un forfait gratuit, l'approbation est refusée avec un HTTP 402. La connexion par appareil exige Python 0.4.22, Node.js 0.2.25, Go v0.3.3, Rust v0.2.20, ou Kotlin, Swift et .NET 0.1.1.

  

```python
api.login_with_browser()

# Optional: request narrower scopes and label the token in the dashboard.
api.login_with_browser(scopes=["hubs:read", "clients:write"], client_name="my-cli")
```

  
  

```ts
// Prints: To sign in, visit https://dash.thalovant.com/activate and enter the code XXXX-XXXX
const token = await api.loginWithBrowser({ clientName: "my-laptop" });

// api.accessToken is now set, exactly like after api.login(...).
```

  
  

```go
token, err := api.LoginWithBrowser(ctx, thalovant.DeviceLoginOptions{
	Scopes:     []string{"hubs:read", "clients:write"}, // optional
	ClientName: "my-cli",                               // optional label in the dashboard
})
if err != nil {
	log.Fatal(err)
}
fmt.Println("signed in, token id:", token["token_id"])
```

  
  

```rust
use thalovant::DeviceLoginOptions;

let token = api
    .login_with_browser(DeviceLoginOptions {
        scopes: vec!["hubs:read".into(), "clients:write".into()],
        client_name: Some("my-tool".into()),
        ..Default::default()
    })
    .await?;
println!("signed in, token scopes: {}", token["scopes"]);
```

  
  

```kotlin

api.loginWithBrowser(
    DeviceLoginOptions(scopes = listOf("hubs:read"), clientName = "kotlin-demo"),
)
```

  
  

```swift
let token = try await api.loginWithBrowser(options: DeviceLoginOptions(
    scopes: ["hubs:read", "clients:write"],
    clientName: "my-macbook"
))
print(token.tokenId ?? "", token.scopes)
```

  
  

```csharp
var result = await api.LoginWithBrowserAsync(new DeviceLoginOptions
{
    Scopes = new[] { "hubs:read", "clients:write" },
    ClientName = "my-tool",
});
Console.WriteLine($"{result.TokenId} expires {result.ExpiresAt}");
```

  

Chaque SDK accepte les mêmes options : `scopes` pour restreindre ce que le jeton peut faire, `client_name` (selon la casse propre au langage) pour nommer le jeton dans le tableau de bord, `open_browser` pour ne pas ouvrir la page, `prompt` pour remplacer la ligne affichée, et un délai d'expiration de 15 minutes par défaut.

La personne qui approuve gère le jeton obtenu sur la [page API Tokens](/fr/account/api-tokens/) du tableau de bord : portées, expiration, dernière utilisation et révocation.

## Se connecter depuis une application mobile ou de bureau

Utilisez la connexion native quand le code *est* l'application que la personne a sous les yeux : une application Android ou iOS, ou un client de bureau. L'application ouvre le navigateur, la personne se connecte avec sa session habituelle, et le navigateur remet à l'application un code d'autorisation. Aucun mot de passe n'atteint jamais votre processus, et aucun code d'appareil n'a à être saisi.

La connexion par appareil est le bon choix pour un outil en ligne de commande sur une machine sans navigateur. La connexion native est le bon choix quand l'application peut en ouvrir un et recevoir une redirection.

Le flux est celui du code d'autorisation OAuth 2.0 avec PKCE ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636), [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252), [RFC 9700](https://datatracker.ietf.org/doc/html/rfc9700)), et il tient en trois appels :

1. **Commencer.** Le SDK construit l'URL d'autorisation et vous remet deux secrets à conserver : un `state` et un `verifier` PKCE.
2. **Ouvrir et attendre.** L'application ouvre cette URL et attend sa redirection enregistrée.
3. **Terminer.** L'application extrait le code de la redirection et l'échange en envoyant le vérificateur, qui n'est jamais passé par le navigateur.

La connexion native exige Python `0.7.6`, Node.js, Kotlin, Swift ou .NET `0.7.2`, Go `v0.9.2` ou Rust `0.9.1`.

  

```python
from thalovant.native_auth import begin_native_sign_in

begun = begin_native_sign_in(
    client_id="my-app",
    redirect_uri="myapp://auth",
)
webbrowser.open(begun.authorization_url)

# ... the browser comes back to myapp://auth?code=...&state=...
code = begun.code_from(redirect)
if code:
    api.complete_native_sign_in(code, begun.verifier, "my-app", "myapp://auth")
```

  
  

```ts

const begun = await beginNativeSignIn({
  clientId: "my-app",
  redirectUri: "myapp://auth",
});
open(begun.authorizationUrl);

// ... the browser comes back to myapp://auth?code=...&state=...
const code = codeFrom(begun, redirect);
if (code) {
  await api.completeNativeSignIn(code, begun.verifier, "my-app", "myapp://auth");
}
```

`beginNativeSignIn` est asynchrone parce qu'il calcule le hachage du défi PKCE avec Web Crypto, ce qui permet au paquet pour navigateur de continuer à fonctionner.

  
  

```go
begun, err := thalovant.BeginNativeSignIn(thalovant.NativeSignInOptions{
    ClientID:    "my-app",
    RedirectURI: "myapp://auth",
})
// open begun.AuthorizationURL

// ... the browser comes back to myapp://auth?code=...&state=...
if code, ok := begun.CodeFrom(redirect); ok {
    token, err := plane.CompleteNativeSignIn(ctx, code, begun.Verifier, "my-app", "myapp://auth")
}
```

  
  

```rust
use thalovant::native_auth::{begin_native_sign_in, NativeSignInOptions};

let begun = begin_native_sign_in(NativeSignInOptions {
    client_id: "my-app".into(),
    redirect_uri: "myapp://auth".into(),
    scopes: None,
    dashboard_url: None,
})?;
// open begun.authorization_url

// ... the browser comes back to myapp://auth?code=...&state=...
if let Some(code) = begun.code_from(&redirect) {
    plane.complete_native_sign_in(&code, &begun.verifier, "my-app", "myapp://auth").await?;
}
```

  
  

```kotlin
val begun = NativeSignIn.begin(
    clientId = "my-app",
    redirectUri = "myapp://auth",
)
// open begun.authorizationUrl

// ... the browser comes back to myapp://auth?code=...&state=...
val code = begun.codeFrom(redirect)
if (code != null) {
    plane.completeNativeSignIn(code, begun.verifier, "my-app", "myapp://auth")
}
```

  
  

```swift
let begun = try NativeSignIn.begin(
    clientID: "my-app",
    redirectURI: "myapp://auth"
)
// open begun.authorizationURL

// ... the browser comes back to myapp://auth?code=...&state=...
if let code = begun.code(from: redirect) {
    try await plane.completeNativeSignIn(
        code: code, verifier: begun.verifier,
        clientID: "my-app", redirectURI: "myapp://auth"
    )
}
```

  
  

```csharp
var begun = NativeSignIn.Begin(
    clientId: "my-app",
    redirectUri: "myapp://auth");
// open begun.AuthorizationUrl

// ... the browser comes back to myapp://auth?code=...&state=...
var code = begun.CodeFrom(redirect);
if (code is not null)
{
    await plane.CompleteNativeSignInAsync(code, begun.Verifier, "my-app", "myapp://auth");
}
```

  

Les portées par défaut sont `hubs:read`, `clients:read` et `clients:write`, ce qui suffit à une application pour lister les hubs et gérer sa propre identité de client. Passez `scopes` pour les restreindre.

### Ce que le SDK refuse, et pourquoi

Ce ne sont pas des suggestions auxquelles l'appelant peut se soustraire. C'est ce qui rend le flux sûr sur un appareil où une autre application pourrait être à l'écoute de la même redirection.

- **`S256` uniquement.** L'API refuse `plain`, et les SDK ne l'offrent pas : il est impossible de demander le défi le plus faible.
- **Le vérificateur ne passe jamais par le navigateur.** Il va au point de terminaison du jeton et nulle part ailleurs. C'est ce qui rend un code intercepté inutile pour qui l'a intercepté.
- **La redirection doit arriver là où la tentative l'a demandée.** `state` prouve que la réponse appartient à cette requête; le SDK compare aussi le schéma, l'hôte et le chemin de la redirection à ceux que la tentative a enregistrés. Un rappel qui arrive à une autre adresse n'appartient pas à cette tentative, aussi valide que paraisse son état.
- **Un refus qui contient un code reste un refus.** Une redirection avec `error=` ne renvoie aucun code, même si un `code=` l'accompagne.
- **L'échange de jeton refuse le texte en clair.** Envoyer un vérificateur et un code en HTTP simple les exposerait tous deux sur le réseau ([CWE-319](https://cwe.mitre.org/data/definitions/319.html)); l'échange exige HTTPS, sauf pour les points de terminaison de développement explicitement en boucle locale (loopback).
- **L'URL du tableau de bord est validée.** Ce doit être une URL Thalovant, et une URL qui contient un fragment est refusée : un fragment placerait chaque paramètre là où un navigateur ne l'envoie jamais.

`code_from` (selon la casse propre au langage) ne renvoie rien, au lieu de lever une exception, en cas d'état non concordant, de code manquant ou de réponse `error=`. Les trois veulent dire « ne continuez pas », et un appelant qui les traite de la même façon ne peut pas en prendre un par erreur pour une réussite.

**Ne réessayez pas un échange échoué avec le même code.** Un code présenté deux fois révoque le jeton émis par le premier échange (RFC 9700) : une nouvelle tentative détruit donc le jeton qu'elle cherche à obtenir. Recommencez à partir de l'appel de départ.

## S'authentifier avec un jeton d'API

Utilisez un jeton d'API enregistré quand le processus doit démarrer déjà authentifié, par exemple pour des tâches d'intégration continue, des serveurs et des configurations d'agents d'IA.

Créez un jeton sur la [page API Tokens](/fr/account/api-tokens/) du tableau de bord ou par la [connexion par appareil](#se-connecter-sans-mot-de-passe), puis passez-le au constructeur du plan de contrôle. Les jetons sont limités à des portées, révocables, et leur dernière utilisation s'affiche sur cette page; en créer un exige un forfait payant pour l'espace de travail. Les exemples lisent le jeton dans une variable d'environnement `THALOVANT_API_TOKEN`, la même convention que celle du [serveur MCP](/fr/developers/mcp/).

  

```python

from thalovant import ThalovantControlPlane

api = ThalovantControlPlane(access_token=os.environ["THALOVANT_API_TOKEN"])
# Ready immediately; no login call needed.
```

  
  

```ts
const api = new ThalovantControlPlane("https://api.thalovant.com", {
  accessToken: process.env.THALOVANT_API_TOKEN,
});

// Ready immediately; no login call needed.
```

  
  

```go
api := thalovant.NewDefaultControlPlane(os.Getenv("THALOVANT_API_TOKEN"))
```

  
  

```rust
use thalovant::ControlPlane;

let token = std::env::var("THALOVANT_API_TOKEN").expect("THALOVANT_API_TOKEN is set");
let api = ControlPlane::with_access_token(token);
```

  
  

```kotlin
val api = ThalovantControlPlane(accessToken = System.getenv("THALOVANT_API_TOKEN"))
```

  
  

```swift
let api = ThalovantControlPlane(accessToken: ProcessInfo.processInfo.environment["THALOVANT_API_TOKEN"])
```

  
  

```csharp
var api = new ThalovantControlPlane(accessToken: Environment.GetEnvironmentVariable("THALOVANT_API_TOKEN"));
```

  

  Gardez les jetons hors du code source et des journaux. Conservez-les dans un gestionnaire de secrets ou un secret d'intégration continue, et révoquez un jeton sur la page API Tokens dès qu'il a pu fuiter.
