Skip to main content

Map and merge profile data

Every social sign-in provider returns different data in their payloads. Some providers use usernames, others emails, and so on. While you might expect to get some basic information consistently, the payload isn't standardized. As a result, when integrating with social sign-in providers, you must specify how to map the data you get from the specific provider to the Identity traits.

You define this mapping by creating a Jsonnet code snippet. This snippet becomes a part of the Ory Identities configuration for the given social sign-in provider.

tip

You can find the mapping required for a basic configuration of social sign-in providers in our documentation. For example, learn how to create a data mapping for GitHub.

Write a Jsonnet data mapper

Jsonnet is data templating language specialized for working with JSON. The simplest document is a plain JSON object:

{
"foo": "bar"
}

JsonNet also supports variables, functions, and other features:

local email = 'hello@example.org';

{
identity: { traits: { email: email } }
}

results in JSON:

{
"identity": {
"traits": {
"email": "hello@example.org"
}
}
}

Expected result

The data mapper must returns an object with the following structure:

{
identity: {
traits: {
/* ... depends on your identity schema ... */
},
},
}

Use social sign-in data

Ory Identities adds an external variable called claims to the data mapper. It contains all values the social sign-in provider returned. For example, this Jsonnet code snippet:

github.data-mapper.jsonnet
// claims contains all the data sent by the upstream.
local claims = std.extVar('claims');

{
identity: {
traits: {
[if "website" in claims then "website" else null]: claims.website,
},
},
}

returns this object:

{
"identity": {
"traits": {
"email": "foo@ory.sh",
"website": "https://www.ory.sh"
}
}
}

Fill in the data gaps

note

Social sign-in providers may not return all data you expect. The user may not give you permission to access their email address, for example. Ory will ask the user to fill in the missing data in such a case.

Sometimes the social sign-in provider does not return the data we expect:

  • User needs to agree to your terms of service.
  • User did not agree to share their email address when performing the consent step.

If the data returned by the JsonNet data mapper does not validate against the Identity Schema of your project, the user will be redirected to the registration page where they need to update the missing fields. When submitting the form again, the data provided by the user and the data coming from the OpenID Connect / OAuth2 provider will be merged. This process repeats itself until the Identity's traits are valid against the defined JSON Schema.

External variable claims

The std.ExtVar('claims') object has the following structure and keys available:

{
iss: "https://accounts.google.com",
sub: "1234",
name: "John Doe",
given_name: "John",
family_name: "Doe",
last_name: "Doe",
middle_name: "Peter",
nickname: "john_doe",
preferred_username: "johnny",
profile: "https://plus.google.com/1234",
picture: "https://plus.google.com/1234/profile.png",
website: "https://example.org",
email: "john@doe.com",
email_verified: true,
gender: "m",
birthdate: "1980/11/12",
zoneinfo: "de",
locale: "de",
phone_number: "+1123123123",
phone_number_verified: true,
updated_at: "2022-11-30T11:07:24.405345Z",
hd: "4321",
team: "some-microsoft-team",
raw_claims: {
/* ... */
},
}
Raw Claims raw_claims

All claims that are not part of the standard userinfo claims, are mapped into raw_claims. An example of mapping a raw claim called "groups":

data-mapper.jsonnet
local claims = std.extVar('claims');

{
identity: {
traits: {
[if "groups" in claims.raw_claims then "groups" else null]: claims.raw_claims.groups,
},
},
}

Set identity metadata

You can set public and admin metadata fields, these fields will then be populated whenever data is mapped. This is useful if you want to store data from the social sign-in provider without the user being able to modify it.

local claims = std.extVar('claims');

{
identity: {
traits: {
email: claims.email
},
metadata_public: {
discord_username: claims.discord_username,
},
metadata_admin: {
// ...
},
}
}

Emails and phone numbers

danger

Never trust unverified email addresses and phone numbers.

When using the email or phone number from a social sign-in provider in your identity, you must make sure that the email or phone number is verified. Not doing so may open several attack vectors related to social sign in.

github.data-mapper.jsonnet
local claims = {
email_verified: false,
} + std.extVar('claims');

{
identity: {
traits: {
// Allowing unverified email addresses enables account
// enumeration attacks, especially if the value is used for
// e.g. verification or as a password login identifier.
//
// Therefore we only return the email if it (a) exists and (b) is marked verified
// by GitHub.
[if 'email' in claims && claims.email_verified then 'email' else null]: claims.email,
},
},
}

Configure data mapper

Configure the data mapper in the Ory Console:

  1. Go to AuthenticationSocial Sign-In in the Ory Console.
  2. Pick a provider of your choice.
  3. Add your JsonNet code snippet to the data mapper input form.

End-to-end GitHub example

Let's set up a complete example with GitHub:

  1. Create a new project or use an existing one in the Ory Console.

  2. Go to AuthenticationSocial Sign-In in the Ory Console.

  3. Use the Add GitHub provider, follow the set-up steps from the GitHub social sign-in documentation.

  4. Copy and paste the following JsonNet data mapper:

    github.data-mapper.jsonnet
    local claims = {
    email_verified: false,
    } + std.extVar('claims');

    {
    identity: {
    traits: {
    // Allowing unverified email addresses enables account
    // enumeration attacks, especially if the value is used for
    // e.g. verification or as a password login identifier.
    //
    // Therefore we only return the email if it (a) exists and (b) is marked verified
    // by GitHub.
    [if 'email' in claims && claims.email_verified then 'email' else null]: claims.email,
    },
    metadata_public: {
    github_username: claims.username,
    }
    },
    }

Let's assume that GitHub returns the following for std.extVar('claims'):

{
"sub": "some-identity-id-4hA8gk",
"email": "foo@ory.sh",
"email_verified": true,
"username": "foo-user"
}

The JsonNet data mapper will return the following data from GitHub:

{
"identity": {
"traits": {
"email": "foo@ory.sh"
},
"metadata_public": {
"github_username": "foo-user"
}
}
}

The sub field, which is returned by OpenID Connect and OAuth2 servers alike is used as the primary credential identifier for the provider. This allows to link the identity to the "social sign-in profile" for future login flows.

In Ory, the data will be stored in the following format:

# This is the YAML representation of an identity
id: "9f425a8d-7efc-4768-8f23-7647a74fdf13"

credentials:
oidc:
id: oidc
identifiers:
- example:some-identity-id-4hA8gk
config:
- provider: example
identifier: some-identity-id-4hA8gk

schema_id: some-example

traits:
email: foo@ory.sh

metadata_public:
github_username: foo-user