I have a react app built using nextjs and next-auth and used keycloak running in docker container for identity and access management. Everything works fine if I run the nextjs app outside container with keycloak running inside the container. But when I run the app using docker compose along with keycloak, I get the following error.
[next-auth][error][SIGNIN_OAUTH_ERROR]
https://next-auth.js.org/errors#signin_oauth_error connect ECONNREFUSED 127.0.0.1:8092 {
error: {
message: 'connect ECONNREFUSED 127.0.0.1:8092',
stack: 'Error: connect ECONNREFUSED 127.0.0.1:8092n' +
' at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1606:16)n' +
' at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17)',
name: 'Error'
},
providerId: 'keycloak',
message: 'connect ECONNREFUSED 127.0.0.1:8092'
}
Here is my compose file.
services:
romford-web:
image: myrepo:romoford-web-1.0
restart: always
container_name: romford-web
environment:
NEXT_PUBLIC_CUSTOMER_API_BASE_URL: http://romford-api:4010/api
NEXT_PUBLIC_DEFAULT_PAGE_SIZE: 4
NEXT_PUBLIC_DEFAULT_PAGE_NUMBER: 1
NEXTAUTH_SECRET: secret
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_URL_INTERNAL: http://romford-web:3000
KEYCLOAK_CLIENT_ID: next-auth-client
KEYCLOAK_CLIENT_SECRET: secret-from-keycloack
KEYCLOAK_ISSUER: http://host.docker.internal:8092/realms/romfordmotors
ports:
- '3000:3000'
networks:
- my_net
romford-api:
image: myrepo:romoford-api-1.0
restart: always
container_name: romford-api
ports:
- '4010:4010'
networks:
- my_net
postgresql:
image: postgres
container_name: postgresql-dev
restart: unless-stopped
environment:
POSTGRES_DB: bitnami_keycloak
POSTGRES_USER: bn_keycloak
POSTGRES_PASSWORD: password
# - ALLOW_EMPTY_PASSWORD=yes
# - POSTGRESQL_USERNAME=bn_keycloak
# - POSTGRESQL_DATABASE=bitnami_keycloak
volumes:
- 'postgresql_data:/var/lib/postgresql/data'
networks:
- my_net
keycloak:
image: docker.io/bitnami/keycloak:latest
container_name: keycloak-dev
restart: unless-stopped
ports:
- '8092:8080'
environment:
KEYCLOAK_CREATE_ADMIN_USER: true
KEYCLOAK_DATABASE_PASSWORD: password
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin-password
depends_on:
- postgresql
volumes:
- 'keycloak_data:/opt/jboss/keycloak/standalone/data'
- 'keycloak_config:/opt/jboss/keycloak/standalone/configuration'
# - './mynewtheme:/opt/bitnami/keycloak/themes/mynewtheme'
networks:
- my_net
mongodb:
image: mongo
restart: always
container_name: mongodb
# environment:
# MONGO_INITDB_ROOT_USERNAME: root
# MONGO_INITDB_ROOT_PASSWORD: example
ports:
- '27018:27017'
networks:
- my_net
volumes:
- api-data:/data/db
volumes:
keycloak_data:
keycloak_config:
api-data:
postgresql_data:
driver: local
networks:
my_net:
name: my_net
# external: true
and providers settings inside my nextjs app are
import NextAuth, { Account, AuthOptions, Session, User } from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import KeycloakProvider, {
KeycloakProfile,
} from 'next-auth/providers/keycloak';
import bcrypt from 'bcrypt';
import { OAuthConfig } from 'next-auth/providers/oauth';
import { JWT } from 'next-auth/jwt';
export const authOptions = {
// Configure one or more authentication providers
providers: [
// logout will not logout from keycloak idp, so we need to the things as described here
// https://stackoverflow.com/questions/71872587/logout-from-next-auth-with-keycloak-provider-not-works
// https://stackoverflow.com/questions/74168539/next-auth-provide-types-for-callback-functions-parameters
// these are implemented below in events and jwt call backs
KeycloakProvider({
clientId: process.env.KEYCLOAK_CLIENT_ID as string,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET as string,
issuer: process.env.KEYCLOAK_ISSUER,
name: 'Romford motors',
}),
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: 'Credentials',
// `credentials` is used to generate a form on the sign in page.
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const userName = credentials?.username ?? '';
const password = credentials?.password ?? '';
// var passwordToEncrypt = '';
// var hash = await bcrypt.hash(passwordToEncrypt, 10);
const hash =
'$2b$10$irGbj/bzy3bgDrfjw4adfQQOMke6tt1uiTf/D34BLeA2Lw7T7wMvADezzC';
var compareResult = await bcrypt.compare(password, hash);
const success = userName.toLowerCase() === 'sajid' && compareResult;
const user = {
id: '1',
name: 'Saj Raj',
email: '[email protected]',
userName,
};
if (success) {
// Any object returned will be saved in `user` property of the JWT
return user;
} else {
// If you return null then an error will be displayed advising the user to check their details.
return null;
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
}
},
}),
// GithubProvider({
// clientId: process.env.GITHUB_ID as string,
// clientSecret: process.env.GITHUB_SECRET as string,
// }),
// ...add more providers here
],
// https://stackoverflow.com/questions/71872587/logout-from-next-auth-with-keycloak-provider-not-works
// https://stackoverflow.com/questions/74168539/next-auth-provide-types-for-callback-functions-parameters
callbacks: {
async jwt({ token, account }: { token: JWT; account: Account | null }) {
if (account) {
token.id_token = account.id_token;
token.provider = account.provider;
token.access_token = account.access_token;
}
return token;
},
async session({
session,
token,
user,
}: {
session: Session;
token: JWT;
user: User;
}): Promise<Session> {
session.accessToken = token.access_token;
session.idToken = token.id_token;
console.log(session);
return session;
},
},
events: {
async signOut(message: { session: Session; token: JWT }) {
if (message.token.provider === 'keycloak') {
const issuerUrl = (
authOptions.providers.find(
(p) => p.id === 'keycloak'
) as OAuthConfig<KeycloakProfile>
).options!.issuer!;
const logOutUrl = new URL(
`${issuerUrl}/protocol/openid-connect/logout`
);
logOutUrl.searchParams.set('id_token_hint', message.token.id_token!);
await fetch(logOutUrl);
}
},
},
};
export default NextAuth(authOptions);
I have already defined NEXT_AUTH_URL and NEXT_AUTH_SECRET as some answers specified but it is still not working.
2
Answers
I was running docker compose in linux environment and host.docker.internal does not work automatically as in windows. I have to add the following in the nextapp service section of my compose file
After adding extrahost, it works fine.
Try to use the service names instead of
host.docker.internal
.Change
localhost
to0.0.0.0
too.