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:8092\n' +
' 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: 'saj@example.com',
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.
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
extra_hosts:
- 'host.docker.internal:host-gateway'
After adding extrahost, it works fine.