DEV Community

Cover image for Enriching Keycloak with LinkedIn VanityName, Headline & Profile Picture via Custom SPI

Enriching Keycloak with LinkedIn VanityName, Headline & Profile Picture via Custom SPI

When integrating LinkedIn as an Identity Provider in Keycloak, you may quickly discover a limitation: by default, LinkedIn only provides basic user information such as email, first name, and last name.

However, in many real-world applications, you may also want to retrieve and store additional profile details, including:

Vanity name (public LinkedIn profile ID)

High-resolution profile picture

Professional headline (user's job title or tagline)
Enter fullscreen mode Exit fullscreen mode

To achieve this, you'll need to create a custom Identity Provider Mapper SPI (Service Provider Interface) in Keycloak. This extension allows you to fetch additional data from LinkedIn's API and map it into the Keycloak user model during the authentication process.

Step 1: Set Up Your LinkedIn App Permissions

Before you begin implementing the custom SPI, make sure your LinkedIn developer app has the correct permissions. You'll need to request access to the following

Image description

These scopes allow your Keycloak instance to retrieve detailed user information such as their profile picture and professional headline.
Configuring LinkedIn as an Identity Provider in Keycloak (Tested on Keycloak 26.2.5)

After you've created a LinkedIn App and obtained the required credentials, follow these steps to configure LinkedIn in your example Keycloak realm:

  1. Navigate to your Keycloak admin console.
  2. Go to:
    Identity Providers β†’ Select LinkedIn.

  3. Fill in the following fields:

  • Client ID: (from your LinkedIn app)
  • Client Secret: (from your LinkedIn app)
  1. In the Scopes field, add the following permissions:

openid profile email r_basicprofile

These scopes are necessary to:

  • Authenticate the user (openid)
  • Retrieve profile information (profile, r_basicprofile)
  • Fetch the user’s verified email address (email)
  1. Enable the following options:
  2. Store Tokens (This is required for the SPI to extract the LinkedIn access token and make additional API calls.)
  3. Trust Email (Assumes the email provided by LinkedIn is verified.)

Click Save.

To use this LinkedIn SPI extension in your Keycloak setup:

  1. Download the JAR file
    Either build it yourself using Maven, or download a prebuilt version from the GitHub Releases page.

  2. Copy the JAR to Keycloak's provider directory

cp linkedin-profile-mapper-1.0.0.jar /opt/keycloak/providers/
Enter fullscreen mode Exit fullscreen mode
  1. Rebuild Keycloak to recognize the new provider
/opt/keycloak/bin/kc.sh build
Enter fullscreen mode Exit fullscreen mode
  1. Restart Keycloak
    /opt/keycloak/bin/kc.sh start
Enter fullscreen mode Exit fullscreen mode

Source Code & Releases
You can find the full source code and latest releases at:

https://github.com/maradwan/keycloak-linkedin-profile-mapper

Enter fullscreen mode Exit fullscreen mode

Then you need to add Identity Provider Mapper

Image description

Then you need to add attributes for vanityName, profilePicture, and headline in the User profile that is in the Realm settings of your example realm

Image description

Create an example-client in Keycloak

In your Keycloak Admin Console:

  1. Go to your realm (e.g., example), then navigate to Clients β†’ click Create client.

  2. Enter example-client as the Client ID, choose OpenID Connect, and click Next.

  3. Set Root URL to http://localhost:3000, and Valid Redirect URI to http://localhost:3000/callback, then click Save.

  4. Go to the Credentials tab β†’ copy the Client Secret.

  5. Under Settings, enable Standard Flow, optionally enable Direct Access Grants.

  6. Use these details in your app:

Test the Integration (Optional FastAPI Example)

You can test the LinkedIn login flow and see the enriched tokens by using a simple FastAPI application:

Install the required packages:

pip install fastapi uvicorn requests
Enter fullscreen mode Exit fullscreen mode
# app.py
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
import requests

app = FastAPI()

# === CONFIG ===
KEYCLOAK_BASE_URL = "https://idp.example.com"
REALM = "example"
CLIENT_ID = "example-client"
CLIENT_SECRET = "XXXXXXXXX"  # Only needed for confidential clients
REDIRECT_URI = "http://localhost:3000/callback"  # Your app's redirect URI

# === ROUTES ===

@app.get("/login")
def login():
    return RedirectResponse(
        f"{KEYCLOAK_BASE_URL}/realms/{REALM}/protocol/openid-connect/auth"
        f"?client_id={CLIENT_ID}"
        f"&redirect_uri={REDIRECT_URI}"
        f"&response_type=code"
        f"&scope=openid"
    )

@app.get("/callback")
def callback(request: Request):
    code = request.query_params.get("code")
    if not code:
        return {"error": "No authorization code provided"}

    # Exchange code with Keycloak (not LinkedIn)
    token_response = requests.post(
        f"{KEYCLOAK_BASE_URL}/realms/{REALM}/protocol/openid-connect/token",
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": REDIRECT_URI,
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )

    if token_response.status_code != 200:
        return {"error": "Token exchange failed", "details": token_response.json()}

    token_data = token_response.json()

    # Decode the token to inspect (optional)
    id_token = token_data.get("id_token")
    access_token = token_data.get("access_token")
    return {
        "access_token": access_token,
        "id_token": id_token,
        "message": "Use this token to call secured APIs or decode for LinkedIn data",
    }
Enter fullscreen mode Exit fullscreen mode

Run the App

uvicorn app:app --reload --port 3000
Enter fullscreen mode Exit fullscreen mode

Image description

Sources:
https://learn.microsoft.com/en-us/linkedin/marketing/quick-start?view=li-lms-2025-03#step-1-apply-for-api-access

Top comments (0)