-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
2,328 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Available in your WorkOS dashboard | ||
WORKOS_CLIENT_ID= | ||
WORKOS_API_KEY= | ||
WORKOS_REDIRECT_URI= | ||
WORKOS_COOKIE_PASSWORD= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,32 @@ | ||
# Welcome to Remix! | ||
# Remix integration example using AuthKit | ||
|
||
- [Remix Docs](https://remix.run/docs) | ||
An example application demonstrating how to authenticate users with AuthKit and the WorkOS Node SDK. | ||
|
||
## Development | ||
> Refer to the [User Management](https://workos.com/docs/user-management) documentation for reference. | ||
From your terminal: | ||
## Prerequisites | ||
|
||
```sh | ||
npm run dev | ||
``` | ||
You will need a [WorkOS account](https://dashboard.workos.com/signup). | ||
|
||
This starts your app in development mode, rebuilding assets on file changes. | ||
## Running the example | ||
|
||
## Deployment | ||
|
||
First, build your app for production: | ||
Make sure the following values are present in your `.env.local` environment variables file. The client ID and API key can be found in the [WorkOS dashboard](https://dashboard.workos.com), and the redirect URI can also be configured there. | ||
|
||
```sh | ||
npm run build | ||
WORKOS_CLIENT_ID="client_..." # retrieved from the WorkOS dashboard | ||
WORKOS_API_KEY="sk_test_..." # retrieved from the WorkOS dashboard | ||
WORKOS_REDIRECT_URI="http://localhost:3000/callback" # configured in the WorkOS dashboard | ||
WORKOS_COOKIE_PASSWORD="<your password>" # generate a secure password here | ||
``` | ||
|
||
Then run the app in production mode: | ||
`WORKOS_COOKIE_PASSWORD` is the private key used to encrypt the session cookie. It has to be at least 32 characters long. You can use the [1Password generator](https://1password.com/password-generator/) or the `openssl` library to generate a strong password via the command line: | ||
|
||
```sh | ||
npm start | ||
``` | ||
openssl rand -base64 24 | ||
``` | ||
|
||
Now you'll need to pick a host to deploy it to. | ||
|
||
### DIY | ||
|
||
If you're familiar with deploying node applications, the built-in Remix app server is production-ready. | ||
|
||
Make sure to deploy the output of `remix build` | ||
Run the following command and navigate to [http://localhost:3000](http://localhost:3000). | ||
|
||
- `build/` | ||
- `public/build/` | ||
```bash | ||
npm run dev | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { getAuthorizationUrl } from './get-authorization-url'; | ||
import { terminateSession } from './session'; | ||
|
||
async function getSignInUrl() { | ||
return getAuthorizationUrl({ screenHint: 'sign-in' }); | ||
} | ||
|
||
async function getSignUpUrl() { | ||
return getAuthorizationUrl({ screenHint: 'sign-up' }); | ||
} | ||
|
||
async function signOut(request: Request) { | ||
return await terminateSession(request); | ||
} | ||
|
||
export { getSignInUrl, getSignUpUrl, signOut }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { HandleAuthOptions } from './interfaces'; | ||
import { WORKOS_CLIENT_ID } from '../.server/env-variables'; | ||
import { workos } from './workos'; | ||
import { encryptSession } from './session'; | ||
import { getSession, commitSession, cookieName } from './cookie'; | ||
import { redirect, json, LoaderFunctionArgs } from '@remix-run/node'; | ||
|
||
export function authLoader(options: HandleAuthOptions = {}) { | ||
return async function loader({ request }: LoaderFunctionArgs) { | ||
const { returnPathname: returnPathnameOption = '/' } = options; | ||
|
||
const url = new URL(request.url); | ||
|
||
const code = url.searchParams.get('code'); | ||
const state = url.searchParams.get('state'); | ||
const returnPathname = state ? JSON.parse(atob(state)).returnPathname : '/'; | ||
|
||
if (code) { | ||
try { | ||
const { accessToken, refreshToken, user, impersonator } = | ||
await workos.userManagement.authenticateWithCode({ | ||
clientId: WORKOS_CLIENT_ID, | ||
code, | ||
}); | ||
|
||
// Clean up params | ||
url.searchParams.delete('code'); | ||
url.searchParams.delete('state'); | ||
|
||
// Redirect to the requested path and store the session | ||
url.pathname = returnPathname ?? returnPathnameOption; | ||
|
||
// The refreshToken should never be accesible publicly, hence why we encrypt it in the cookie session | ||
// Alternatively you could persist the refresh token in a backend database | ||
const encryptedSession = await encryptSession({ | ||
accessToken, | ||
refreshToken, | ||
user, | ||
impersonator, | ||
}); | ||
|
||
const session = await getSession(cookieName); | ||
|
||
session.set('jwt', encryptedSession); | ||
const cookie = await commitSession(session); | ||
|
||
return redirect(url.toString(), { | ||
headers: { | ||
'Set-Cookie': cookie, | ||
}, | ||
}); | ||
} catch (error) { | ||
const errorRes = { | ||
error: error instanceof Error ? error.message : String(error), | ||
}; | ||
|
||
console.error(errorRes); | ||
|
||
return errorResponse(); | ||
} | ||
} | ||
|
||
function errorResponse() { | ||
return json( | ||
{ | ||
error: { | ||
message: 'Something went wrong', | ||
description: | ||
'Couldn’t sign in. If you are not sure what happened, please contact your organization admin.', | ||
}, | ||
}, | ||
{ status: 500 } | ||
); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { | ||
WORKOS_REDIRECT_URI, | ||
WORKOS_COOKIE_MAX_AGE, | ||
WORKOS_COOKIE_PASSWORD, | ||
} from './env-variables'; | ||
import { createCookieSessionStorage } from '@remix-run/node'; | ||
|
||
const redirectUrl = new URL(WORKOS_REDIRECT_URI); | ||
const isSecureProtocol = redirectUrl.protocol === 'https:'; | ||
|
||
const cookieName = 'wos-session'; | ||
const cookieOptions = { | ||
path: '/', | ||
httpOnly: true, | ||
secure: isSecureProtocol, | ||
sameSite: 'lax' as const, | ||
// Defaults to 400 days, the maximum allowed by Chrome | ||
// It's fine to have a long cookie expiry date as the access/refresh tokens | ||
// act as the actual time-limited aspects of the session. | ||
maxAge: WORKOS_COOKIE_MAX_AGE | ||
? parseInt(WORKOS_COOKIE_MAX_AGE, 10) | ||
: 60 * 60 * 24 * 400, | ||
secrets: [WORKOS_COOKIE_PASSWORD], | ||
}; | ||
const { getSession, commitSession, destroySession } = | ||
createCookieSessionStorage({ | ||
cookie: { | ||
name: cookieName, | ||
...cookieOptions, | ||
}, | ||
}); | ||
|
||
export { cookieName, getSession, commitSession, destroySession }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
function getEnvVariable(name: string): string { | ||
const envVariable = process.env[name]; | ||
if (!envVariable) { | ||
throw new Error(`${name} environment variable is not set`); | ||
} | ||
return envVariable; | ||
} | ||
|
||
function getOptionalEnvVariable(name: string): string | undefined { | ||
return process.env[name]; | ||
} | ||
|
||
const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID'); | ||
const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY'); | ||
const WORKOS_REDIRECT_URI = getEnvVariable('WORKOS_REDIRECT_URI'); | ||
const WORKOS_COOKIE_PASSWORD = getEnvVariable('WORKOS_COOKIE_PASSWORD'); | ||
const WORKOS_API_HOSTNAME = getOptionalEnvVariable('WORKOS_API_HOSTNAME'); | ||
const WORKOS_API_HTTPS = getOptionalEnvVariable('WORKOS_API_HTTPS'); | ||
const WORKOS_API_PORT = getOptionalEnvVariable('WORKOS_API_PORT'); | ||
const WORKOS_COOKIE_MAX_AGE = getOptionalEnvVariable('WORKOS_COOKIE_MAX_AGE'); | ||
|
||
if (WORKOS_COOKIE_PASSWORD.length < 32) { | ||
throw new Error('WORKOS_COOKIE_PASSWORD must be at least 32 characters long'); | ||
} | ||
|
||
export { | ||
WORKOS_CLIENT_ID, | ||
WORKOS_API_KEY, | ||
WORKOS_REDIRECT_URI, | ||
WORKOS_COOKIE_PASSWORD, | ||
WORKOS_API_HOSTNAME, | ||
WORKOS_API_HTTPS, | ||
WORKOS_API_PORT, | ||
WORKOS_COOKIE_MAX_AGE, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { workos } from './workos'; | ||
import { WORKOS_CLIENT_ID, WORKOS_REDIRECT_URI } from './env-variables'; | ||
import { GetAuthURLOptions } from './interfaces'; | ||
|
||
async function getAuthorizationUrl(options: GetAuthURLOptions = {}) { | ||
const { returnPathname, screenHint } = options; | ||
|
||
return workos.userManagement.getAuthorizationUrl({ | ||
provider: 'authkit', | ||
clientId: WORKOS_CLIENT_ID, | ||
redirectUri: WORKOS_REDIRECT_URI, | ||
state: returnPathname | ||
? btoa(JSON.stringify({ returnPathname })) | ||
: undefined, | ||
screenHint, | ||
}); | ||
} | ||
|
||
export { getAuthorizationUrl }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { User } from '@workos-inc/node'; | ||
|
||
export interface HandleAuthOptions { | ||
returnPathname?: string; | ||
} | ||
|
||
export interface Impersonator { | ||
email: string; | ||
reason: string | null; | ||
} | ||
export interface Session { | ||
accessToken: string; | ||
refreshToken: string; | ||
user: User; | ||
impersonator?: Impersonator; | ||
} | ||
|
||
export interface UserInfo { | ||
user: User; | ||
sessionId: string; | ||
organizationId?: string; | ||
role?: string; | ||
impersonator?: Impersonator; | ||
accessToken: string; | ||
} | ||
export interface NoUserInfo { | ||
user: null; | ||
sessionId?: undefined; | ||
organizationId?: undefined; | ||
role?: undefined; | ||
impersonator?: undefined; | ||
} | ||
|
||
export interface AccessToken { | ||
sid: string; | ||
org_id?: string; | ||
role?: string; | ||
} | ||
|
||
export interface GetAuthURLOptions { | ||
screenHint?: 'sign-up' | 'sign-in'; | ||
returnPathname?: string; | ||
} | ||
|
||
export interface AuthkitMiddlewareAuth { | ||
enabled: boolean; | ||
unauthenticatedPaths: string[]; | ||
} | ||
|
||
export interface AuthkitMiddlewareOptions { | ||
debug?: boolean; | ||
middlewareAuth?: AuthkitMiddlewareAuth; | ||
} |
Oops, something went wrong.