> ## Documentation Index
> Fetch the complete documentation index at: https://kernel.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials

> Use stored credentials for login and automatic reauthentication

Credentials let you store login information securely. Kernel can automatically authenticate credential-only flows and attempts to provide TOTP codes when needed.

**There are three ways to provide credentials:**

* **Automatically save during login** — Capture credentials directly from the user when they log in via [Hosted UI](/docs/auth/hosted-ui) or [Programmatic](/docs/auth/programmatic)
* **Pre-store in Kernel** — Create credentials before login for supported headless authentication flows
* **Connect 1Password** — Use credentials from your existing 1Password vaults

<Card title="1Password Integration" icon="key" href="/docs/integrations/1password">
  Connect your 1Password vaults to automatically use existing credentials with Managed Auth. Credentials are automatically matched by domain.
</Card>

## Save credentials during login

By default, Kernel saves durable credential fields entered during login so they can be used for eligible reauthentication attempts. No extra parameters are needed:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const login = await kernel.auth.connections.login(auth.id);
  ```

  ```python Python theme={null}
  login = await kernel.auth.connections.login(auth.id)
  ```

  ```go Go theme={null}
  login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{})
  if err != nil {
  	panic(err)
  }
  _ = login
  ```
</CodeGroup>

Once saved, the browser profile reuses its authenticated session until the site expires it. For supported credential-based flows, Kernel can then reauthenticate with the stored values. Credentials are updated after every successful login. Submitted one-time codes aren't saved; Kernel generates TOTP codes from a stored `totp_secret`.

To opt out of credential saving, set `save_credentials: false` when creating the connection:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const auth = await kernel.auth.connections.create({
    domain: 'example.com',
    profile_name: 'my-profile',
    save_credentials: false,
  });
  ```

  ```python Python theme={null}
  auth = await kernel.auth.connections.create(
      domain="example.com",
      profile_name="my-profile",
      save_credentials=False,
  )
  ```

  ```go Go theme={null}
  auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{
  	ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{
  		Domain:          "example.com",
  		ProfileName:     "my-profile",
  		SaveCredentials: kernel.Bool(false),
  	},
  })
  if err != nil {
  	panic(err)
  }
  _ = auth
  ```
</CodeGroup>

## Pre-store credentials

For credential-based flows that you want to run without user input, create credentials upfront:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const credential = await kernel.credentials.create({
    name: 'my-netflix-login',
    domain: 'netflix.com',
    values: {
      email: 'user@netflix.com',
      password: 'secretpassword123',
    },
  });
  ```

  ```python Python theme={null}
  credential = await kernel.credentials.create(
      name="my-netflix-login",
      domain="netflix.com",
      values={
          "email": "user@netflix.com",
          "password": "secretpassword123",
      },
  )
  ```

  ```go Go theme={null}
  credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{
  	CreateCredentialRequest: kernel.CreateCredentialRequestParam{
  		Name:   "my-netflix-login",
  		Domain: "netflix.com",
  		Values: map[string]string{
  			"email":    "user@netflix.com",
  			"password": "secretpassword123",
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
  _ = credential
  ```
</CodeGroup>

Then link the credential when creating a connection:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const auth = await kernel.auth.connections.create({
    domain: 'netflix.com',
    profile_name: 'my-profile',
    credential: { name: credential.name },
  });

  // Start login with stored credentials
  const login = await kernel.auth.connections.login(auth.id);
  ```

  ```python Python theme={null}
  auth = await kernel.auth.connections.create(
      domain="netflix.com",
      profile_name="my-profile",
      credential={"name": credential.name},
  )

  # Start login with stored credentials
  login = await kernel.auth.connections.login(auth.id)
  ```

  ```go Go theme={null}
  auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{
  	ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{
  		Domain:      "netflix.com",
  		ProfileName: "my-profile",
  		Credential: kernel.ManagedAuthCreateRequestCredentialParam{
  			Name: kernel.String(credential.Name),
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }

  // Start login with stored credentials
  login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{})
  if err != nil {
  	panic(err)
  }
  _ = login
  ```
</CodeGroup>

### 2FA with TOTP

For sites with authenticator app 2FA, include `totp_secret` so Kernel can generate a fresh code during automatic login and reauthentication:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const credential = await kernel.credentials.create({
    name: 'my-login',
    domain: 'github.com',
    values: {
      username: 'my-username',
      password: 'my-password',
    },
    totp_secret: 'JBSWY3DPEHPK3PXP',  // From authenticator app setup
  });
  ```

  ```python Python theme={null}
  credential = await kernel.credentials.create(
      name="my-login",
      domain="github.com",
      values={
          "username": "my-username",
          "password": "my-password",
      },
      totp_secret="JBSWY3DPEHPK3PXP",  # From authenticator app setup
  )
  ```

  ```go Go theme={null}
  credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{
  	CreateCredentialRequest: kernel.CreateCredentialRequestParam{
  		Name:   "my-login",
  		Domain: "github.com",
  		Values: map[string]string{
  			"username": "my-username",
  			"password": "my-password",
  		},
  		TotpSecret: kernel.String("JBSWY3DPEHPK3PXP"), // From authenticator app setup
  	},
  })
  if err != nil {
  	panic(err)
  }
  _ = credential
  ```
</CodeGroup>

### SSO / OAuth

For sites with "Sign in with Google/GitHub/Microsoft", set `sso_provider` so Kernel can select the matching SSO route. Automatic completion depends on the provider's login requirements.

Common SSO provider domains (Google, Microsoft, Okta, Auth0, GitHub, etc.) are allowed by default, so you don't need to add them to `allowed_domains`:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const credential = await kernel.credentials.create({
    name: 'my-google-login',
    domain: 'accounts.google.com',
    sso_provider: 'google',
    values: {
      email: 'user@gmail.com',
      password: 'password',
    },
  });

  const auth = await kernel.auth.connections.create({
    domain: 'target-site.com',
    profile_name: 'my-profile',
    credential: { name: credential.name },
  });
  ```

  ```python Python theme={null}
  credential = await kernel.credentials.create(
      name="my-google-login",
      domain="accounts.google.com",
      sso_provider="google",
      values={
          "email": "user@gmail.com",
          "password": "password",
      },
  )

  auth = await kernel.auth.connections.create(
      domain="target-site.com",
      profile_name="my-profile",
      credential={"name": credential.name},
  )
  ```

  ```go Go theme={null}
  credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{
  	CreateCredentialRequest: kernel.CreateCredentialRequestParam{
  		Name:        "my-google-login",
  		Domain:      "accounts.google.com",
  		SSOProvider: kernel.String("google"),
  		Values: map[string]string{
  			"email":    "user@gmail.com",
  			"password": "password",
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }

  auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{
  	ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{
  		Domain:      "target-site.com",
  		ProfileName: "my-profile",
  		Credential: kernel.ManagedAuthCreateRequestCredentialParam{
  			Name: kernel.String(credential.Name),
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }
  _ = auth
  ```
</CodeGroup>

## Partial Credentials

Credentials don't need to contain every field required by the login form. You can store what you have and collect the necessary fields from the user. `auth.connections.login()` pauses for missing values.

As an example, the below credential has email + TOTP secret stored (and automatically handled), but no password. The password is dynamically collected from the user using Kernel's Hosted UI or your Programmatic flow:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const credential = await kernel.credentials.create({
    name: 'my-login',
    domain: 'example.com',
    values: { email: 'user@example.com' },  // No password
    totp_secret: 'JBSWY3DPEHPK3PXP',
  });

  const auth = await kernel.auth.connections.create({
    domain: 'example.com',
    profile_name: 'my-profile',
    credential: { name: credential.name },
  });

  const login = await kernel.auth.connections.login(auth.id);

  // Stream state changes and submit the missing password
  const authEvents = await kernel.auth.connections.follow(auth.id);
  for await (const event of authEvents) {
    const passwordField = event.fields?.find(field => field.ref === 'password');
    if (
      event.event === 'managed_auth_state' &&
      event.flow_step === 'AWAITING_INPUT' &&
      event.interaction_id &&
      passwordField
    ) {
      // Only password is pending; email is filled from the stored credential.
      await kernel.auth.connections.submit(auth.id, {
        interaction_id: event.interaction_id,
        field_values: { [passwordField.id]: 'user-provided-password' },
      });
    }
  }
  // TOTP auto-submitted from credential → SUCCESS
  ```

  ```python Python theme={null}
  credential = await kernel.credentials.create(
      name="my-login",
      domain="example.com",
      values={"email": "user@example.com"},  # No password
      totp_secret="JBSWY3DPEHPK3PXP",
  )

  auth = await kernel.auth.connections.create(
      domain="example.com",
      profile_name="my-profile",
      credential={"name": credential.name},
  )

  login = await kernel.auth.connections.login(auth.id)

  # Stream state changes and submit the missing password
  auth_events = await kernel.auth.connections.follow(auth.id)
  async for event in auth_events:
      password_field = next(
          (field for field in (event.fields or []) if field.ref == "password"),
          None,
      )
      if (
          event.event == "managed_auth_state"
          and event.flow_step == "AWAITING_INPUT"
          and event.interaction_id
          and password_field
      ):
          # Only password is pending; email is filled from the stored credential.
          await kernel.auth.connections.submit(
              auth.id,
              interaction_id=event.interaction_id,
              field_values={password_field.id: "user-provided-password"},
          )
  # TOTP auto-submitted from credential → SUCCESS
  ```

  ```go Go theme={null}
  credential, err := client.Credentials.New(ctx, kernel.CredentialNewParams{
  	CreateCredentialRequest: kernel.CreateCredentialRequestParam{
  		Name:   "my-login",
  		Domain: "example.com",
  		Values: map[string]string{
  			"email": "user@example.com", // No password
  		},
  		TotpSecret: kernel.String("JBSWY3DPEHPK3PXP"),
  	},
  })
  if err != nil {
  	panic(err)
  }

  auth, err := client.Auth.Connections.New(ctx, kernel.AuthConnectionNewParams{
  	ManagedAuthCreateRequest: kernel.ManagedAuthCreateRequestParam{
  		Domain:      "example.com",
  		ProfileName: "my-profile",
  		Credential: kernel.ManagedAuthCreateRequestCredentialParam{
  			Name: kernel.String(credential.Name),
  		},
  	},
  })
  if err != nil {
  	panic(err)
  }

  login, err := client.Auth.Connections.Login(ctx, auth.ID, kernel.AuthConnectionLoginParams{})
  if err != nil {
  	panic(err)
  }
  _ = login

  // Stream state changes and submit the missing password
  authEvents := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
  for authEvents.Next() {
  	event := authEvents.Current()
  	if event.Event != "managed_auth_state" || event.FlowStep != "AWAITING_INPUT" || event.InteractionID == "" {
  		continue
  	}
  	for _, field := range event.Fields {
  		if field.Ref != "password" {
  			continue
  		}
  		// Only password is pending; email is filled from the stored credential.
  		_, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{
  			SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{
  				InteractionID: kernel.String(event.InteractionID),
  				FieldValues: map[string]string{
  					field.ID: "user-provided-password",
  				},
  			},
  		})
  		if err != nil {
  			panic(err)
  		}
  		break
  	}
  }
  if err := authEvents.Err(); err != nil {
  	panic(err)
  }
  // TOTP auto-submitted from credential → SUCCESS
  ```
</CodeGroup>

This is useful when you want to:

* Store TOTP secrets but have users enter their password each time
* Pre-fill username/email but collect password at runtime
* Merge user-provided values into an existing credential automatically on successful login

## Security

| Feature                | Description                                          |
| ---------------------- | ---------------------------------------------------- |
| **Encrypted at rest**  | Values encrypted using per-organization keys         |
| **Write-only**         | Values cannot be retrieved via API after creation    |
| **Never logged**       | Values are never written to logs                     |
| **Never shared**       | Values are never passed to LLMs                      |
| **Isolated execution** | Authentication runs in isolated browser environments |

## Notes

* The `values` object is flexible and can be used to store whatever fields the login form needs (`email`, `username`, `company_id`, etc.)
* Deleting a credential unlinks it from associated connections so they can no longer auto-authenticate
* Use one credential per account. We recommend creating separate credentials for different user accounts
