Skip to content

Commit

Permalink
feat: add required environment variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Benehiko committed Sep 13, 2023
1 parent 0a1fb89 commit e9bc673
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 38 deletions.
45 changes: 38 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,49 @@ registration, account recovery, ... screens, please check out the

## Configuration

This application can be configured using two environment variables:
Below is a list of environment variables required by the Express.js service
to function properly.

- `KRATOS_PUBLIC_URL` (required): The URL where ORY Kratos's Public API is
located at. If this app and ORY Kratos are running in the same private
network, this should be the private network address (e.g.
In a local development run of the service using `npm run start`,
some of these values will be set by nodemon and is configured by the
`nodemon.json` file.

When using this UI with an Ory Network project, you can use `ORY_SDK_URL` instead
of `KRATOS_PUBLIC_URL` and `HYDRA_ADMIN_URL`.

Ory Identities requires the following variables to be set:

- `ORY_SDK_URL` or `KRATOS_PUBLIC_URL` (required): The URL where ORY Kratos's
Public API is located at. If this app and ORY Kratos are running in the
same private network, this should be the private network address (e.g.
`kratos-public.svc.cluster.local`).
- `KRATOS_BROWSER_URL` (optional) The browser accessible URL where ORY Kratos's
public API is located, only needed if it differs from `KRATOS_PUBLIC_URL`

Ory OAuth2 requires more setup to get CSRF cookies on the `/consent` endpoint.

- `ORY_SDK_URL` or `HYDRA_ADMIN_URL` (optional): The URL where Ory Hydra's
Public API is located at. If this app and Ory Hydra are running in the
same private network, this should be the private network address
(e.g. `hydra-admin.svc.cluster.local`)
- `COOKIE_SECRET` (required): Required for signing cookies. Must be
a string with at least 8 alphanumerical characters.
- `CSRF_COOKIE_SECRET` (optional): Required for the Consent route to set a CSRF
cookie with a hashed value. The value must be a string with at least 8
alphanumerical characters.
- `ORY_ADMIN_API_TOKEN` (optional): When using with an Ory Network project,
you should add the `ORY_ADMIN_API_TOKEN` for OAuth2 Consent flows.
- `CSRF_COOKIE_NAME` (optional): By default the CSRF cookie will be set to `ax-x-csrf-token`.
- `DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES` (optional) This environment variables
should only be used in local development when you do not have HTTPS setup.
This sets the CSRF cookies to `secure: false`, required for running locally.

Getting TLS working:

- `TLS_CERT_PATH` (optional): Path to certificate file. Should be set up
together with `TLS_KEY_PATH` to enable HTTPS.
- `TLS_KEY_PATH` (optional): Path to key file Should be set up together with
`TLS_CERT_PATH` to enable HTTPS.
- `KRATOS_BROWSER_URL` (optional) The browser accessible URL where ORY Kratos's
public API is located, only needed if it differs from `KRATOS_PUBLIC_URL`

This is the easiest mode as it requires no additional set up. This app runs on
port `:4455` and ORY Kratos `KRATOS_PUBLIC_URL` URL.
Expand Down Expand Up @@ -54,7 +85,7 @@ recommended.
To run this app with dummy data and no real connection to ORY Kratos, use:

```shell script
$ NODE_ENV=stub npm start
NODE_ENV=stub npm start
```

### Test with ORY Kratos
Expand Down
12 changes: 10 additions & 2 deletions nodemon.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"watch": ["src"],
"watch": [
"src"
],
"ext": "ts",
"exec": "ts-node ./src/index.ts"
"exec": "ts-node ./src/index.ts",
"env": {
"COOKIE_SECRET": "I_AM_VERY_SECRET",
"CSRF_COOKIE_SECRET": "I_AM_VERY_SECRET_TOO",
"DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES": "true",
"ORY_SDK_URL": "http://localhost:4000"
}
}
32 changes: 24 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ import { engine } from "express-handlebars"
import * as fs from "fs"
import * as https from "https"



const baseUrl = process.env.BASE_PATH || "/"

const app = express()
const router = express.Router()

app.use(middlewareLogger)
app.use(cookieParser())
app.use(cookieParser(process.env.COOKIE_SECRET))
app.use(addFavicon(defaultConfig))
app.use(detectLanguage)
app.set("view engine", "hbs")
Expand Down Expand Up @@ -79,13 +81,27 @@ let listener = (proto: "http" | "https") => () => {
console.log(`Listening on ${proto}://0.0.0.0:${port}`)
}

if (process.env.TLS_CERT_PATH?.length && process.env.TLS_KEY_PATH?.length) {
const options = {
cert: fs.readFileSync(process.env.TLS_CERT_PATH),
key: fs.readFileSync(process.env.TLS_KEY_PATH),
}

https.createServer(options, app).listen(port, listener("https"))
// When using the Ory Admin API Token, we assume that this application is also
// handling OAuth2 Consent requests. In that case we need to ensure that the
// COOKIE_SECRET and CSRF_COOKIE_SECRET environment variables are set.
if (process.env.ORY_ADMIN_API_TOKEN &&
(String(process.env.COOKIE_SECRET || "").length < 8) ||
String(process.env.CSRF_COOKIE_SECRET || "").length < 8) {
console.error("Cannot start the server without the required environment variables!")
console.error("COOKIE_SECRET must be set and be at least 8 alphanumerical character `export COOKIE_SECRET=...`")
console.error("CSRF_COOKIE_SECRET must be set and be at least 8 alphanumerical character `export CSRF_COOKIE_SECRET=...`")
} else {
app.listen(port, listener("http"))

if (process.env.TLS_CERT_PATH?.length && process.env.TLS_KEY_PATH?.length) {
const options = {
cert: fs.readFileSync(process.env.TLS_CERT_PATH),
key: fs.readFileSync(process.env.TLS_KEY_PATH),
}

https.createServer(options, app).listen(port, listener("https"))
} else {
app.listen(port, listener("http"))
}

}
14 changes: 8 additions & 6 deletions src/pkg/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ const apiBaseIdentityUrl = process.env.KRATOS_ADMIN_URL || baseUrlInternal
export const apiBaseUrl =
process.env.KRATOS_BROWSER_URL || apiBaseFrontendUrlInternal

const hydraBaseOptions: any = {}

if (process.env.MOCK_TLS_TERMINATION) {
hydraBaseOptions.headers = { "X-Forwarded-Proto": "https" }
}

// Sets up the SDK
const sdk = {
Expand All @@ -32,7 +27,14 @@ const sdk = {
oauth2: new OAuth2Api(
new Configuration({
basePath: apiBaseOauth2UrlInternal,
baseOptions: hydraBaseOptions,
...(process.env.ORY_ADMIN_API_TOKEN && {
accessToken: process.env.ORY_ADMIN_API_TOKEN,
}),
...(process.env.MOCK_TLS_TERMINATION && {
baseOptions: {
"X-Forwarded-Proto": "https"
}
})
}),
),
identity: new IdentityApi(
Expand Down
43 changes: 28 additions & 15 deletions src/routes/consent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,41 @@ import {
} from "@ory/client"
import { UserConsentCard } from "@ory/elements-markup"
import bodyParser from "body-parser"
import { doubleCsrf } from "csrf-csrf"
import { doubleCsrf, DoubleCsrfCookieOptions } from "csrf-csrf"
import { Request, Response, NextFunction } from "express"

const cookieOptions: DoubleCsrfCookieOptions = {
sameSite: "lax",
signed: true,
// set secure cookies by default (recommended in production)
// can be disabled through DANGEROUSLY_DISABLE_SECURE_COOKIES=true env var
secure: true,
...(process.env.DANGEROUSLY_DISABLE_SECURE_CSRF_COOKIES && {
secure: false,
})
}

const cookieName = process.env.CSRF_COOKIE_NAME || "ax-x-csrf-token"
const cookieSecret = process.env.CSRF_COOKIE_SECRET

// Sets up csrf protection
const {
generateToken, // Use this in your routes to provide a CSRF hash + token cookie and token.
invalidCsrfTokenError,
doubleCsrfProtection, // This is the default CSRF protection middleware.
} = doubleCsrf({
getSecret: () => "VERY_SECRET_VALUE", // A function that optionally takes the request and returns a secret
cookieName: "ax-x-csrf-token", // The name of the cookie to be used, recommend using Host prefix.
cookieOptions: {
sameSite: "lax", // Recommend you make this strict if posible
secure: true,
},
getSecret: () => cookieSecret || "", // A function that optionally takes the request and returns a secret
cookieName: cookieName, // The name of the cookie to be used, recommend using Host prefix.
cookieOptions,
ignoredMethods: ["GET", "HEAD", "OPTIONS"], // A list of request methods that will not be protected.
getTokenFromRequest: (req) => req.headers["x-csrf-token"], // A function that returns the token from the request
})

// Checks if OAuth2 consent is enabled
// This is used to determine if the consent route should be registered
// We need to check if the environment variables are set
const isOAuthCosentEnabled = () => (process.env.HYDRA_ADMIN_URL || process.env.ORY_SDK_URL) && process.env.CSRF_COOKIE_SECRET

// Error handling, validation error interception
const csrfErrorHandler = (
error: unknown,
Expand All @@ -37,7 +53,7 @@ const csrfErrorHandler = (
next: NextFunction,
) => {
if (error == invalidCsrfTokenError) {
next(new Error("csrf validation error"))
next(new Error("A security violation was detected, please fill out the form again."))
} else {
next()
}
Expand Down Expand Up @@ -82,7 +98,6 @@ async function createOAuth2ConsentRequestSession(
// A simple express handler that shows the Hydra consent screen.
export const createConsentRoute: RouteCreator =
(createHelpers) => (req, res, next) => {
console.log("createConsentRoute")
res.locals.projectName = "An application requests access to your data!"

const { oauth2, identity } = createHelpers(req, res)
Expand All @@ -102,7 +117,6 @@ export const createConsentRoute: RouteCreator =
trustedClients = String(process.env.TRUSTED_CLIENT_IDS).split(",")
}

console.log("getOAuth2ConsentRequest", challenge)
// This section processes consent requests and either shows the consent UI or
// accepts the consent request right away if the user has given consent to this
// app before
Expand Down Expand Up @@ -263,23 +277,22 @@ export const createConsentPostRoute: RouteCreator =

var parseForm = bodyParser.urlencoded({ extended: false })

export const registerConsentRoute: RouteRegistrator = function (
export const registerConsentRoute: RouteRegistrator = function(
app,
createHelpers = defaultConfig,
) {
if (process.env.HYDRA_ADMIN_URL) {
console.log("found HYDRA_ADMIN_URL")
if (isOAuthCosentEnabled()) {
return app.get("/consent", createConsentRoute(createHelpers))
} else {
return register404Route
}
}

export const registerConsentPostRoute: RouteRegistrator = function (
export const registerConsentPostRoute: RouteRegistrator = function(
app,
createHelpers = defaultConfig,
) {
if (process.env.HYDRA_ADMIN_URL) {
if (isOAuthCosentEnabled()) {
return app.post(
"/consent",
parseForm,
Expand Down

0 comments on commit e9bc673

Please sign in to comment.