Creating, finding and updating users, and moving them in and out in bulk. For roles and permissions see RBAC; for MFA enrolment see MFA.

Assumes Management API credentials are configured — see Configuration.

Auth0 reference: Users, Jobs.

The basics

alias Auth0Client.Management.User

User.all(%{q: "email:*@example.com"})
#=> {:ok, [%{"user_id" => "auth0|abc123", ...}]}

User.get("auth0|abc123", %{fields: "email,name"})
#=> {:ok, %{"email" => "someone@example.com", "name" => "Someone"}}

User.create("Username-Password-Authentication", %{
  email: "someone@example.com",
  password: "a-strong-password"
})

User.update("auth0|abc123", %{app_metadata: %{admin: true}})

User.delete("auth0|abc123")
#=> :ok

Finding a user by email

If you have an address and want the user, use by_email/2 rather than a search:

User.by_email("someone@example.com")
#=> {:ok, [%{"user_id" => "auth0|abc123", ...}]}

It is an exact, case-insensitive match, and returns a list because one address can belong to several identities across connections. User.all/1 would go through the search engine instead, with the caveats below.

Searching

User.all/1 uses the v3 search engine, the only one Auth0 still supports. It differs from v2 in ways that return nothing rather than erroring:

  • .raw sub-fields are gone — query the field directly
  • connection became identities.connection
  • matching is case-sensitive
  • results cap at 1,000 records

For anything larger, run an export rather than paginating.

Password resets

To email the user a reset link, use Auth0Client.Authentication.change_password/3 — see Logging users in. To generate a link yourself:

Auth0Client.Management.Ticket.password_change(%{user_id: "auth0|abc123"})
#=> {:ok, %{"ticket" => "https://your-tenant.auth0.com/lo/reset?ticket=..."}}

Sessions and tokens

From the user's side — everything at once:

User.sessions("auth0|abc123")
User.delete_sessions("auth0|abc123")       # signs them out everywhere

User.refresh_tokens("auth0|abc123")
User.delete_refresh_tokens("auth0|abc123")

User.revoke_access("auth0|abc123")         # both at once

Or one at a time, using the ids those listings return:

alias Auth0Client.Management.{RefreshToken, Session}

Session.get(session_id)
Session.delete(session_id)                 # ends the session
Session.revoke(session_id)                 # ends it AND its refresh tokens

RefreshToken.get(token_id)
RefreshToken.delete(token_id)
RefreshToken.revoke(%{ids: [token_id]})    # in bulk, up to 100

delete and revoke differ where it matters

Session.delete/1 ends the session but leaves its refresh tokens alive, so an application holding one gets a new access token immediately. After a credential compromise, Session.revoke/1 is the call that actually locks the user out.

RefreshToken.revoke/1 takes an exclusive choice, not a set of filters: ids, or user_id, or user_id + client_id, or those plus audience. client_id alone is rejected, and ids combines with nothing.

RefreshToken.all/1 is the same listing as User.refresh_tokens/2user_id is required either way — except that it also filters by client_id.

To revoke a refresh token as its holder rather than as an administrator, with no Management token involved, use Auth0Client.Authentication.Token.revoke/3 — see Logging users in.

To sign a user out of every application, not just this tenant's, Auth0Client.Authentication.global_token_revocation/3 is Auth0's Universal Logout endpoint. It authenticates with a bearer token you supply rather than a management token, and — like everything above — it destroys sessions and refresh tokens but leaves already-issued access tokens working until they expire.

A grant records that a user consented to an application's scopes. Deleting one makes Auth0 prompt for consent again and invalidates the refresh tokens issued under it.

alias Auth0Client.Management.Grant

Grant.all(user_id: "auth0|abc123")
Grant.delete("cgr_abc123")                 # one application
Grant.delete_by_user("auth0|abc123")       # every application, all at once

delete_by_user/1 takes the user id as a query parameter rather than a path segment, which is why it is a separate function.

Bulk import and export

Jobs are asynchronous: they return immediately and do the work in the background, so start one, poll it, then check for errors.

alias Auth0Client.Management.Job

# Export — poll until the job carries a `location` to download from
{:ok, job} = Job.users_exports(%{connection_id: "con_abc123", format: "json"})
{:ok, %{"status" => "completed", "location" => url}} = Job.get(job["id"])

# Import — takes the contents of a JSON file, or a stream for a large one
{:ok, job} = Job.users_imports(File.read!("users.json"), "con_abc123", %{upsert: true})
{:ok, job} = Job.users_imports(File.stream!("users.json"), "con_abc123")

Import is the one endpoint Auth0 accepts only as multipart/form-data; the library handles that for you, including turning upsert: true into the string form Auth0 expects.

Checking the result

Job.errors/1 answers :ok when the import was clean and {:ok, errors} when some records failed — Auth0 returns 204 in the first case. The success path is the surprising one, so match both:

case Job.errors(job["id"]) do
  :ok -> :imported_cleanly
  {:ok, errors} -> handle(errors)
end