V1 Meetup Talk: Authentication and Security

RedwoodJS 1.0

opengraph-256

Authentication and Security

Authentication is not just a password.

redwood_v1_auth_security.pdf (1.7 MB)


:closed_lock_with_key: Authentication is not just a password.

It’s how you begin a relationship with your user.

  • Personal Information (Who they are: name, age, gender, location, favorites)
  • Roles and Permissions (What they can do)
  • Activity (Helps track what they do)
  • History (Purchases, Posts, etc)

:closed_lock_with_key: Authentication is not just a password.

It’s how you maintain a trusted relationship with your user.

Your user’s info is your responsibility.

RedwoodJS gives you the choice, the tools, and the community to help, but …

Ultimately, it’s up to you. It’s a balance of effort, risk, cost.


:evergreen_tree: Approach

  • Choice
  • Easy to Setup
  • Tightly Integrated by Default
  • Role-based Access Control (RBAC)
  • Observability
  • Secret Management

:thinking: Choice

… + dbAuth

https://redwood-playground-auth.netlify.app


:thinking: How do I choose?

  • dbAuth - Side projects, low risk data, build own (mails, etc)
  • Netlify Identity - Already hosting on Netlify, manage via API
  • Auth0 - Rich hosted UI, huge library of integrations social and enterprise, rules and hooks, anomaly detection, whitelisting.
  • Clerk - Passwordless, Social, MFA, Device tracking, JWT re-shaping via templates. Ready-built React components.
  • Supabase - Integrates across stack: Postgres and Row Level Security, Storage, and Realtime. Many social integrations.

:fearful: The Argument for using an Identity Service

If you are not careful you’ll have to build soooo much.^2

Mail. Password strength. Auditing. Admin api. Callback whitelisting.

App and user metadata. Multiple identity provider support.

Account blocking. Login attempt anomaly detection. IP address spoof detection. Token refreshing.

Then you’ll want passwordless and magic link. And then SMS one time password authentication. And then support multiple phone providers.

Oh and then 2FA or even MFA.

And then you’ll have to do GDPR protection on all your user profile data.

:relieved: Easy to Setup


:knot: Tightly Integrated by Default

  • API Side
    • GraphQL Server (SDL and Validator Directives)
    • Serverless Functions (Open/Authenticated endpoints)
    • Webhooks (Popular verifiers out of box, integrate)
  • Web Side
    • Routes (<Private>)
    • useAuth Hook (logIn, logOut, isAuthenticated, hasRole)

:ticket: RBAC Example App

The RedwoodJS Blog tutorial – but with Netlify Identity authentication and RBAC (and more)!

Site: https://redwoodblog-with-identity.netlify.app
Repo: GitHub - dthyresson/redwoodblog-rbac: The RedwoodJS Blog tutorial -- but with Netlify Identity authentication and RBAC (and more)!


:lock: Not Logged In

const { logIn, logOut, isAuthenticated, hasRole, currentUser } = useAuth();

// ...

<a href="#" onClick={isAuthenticated ? logOut : logIn}>
  <span>{isAuthenticated ? "Log Out" : "Log In"}</span>
</a>

:unlock: Authenticated

const { logIn, logOut, isAuthenticated, hasRole, currentUser } = useAuth();

// ...

{
  (hasRole("admin") ||
    hasRole("author") ||
    hasRole("editor") ||
    hasRole("publisher")) && (
    <NavLink to={routes.posts()}>Manage Posts</NavLink>
  );
}

:paintbrush: Roles on SDL via Directives

// api/src/graphql/posts.sdl.js

export const schema = gql`
  type Post {
    id: Int!
    createdAt: DateTime!
    updatedAt: DateTime
    publishedAt: DateTime
    title: String!
    body: String!
  }

  type Query {
    posts: [Post!]! @skipAuth
    post(id: Int!): Post! @skipAuth
  }

  // ...

  type Mutation {
    createPost(input: CreatePostInput!): Post!
      @requireAuth(roles: ["admin", "author", "publisher"])
    updatePost(id: Int!, input: UpdatePostInput!): Post!
      @requireAuth(roles: ["admin", "editor", "publisher"])
    deletePost(id: Int!): Post! @requireAuth(roles: ["admin", "publisher"])
  }
`

// api/src/lib/auth.js

export const requireAuth = ({ roles } = {}) => {
  if (!isAuthenticated) {
    throw new AuthenticationError("You don't have permission to do that.")
  }

  if (!hasRole({ roles })) {
    throw new ForbiddenError("You don't have access to do that.")
  }
}

:gear: Profile / Settings

const SettingsPage = () => {
  const { isAuthenticated, currentUser, userMetadata, hasRole } = useAuth()

// ...

<dd>
  {userMetadata.user_metadata.full_name}
</dd>

/// ...
<ul>
{currentUser.roles.map((role) => {
  return (
  <li key={role}>
    <div>{role}</div>
  </li>)
</ul>

:eyes: Observability

Logging helps with audits and tracking down attacks or infractions.

RedwoodJS api-side logger can record activity and tie the event to a user and a request id … and send the data to LogFlare, DataDog, or other services to store, search and analyze the activity.

Or, let an auth service track sign ins, sign outs, password changes.


:zipper_mouth_face: Secrets

  • Store these in .env.
  • Never commit secrets to your repo
  • Use a service like Doppler to share and manage multiple environments
  • Use the secret management tools from your provider like Netlify and Vercel

:next_track_button: What’s Next

  • New providers, such as Okta
  • Move to “plug-in” model
  • Permissions and Attribute Based Access Control (ABAC)
  • Improved GraphQL Playground support for auth headers
5 Likes

Great presentation and an exceptional overview, @dthyresson :rocket:

This should be a must-read for anyone building a product or startup.