Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edge Runtime and AWS SDK credentials #65769

Open
r007 opened this issue May 15, 2024 · 3 comments
Open

Edge Runtime and AWS SDK credentials #65769

r007 opened this issue May 15, 2024 · 3 comments
Labels
bug Issue was opened via the bug report template. Middleware Related to Next.js Middleware Runtime Related to Node.js or Edge Runtime with Next.js.

Comments

@r007
Copy link

r007 commented May 15, 2024

Link to the code that reproduces this issue

https://github.com/r007/next-aws-sdk-middleware-issue

To Reproduce

For example, if I want to get a variable from AWS Systems Manager Parameter Store, I can use this code:

import {NextResponse} from 'next/server'
import {SSMClient, GetParameterCommand} from '@aws-sdk/client-ssm'

export async function middleware() {
  const ssm = new SSMClient({region: 'us-east-1'})
  const command = new GetParameterCommand({Name: 'put-some-variable-here'})
  const response = await ssm.send(command)
  console.log(response)

  return NextResponse.next()
}

export const config = {
  matcher: ['/:path*']
}

It'll throw a Credential is missing error, because the edge runtime can't get AWS credentials from local file.

Current vs. Expected behavior

Hi guys,

I want to use AWS SDK API in my next.js middleware to get parameters. The reason for this is that Edge@Lambda doesn't support environment variables, so I have to use AWS Parameter Store instead.

Next.js Edge runtime doesn't work with AWS SDK at all. Simply because it uses node.js API behind the scene to get credentials (access key + secret access key). It reads a local file with credentials, but since the Edge runtime can't read anything, it fails to get access key + secret key needed to make an API call.

Any suggestions? Possible solutions?

Best regards,
Sergey

Provide environment information

Node.js v20.12.2

Operating System:
  Platform: linux
  Arch: x64
  Version: #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022
  Available memory (MB): 7911
  Available CPU cores: 8
Binaries:
  Node: 20.12.2
  npm: 10.7.0
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 14.2.3 // Latest available version is detected (14.2.3).
  eslint-config-next: N/A
  react: 18.3.1
  react-dom: 18.3.1
  typescript: N/A
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Middleware, Runtime

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

I'm trying to implement authentication with AWS Cognito inside the middleware + token renewal.

@r007 r007 added the bug Issue was opened via the bug report template. label May 15, 2024
@github-actions github-actions bot added Middleware Related to Next.js Middleware Runtime Related to Node.js or Edge Runtime with Next.js. labels May 15, 2024
@masonembry
Copy link

I'm trying to do the same thing. I've been paying a hefty "Next tax" lately. Looks like this is something we simply can't do. Middleware for auth doesn't seem like that weird of a use case...

@gardner
Copy link

gardner commented Jun 5, 2024

How are you creating the lambdas? Are you using CDK or similar?

Have you tried configuring your Lambda@Edge with an IAM Execution Role that has access to SSM as describe in this blog post?

@r007
Copy link
Author

r007 commented Jun 7, 2024

@gardner yes of course. I use cdk everywhere. All the necessary permissions already set. It's not about the SSM itself, but rather about total inability to use AWS SDK in a middleware (because it depends heavily on nodejs api).

I ended up removing AWS SDK from the middleware altogether. They were replaced with fetch('api/auth') calls. Basically, I am trying to implement authorization checks with periodic token refresh. Here's how it looks like at the moment:

export default async function middleware(request: NextRequest) {
  const {url, cookies} = request
  const path = request.nextUrl.pathname

  // Get all the tokens
  const accessToken = cookies.get('accessToken')?.value
  const idToken = cookies.get('idToken')?.value
  const refreshToken = cookies.get('refreshToken')?.value

  const isAuthPage =
    path.startsWith('/confirm-register') ||
    path.startsWith('/forgot-password') ||
    path.startsWith('/login') ||
    path.startsWith('/register') ||
    path.startsWith('/reset-password')

  const isDashboard = path.startsWith('/dashboard')

  let isSessionValid = false
  let isTokenExpired = false

  // Redirect to dashboard if user is already logged in
  if (accessToken && idToken && refreshToken) {
    const IdToken = new CognitoIdToken({ IdToken: idToken})
    const AccessToken = new CognitoAccessToken({ AccessToken: accessToken })
    const RefreshToken = new CognitoRefreshToken({ RefreshToken: refreshToken })

    const cachedSession = new CognitoUserSession({
      IdToken,
      AccessToken,
      RefreshToken
    })

    isSessionValid = cachedSession.isValid()
    isTokenExpired = tokenExpired(IdToken)
  }

  // We have a valid cognito session, redirect user to dashboard
  if (isAuthPage) {
    if (isSessionValid) {
      return NextResponse.redirect(new URL('/dashboard', url))
    }

    return NextResponse.next()
  }

  // In case of dashboard, check if session is valid and token not expired yet
  if (isDashboard) {
    if (isSessionValid) {
      if (isTokenExpired) {
        // Update all the tokens
        return await getNewTokens(url, refreshToken)
      }

      // Do nothing, just procceed with request
      return NextResponse.next()
    }
  }

  return NextResponse.redirect(new URL('/logout', url))

Plus some manual checks.

const tokenExpired = (token: CognitoIdToken): boolean => {
  return new Date(token.getExpiration() * 1000) <= new Date()
}

I moved AWS SDK Cognito client to /app/api/refresh-token, so that it can be used without any limitations.

// Receive new tokens and update cookies
const response = await fetch(new URL('/api/refresh-token', base), {
   headers: {
     'Content-Type': 'application/json',
     cookie: `refreshToken=${refreshToken}`
   }
})

This solution is far from ideal but more or less solves the problem. Of course it would be nice to use SDK directly from the middleware without having to move to it to the API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. Middleware Related to Next.js Middleware Runtime Related to Node.js or Edge Runtime with Next.js.
Projects
None yet
Development

No branches or pull requests

3 participants