Skip to content

Commit

Permalink
Merge branch 'main' into alexcarpenter/sdki-747-launch-draft-docs-cha…
Browse files Browse the repository at this point in the history
…nges
  • Loading branch information
alexcarpenter committed Dec 17, 2024
2 parents 507f71d + a2866fc commit 0c92c9d
Show file tree
Hide file tree
Showing 20 changed files with 589 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ To make the setup process easier, it's recommended to keep two browser tabs open

1. In the Clerk Dashboard, navigate to the [**SSO Connections**](https://dashboard.clerk.com/last-active?path=user-authentication/sso-connections) page.
1. Select **Add connection** and select **For specific domains**.
1. Under **Third party**, select **OpenID Connect (OIDC)**.
1. Under **OpenID Connect (OIDC)**, select **Custom OIDC Provider**.
1. Add the **Name** of the connection.
1. Add the **Key** of the provider. This is the provider's unique identifier (cannot be changed after creation).
1. Add the **Specific Domain** that you want to allow this connection for. This is the domain of the users you want to allow to sign in to your app.
Expand Down
93 changes: 35 additions & 58 deletions docs/custom-flows/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ description: Provide your users with useful information about the errors being r

Clerk-related errors are returned as an array of [`ClerkAPIError`](/docs/references/javascript/types/clerk-api-error) objects. These errors contain a `code`, `message`, `longMessage` and `meta` property. These properties can be used to provide your users with useful information about the errors being returned from sign-up and sign-in requests.

This guide demonstrates how to handle Clerk-related errors when building custom flows.

> [!TIP]
> To see a list of all possible errors, refer to the [Errors](/docs/errors/overview) documentation.
Expand All @@ -18,7 +16,7 @@ The following example uses the [email & password sign-in custom flow](/docs/cust
<Tab>
This example is written for Next.js App Router but it can be adapted for any React meta framework, such as Remix.

```tsx {{ filename: 'app/sign-in/[[...sign-in]]/page.tsx' }}
```tsx {{ filename: 'app/sign-in/[[...sign-in]]/page.tsx', mark: [[6, 7], 13, [21, 22], [45, 48], [79, 85]] }}
'use client'

import * as React from 'react'
Expand All @@ -39,7 +37,7 @@ The following example uses the [email & password sign-in custom flow](/docs/cust
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()

// clear any errors that may have occured during previous form submission
// Clear any errors that may have occurred during previous form submission
setErrors(undefined)

if (!isLoaded) {
Expand All @@ -48,21 +46,20 @@ The following example uses the [email & password sign-in custom flow](/docs/cust

// Start the sign-in process using the email and password provided
try {
const completeSignIn = await signIn.create({
const signInAttempt = await signIn.create({
identifier: email,
password,
})

// This is mainly for debugging while developing.
if (completeSignIn.status !== 'complete') {
console.log(JSON.stringify(completeSignIn, null, 2))
}

// If sign-in process is complete, set the created session as active
// and redirect the user
if (completeSignIn.status === 'complete') {
await setActive({ session: completeSignIn.createdSessionId })
if (signInAttempt.status === 'complete') {
await setActive({ session: signInAttempt.createdSessionId })
router.push('/')
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signInAttempt, null, 2))
}
} catch (err) {
if (isClerkAPIResponseError(err)) setErrors(err.errors)
Expand Down Expand Up @@ -113,7 +110,7 @@ The following example uses the [email & password sign-in custom flow](/docs/cust

<Tab>
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html' }}
```html {{ filename: 'index.html', mark: [22] }}
<!doctype html>
<html lang="en">
<head>
Expand All @@ -124,32 +121,25 @@ The following example uses the [email & password sign-in custom flow](/docs/cust
<body>
<div id="signed-in"></div>

<div id="sign-up">
<h2>Sign up</h2>
<form id="sign-up-form">
<div id="sign-in">
<h2>Sign in</h2>
<form id="sign-in-form">
<label for="email">Enter email address</label>
<input type="email" name="email" id="sign-up-email" />
<input name="email" id="sign-in-email" />
<label for="password">Enter password</label>
<input type="password" name="password" id="sign-up-password" />
<input name="password" id="sign-in-password" />
<button type="submit">Continue</button>
</form>
</div>

<form id="verifying" hidden>
<h2>Verify your email</h2>
<label for="code">Enter your verification code</label>
<input id="code" name="code" />
<button type="submit" id="verify-button">Verify</button>
</form>

<p id="error"></p>

<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
```

```js {{ filename: 'main.js' }}
```js {{ filename: 'main.js', mark: [[43, 49]] }}
import { Clerk } from '@clerk/clerk-js'

const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
Expand All @@ -160,52 +150,39 @@ The following example uses the [email & password sign-in custom flow](/docs/cust
if (clerk.user) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`
<div id="user-button"></div>
`

const userbuttonDiv = document.getElementById('user-button')

clerk.mountUserButton(userbuttonDiv)
} else {
// Handle the sign-up form
document.getElementById('sign-up-form').addEventListener('submit', async (e) => {
// Handle the sign-in form
document.getElementById('sign-in-form').addEventListener('submit', async (e) => {
e.preventDefault()

const formData = new FormData(e.target)
const emailAddress = formData.get('email')
const password = formData.get('password')

try {
// Start the sign-up process using the phone number method
await clerk.client.signUp.create({ emailAddress, password })
await clerk.client.signUp.prepareEmailAddressVerification()
// Hide sign-up form
document.getElementById('sign-up').setAttribute('hidden', '')
// Show verification form
document.getElementById('verifying').removeAttribute('hidden')
} catch (err) {
if (isClerkAPIResponseError(err)) {
const errors = err.errors
document.getElementById('error').textContent = errors[0].longMessage
}
console.error(JSON.stringify(err, null, 2))
}
})

// Handle the verification form
document.getElementById('verifying').addEventListener('submit', async (e) => {
const formData = new FormData(e.target)
const code = formData.get('code')

try {
// Verify the phone number
const verify = await clerk.client.signUp.attemptEmailAddressVerification({
code,
// Start the sign-in process
const signInAttempt = await clerk.client.signIn.create({
identifier: emailAddress,
password,
})

// Now that the user is created, set the session to active.
await clerk.setActive({ session: verify.createdSessionId })
} catch (err) {
// If the sign-in is complete, set the user as active
if (signInAttempt.status === 'complete') {
await clerk.setActive({ session: signInAttempt.createdSessionId })

location.reload()
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(signInAttempt, null, 2))
}
} catch (error) {
if (isClerkAPIResponseError(err)) {
const errors = err.errors
document.getElementById('error').textContent = errors[0].longMessage
Expand Down
103 changes: 103 additions & 0 deletions docs/how-clerk-works/cookies.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: Cookies
description: Learn how cookies enable secure authentication and manage browser-server interactions.
---

Cookies play a vital role in authentication, state management, and browser-server communication. By understanding their attributes, developers can ensure secure and efficient use in their applications. Clerk leverages cookies in a secure, privacy-compliant manner to facilitate seamless user authentication across domains and subdomains.

## What are cookies?

Cookies are small pieces of information stored in the browser and sent automatically alongside some requests coming from that browser.

By default, HTTP requests are "stateless" and lack memory of previous interactions. Cookies change this behavior, as they are stored long term and can be sent alongside each request.

### Setting cookies

Cookies are typically created by the server and communicated to the browser through the [`Set-Cookie` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). When the browser receives this header, it stores the cookie and includes it in future requests to the **same domain**. Here's an example:

```http
HTTP/2 200
Content-Type: text/html
Set-Cookie: session_id=sess123
<html>
<body>
<p>Hello, world!</p>
</body>
</html>
```

This response sets a `session_id` cookie with the value `sess123`. To view cookies in your browser's developer tools, navigate to the **Application** tab. Then in the **Storage** section, select **Cookies**. Here's an example from Clerk's website:

![cookies in browser console](/docs/images/how-clerk-works/devtools-cookies.png)

## Cookie domains and scope

Each cookie has a [`Domain`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent) that indicates **the domain from which the cookie was set**. This determines when the cookie will be included in requests.

For example:

- If a cookie is set by `example.com` without a `Domain` value, it will be sent only with requests _to_ `example.com`.
- If the cookie's `Domain` value is _explicitly_ set to `example.com`, it will also be sent with requests to subdomains like `sub.example.com`.
- However, cookies set by subdomains (e.g., `sub.example.com`) won't be sent with requests to the parent domain (`example.com`).

## Tracking cookies and privacy concerns

Historically, cookies were often used for tracking user behavior across websites.

For example, say you [hotlink](https://developer.mozilla.org/en-US/docs/Glossary/Hotlink) an image from `facebook.com` on to your website, `example.com`, as such:

```
<!doctype html>
<html>
<body>
<p>Check out this cool picture of me on vacation that I posted on FB</p>
<img src='http://facebook.com/images/h0e208whe8r0.jpg alt='Me on vacation' />
<body>
</html>
```

To display the image, `example.com` requests the image from `facebook.com`. Let's say Facebook's web server:

1. Retrieves the image
1. Creates an entry for you as a user in their database with a unique ID
1. Records that you visited `example.com`
1. Sets a cookie with your unique ID and [the `SameSite` value set to "none"](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)

The response from Facebook would look something like:

```
HTTP/2 200
Set-Cookie: fb_tracker=user123; SameSite=none
...the content of the image
```

Now let's say you visit another website, `foobar.com`, and that website is using a script from Facebook for tracking the effectiveness of Facebook ads. So now `foobar.com` makes a request to `facebook.com`, and Facebook gets back the cookie that it set from the image on `example.com`. But how could this happen? Let's revisit this statement one more time:

> Cookies are typically created by the server and communicated to the browser through the [`Set-Cookie` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). When the browser receives this header, it stores the cookie and includes it in future requests to the **same domain**.
Despite being set on `example.com`, the cookie's domain value is `facebook.com`, since it was set _by_ Facebook. And remember that the cookie was set with the `SameSite` value set to “none”, which, according to [the docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value), means that the browser sends the cookie **with both cross-site and same-site requests.**

So in this scenario, even if the cookie was set on a different website, the browser still sends the cookie back to Facebook, because the cookie has `facebook.com` set as its domain. Facebook then gets the cookie, is able to identify you as a user, and can identify that you also visited `foobar.com`. Any other site that you visit that loads anything from Facebook is an opportunity for Facebook to get back the cookie and use it to build a profile of your browsing habits and history.

Clerk doesn't do any of this type of tracking. However, this example is still helpful for building a foundation around the edges of how cookies are stored and transmitted.

## Controlling cross-site cookie behavior with `SameSite`

The `SameSite` attribute of cookies plays a crucial role in controlling cross-site cookie behavior. Clerk uses the `SameSite=Lax` setting to ensure a secure and user-friendly experience. This setting allows cookies to be sent with top-level navigation (e.g., clicking a link) but not with other cross-site requests (e.g., loading images).

See MDN's guide on [`SameSite`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value) for more details.

## Sharing cookies across subdomains

Sharing cookies across subdomains is controlled by the `Domain` attribute.

- A cookie **explicitly** set with `Domain=example.com` will be sent with requests to both `example.com` and `sub.example.com`.
- A cookie set without a domain value will only be sent to the domain that created it (e.g., `example.com`) and not its subdomains.

Cookies **explicitly** set with a domain appear in devtools with a leading period (e.g., `.example.com`). Cookies set without a domain value appear without the leading period (e.g., `example.com`).

## Controlling JavaScript access with `HttpOnly`

By default, cookies can be accessed via `document.cookie` in JavaScript. While this can be useful, it exposes cookies to risks like [Cross-Site Scripting (XSS) attacks](https://owasp.org/www-community/attacks/xss/). Setting the [`HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly) flag prevents JavaScript from accessing the cookie, enhancing security. These cookies are still sent with HTTP requests but are inaccessible to client-side scripts.
Loading

0 comments on commit 0c92c9d

Please sign in to comment.