Nigel
Nigel

Reputation: 757

Accessing AWS credentials from a javascript web app

I am writing a web app in vanilla javascript that uses AWS Bedrock. If I use the Bedrock CLI, all is well - I have a credentials file in .aws in my home directory and the necessary IAM Permissions to access the model. Similarly if I hard code the AWS credentials in the call to the API in my javascript code:

const client = new BedrockRuntimeClient({
  region: "eu-west-2",
  credentials: {
    secretAccessKey: "My secret key",
    accessKeyId: "My access key"
  } 
}); 

this works too.

However, if I remove the hard coded credentials from the code, as of course I should, where should I put the credentials file so that my web app can access it? It won't find it in my home directory, because the server knows nothing about my home directory. The advice on the web is very confusing, not helped by the change from version 2 to version 3 of the AWS-SDK, thus making suggestions that work for version 2 no longer applicable (I am using version 3).

To complicate matters more, I am developing the app on MacOS, but intending to deploy it on an EC2 instance and would like any solution to be portable between these two. For both, the server is Apache2.4.

Upvotes: 0

Views: 107

Answers (1)

Nigel
Nigel

Reputation: 757

I eventually found how to do this, with a lot of help from AlanKrantas's example : use AWS Cognito to generate a temporary identity. Here is some example code:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Simple Web Page</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                margin: 20px;
                line-height: 1.5;
            }
            .container {
                max-width: 600px;
                margin: auto;
            }
            textarea {
                width: 100%;
                height: 100px;
                margin-bottom: 10px;
            }
            button {
                display: block;
                margin-bottom: 10px;
                padding: 10px 20px;
                background-color: #007bff;
                color: white;
                border: none;
                cursor: pointer;
                font-size: 16px;
            }
            button:hover {
                background-color: #0056b3;
            }
            .output {
                padding: 10px;
                border: 1px solid #ccc;
                background-color: #f9f9f9;
                min-height: 50px;
            }
        </style>
        <script type="module" src="app.mjs"></script>
    </head>
    <body>
        <div class="container">
            <h1>Simple Llambda 3 Chat</h1>
            <textarea id="inputText" placeholder="Enter some text..."></textarea>
            <button onclick="submitText()">Submit</button>
            <div class="output" id="outputText"></div>
        </div>

        <script>
            async function submitText() {
                const input = document.getElementById('inputText').value
                const output = document.getElementById('outputText')
                output.textContent = input ? await chat(input) : 'Please enter some text.'
            }
        </script>
    </body>
</html>

and

/*
 * Invoke AWS Bedrock LLM model in guest mode using AWS SDK for JavaScript
 *
 * This is a simplified version of the code example in the blog post:
 * https://github.com/alankrantas/aws-sdk-js-bedrock-llm-example/blob/main/README.md
 * See the blog post for details about how to set up the AWS environment.
 */

// bedrock resource
const region = "AWS REGION"
const cognitoIdentityPoolId = `{REGION}:00000000-0000-0000-0000-000000000000`
const bedrockRoleArn =
    "arn:aws:iam::000000000000:role/service-role/{AWSRoleForBedrockName}"

// select model 
const modelId = "AWS MODEL ID"

// ================================================================================

import {
    CognitoIdentityClient,
    GetIdCommand,
    GetOpenIdTokenCommand,
} from "@aws-sdk/client-cognito-identity"
import {
    STSClient,
    AssumeRoleWithWebIdentityCommand,
} from "@aws-sdk/client-sts"
import {
    BedrockRuntimeClient,
    ConverseCommand,
} from "@aws-sdk/client-bedrock-runtime"

export async function chat(userMessage) {
    try {
        const config = { region: region }

        // get guest identity id

        const cognitoClient = new CognitoIdentityClient(config)
        const idResponse = await cognitoClient.send(
            new GetIdCommand({ IdentityPoolId: cognitoIdentityPoolId })
        )
        const id = idResponse.IdentityId

        // get access token
        const tokenResponse = await cognitoClient.send(
            new GetOpenIdTokenCommand({ IdentityId: id })
        )
        const token = tokenResponse.Token

        // get credential with session policy
        const stsClient = new STSClient(config)
        const credentialsResponse = await stsClient.send(
            new AssumeRoleWithWebIdentityCommand({
                RoleArn: bedrockRoleArn,
                WebIdentityToken: token,
                RoleSessionName: "bedrock_session",
            })
        )
        const credentials = credentialsResponse.Credentials

        // get bedrock client using the credential

        const bedrockClient = new BedrockRuntimeClient({
            region: region,
            credentials: {
                accessKeyId: credentials.AccessKeyId,
                secretAccessKey: credentials.SecretAccessKey,
                expiration: credentials.Expiration,
                sessionToken: credentials.SessionToken,
            },
        })


        // set the system prompt and the user's query
        const systemPrompt = [{ text: "answer concisely in no more than 200 words. Use Markdown formatting for your response" }]

        const conversation = [
            {
                role: "user",
                content: [{ text: userMessage }],
            },
        ]

        // Create a command with the model ID, the message, and a basic configuration.
        const command = new ConverseCommand({
            modelId,
            messages: conversation,
            system: systemPrompt,
            inferenceConfig: { maxTokens: 512, temperature: 0.5, topP: 0.9 },
        })


        // Send the command to the model and wait for the response
        const response = await bedrockClient.send(command)

        // Extract and print the response text.
        const responseText = response.output.message.content[0].text
        console.log(response)
        console.log(responseText)
        return responseText
    } catch (err) {
        console.log(`ERROR: Can't invoke '${modelId}'. Reason: ${err}`)
    }
}
window.chat = chat

It is a shame that AWS doesn't provide an example such as this in its documentation. It would have saved me a lot of time!

Upvotes: 0

Related Questions