Skip to content

Sign users in

Your page draws the sign-in form and sends what the user types to EdgeKit. EdgeKit sends the emails, checks the code or token, creates the user when the address is new, and sets the session cookie.

The samples below call your tenant host. From a page, call the same path under /edgekit/ on your own domain, which is what the browser client does: id.signIn.emailOtp, id.signIn.verifyEmailOtp, id.signIn.magicLink, id.consumeMagicLink and id.signIn.google on createClient.

The routes that send a code or a link, and the routes that sign in with a code or with Google, answer only where EdgeKit sign-in is open: at an edgekit_only site, or at a dual site where EdgeKit has turned it on. Everywhere else they answer 404 not_found. See Choose who signs users in.

GET /v1/id/config tells your page what to render. It needs no credential and answers any origin.

Terminal window
curl https://your-tenant-host/v1/id/config \
-H 'x-edgekit-site-id: site_example'
const response = await fetch('https://your-tenant-host/v1/id/config', {
headers: { 'x-edgekit-site-id': 'site_example' },
});
const config = await response.json();
{
"methods": [
{ "name": "email-otp", "registrationTraits": [] },
{ "name": "magic-link", "registrationTraits": [] }
],
"captcha": null,
"flow": {
"otpLength": 6,
"otpExpiresIn": 300,
"magicLinkExpiresIn": 300,
"resendCooldown": 60
},
"operations": [
"sign-in",
"sign-out",
"get-session",
"change-identifier",
"update-profile"
],
"idMode": "edgekit_only"
}
  • methods: the sign-in methods to offer, in order. email-otp carries the traits a new user can answer at sign-up, and google, when present, carries the clientId your page passes to Google. methods is empty where EdgeKit sign-in is closed. It tells your page which buttons to draw; it does not switch a method off.
  • captcha: a widget and site key for your page to show, or null. EdgeKit does not check a captcha answer on any sign-in route.
  • flow: the code length, the code and link lifetimes in seconds, and resendCooldown, how many seconds your page should wait before offering to send again. EdgeKit enforces its own limit per address, below, not the cooldown.
  • operations: what this site serves. See Choose who signs users in.
  • idMode: this site’s id mode.

The answer is cached for up to 5 minutes by shared caches and 1 minute by the browser, so a change to these settings can take 5 minutes to reach your pages. A site that is not active or not in your newest deployment answers 404 not_found, Unknown site..

The email code is the method that can record a new user’s traits, and the one with no link to open.

  1. Send the code. EdgeKit answers 200 whether or not a user holds the address, so the route never tells anyone which addresses exist.

    Terminal window
    curl -X POST https://your-tenant-host/v1/id/email-otp/send-verification-otp \
    -H 'x-edgekit-site-id: site_example' \
    -H 'content-type: application/json' \
    -d '{"email": "ada@example.com"}'
  2. Send the code the user typed. On success EdgeKit answers 200 with the user and the session, and sets the session cookie.

    const response = await fetch(
    'https://your-tenant-host/v1/id/sign-in/email-otp',
    {
    method: 'POST',
    credentials: 'include',
    headers: {
    'x-edgekit-site-id': 'site_example',
    'content-type': 'application/json',
    },
    body: JSON.stringify({
    email: 'ada@example.com',
    otp: '482913',
    firstName: 'Ada',
    lastName: 'Lovelace',
    userTraits: { job_industry: '01926f3a-8b51-7e99-9aaa-8b9c0d1e2f3a' },
    }),
    },
    );
    const { user, session } = await response.json();

If no user holds the address, this request creates one, verified, and attaches them to this site. firstName, lastName (1 to 100 characters each) and userTraits are read on that request only: a user who already exists keeps the name and traits they have. A list trait’s value is the id of one of its values, not its label. When the request carries userTraits, they are checked against your traits, including required ones, and a failure answers 422 unprocessable_entity. A request without userTraits registers the user with no traits checked, so your sign-up form must ask the questions. See Ask users about themselves.

A user who already exists is signed in, and an unverified address becomes verified.

  1. Send the link. name is optional, and is the new user’s name when the address is new. callbackURL is optional and is carried in the link for your page to use afterwards. It must be a relative path, or an absolute URL on one of the site’s Redirect origins.

    Terminal window
    curl -X POST https://your-tenant-host/v1/id/sign-in/magic-link \
    -H 'x-edgekit-site-id: site_example' \
    -H 'content-type: application/json' \
    -d '{"email": "ada@example.com", "callbackURL": "/account"}'
  2. The email’s link opens on the host EdgeKit received the send request on: your tenant host when your forwarder sets Host to it, as Mount /edgekit/ on your domain says. Its path is /edgekit/v1/id/magic-link/verify?token=…&callbackURL=… for a request that came through the mount, and /v1/id/magic-link/verify?… otherwise. EdgeKit answers both 404 not_found, and no setting points the link at a page you host, so a user who opens it cannot finish signing in. Offer the email code instead.

  3. A page that holds a token redeems it with POST /v1/id/magic-link/consume. Redeem it when the user selects a button, not on page load: mail scanners open emailed links, and a scanner that redeems the token leaves the user a link that no longer works.

    async function consumeMagicLink(token: string) {
    return fetch('/edgekit/v1/id/magic-link/consume', {
    method: 'POST',
    credentials: 'include',
    headers: {
    'x-edgekit-site-id': 'site_example',
    'content-type': 'application/json',
    },
    body: JSON.stringify({ token }),
    });
    }

A token works once. A used, unknown or expired token answers 400 bad_request. A new address becomes a verified user with the name sent in step 1. A magic link records no traits, and a trait sent with it is dropped without an error. While magic link is offered on any of your sites, no trait can be marked required.

Google sign-in is offered when EdgeKit holds your organization’s own Google OAuth client. GET /v1/id/config then lists google with its clientId. Users see your Google client’s consent screen.

  1. Your page loads Google Identity Services and renders Google’s button with that clientId. The browser client does not load it for you.

  2. Send the credential Google hands your page as idToken. Send nonce only if you asked Google for one, with the same value.

    const response = await fetch('/edgekit/v1/id/sign-in/google', {
    method: 'POST',
    credentials: 'include',
    headers: {
    'x-edgekit-site-id': 'site_example',
    'content-type': 'application/json',
    },
    body: JSON.stringify({ idToken: googleCredential }),
    });

There is no redirect flow. A new address becomes a user with the first and last name from Google. The route records no traits: a Google user answers them later, through PATCH /v1/id/user-details. Retrying with the same token while it is valid is safe, and each call creates a new session.

Where EdgeKit sign-in is closed, or your organization has no Google client, the route answers 404 not_found.

What Limit
Sign-in emails to one address 3 a minute, counting codes, magic links and email-change codes together
Code attempts for one address 3 a minute, across every code sent to it
Wrong guesses at one code 3, then the code is spent
Email code 6 digits, valid for 5 minutes
Magic link Valid for 5 minutes, one use

The per-address limits ignore a +tag and dots before the @, so ada+news@example.com and a.da@example.com share ada@example.com’s allowance. A limit reached answers 429 too_many_requests with Retry-After: 60.

Status Code Message Cause
400 bad_request That code is not valid. The code is wrong, or no code is outstanding.
400 bad_request That code has expired. Request a new one. The code is older than 5 minutes.
400 bad_request That sign-in could not be verified. Start again. A Google token or magic-link token did not verify.
400 bad_request That address already has an account. Sign in with an emailed code once, then Google will work. Google sign-in for an existing user whose address is not verified yet.
400 bad_request That Google account has no email address to sign in with. The Google account has no address.
403 forbidden That redirect target is not registered for this site. callbackURL is on an origin not in Redirect origins.
404 not_found No route matches this path. EdgeKit sign-in is closed at this site, or no Google client.
422 unprocessable_entity Request validation failed. A malformed address, a code that is not 6 digits, a callbackURL that is not a path or an http(s) URL, or traits that fail their checks.
429 too_many_requests Too many sign-in emails for this address. Try again in a minute. The send limit for this address.
429 too_many_requests Too many code attempts for this address. Try again in a minute. The attempt limit for this address.
429 too_many_requests Too many attempts. Request a new code. Three wrong guesses at this code.
500 internal_error Varies EdgeKit could not send the email, or failed in another way.

Every refusal is in the error code reference. The browser client answers a refusal as failed with its code, and no answer at all as unavailable.

The emails’ wording is fixed. The code arrives with the subject Your sign-in code, and the link with Sign in. They go out from EdgeKit’s own address until your organization sets its transactional mail settings, which set the sender name and address: see Site settings. You set them on any site’s screen in Delivery › Delivery › Sites, with the Owner or Admin role. They apply to every site in your organization and take effect at once.