Skip to content

Commit

Permalink
docs: document new oidc jsonnet mapper (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr authored May 6, 2020
1 parent 1b4567b commit 088b30f
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 42 deletions.
Empty file.
176 changes: 134 additions & 42 deletions docs/docs/concepts/credentials/openid-connect-oidc-oauth2.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ supported, to authenticate identities using a third-party identity provider,
such as Google, Microsoft, GitHub - or any other OAuth2 / OpenID Connect
provider (for example [ORY Hydra](https://www.ory.sh/hydra)).

This strategy expects that you've set up your Identity Traits Default JSON
Schema.

## Configuration

You can configure multiple OAuth2 / OpenID Connect providers. First, enable the
Expand All @@ -30,25 +33,28 @@ selfservice:
strategies:
oidc:
providers:
- id: github # The ID of the provider. DO NOT change this once this is in use.
- # REQUIRED - The ID of the provider.
# DO NOT change this once this is in use.
id: github

# The provider you would like to use. ORY Kratos comes with some predefined providers to make
# REQUIRED - The provider you would like to use. ORY Kratos comes with some predefined providers to make
# life easier for you, but you can always opt for the "generic" provider, which works
# with any Certified OpenID Connect Provider (Google, ORY Hydra, ...):
#
# provider: github
# provider: google
# .... more to come
#
provider: generic

# Other supported providers are (more to come):
#
# provider: github
# provider: google
# REQUIRED - See section "Data Mapping with Jsonnet" for more information.
mapper_url: http://mydomain.com/github.schema.json

# The OAuth2 / OpenID Connect provider will provide you with a OAuth2 Client ID and Client Secret. You need
# to set them here:
client_id: ...
client_secret: ...

schema_url: http://mydomain.com/github.schema.json # See section "Schema"

# What scope to request. Usually, this would be something like "profile" or "email".
# Please check the documentation of the OAuth2 / OpenID Connect provider to see what's allowed here.
scope:
Expand Down Expand Up @@ -87,69 +93,155 @@ selfservice:
:::
## JSON Schema
## Data Mapping with Jsonnet
This strategy expects that you've set up your default JSON Schema for identity
traits. There are no extra settings for that.
The data provided by Google, GitHub, Facebook, and others will vary in payloads.
One service might include the `website` or `phone_number` while another might
not.

You do however need to set up an additional JSON Schema for your provider. This
is required because we need to transform profile data coming from, for example
GitHub, to your identity model.
Therefore you need to specify how this data maps to the identity's traits. You
can do that by writing a [Jsonnet Code Snippet](../../reference/jsonnet.mdx) and
referencing that in your ORY Kratos config file:

Defining that JSON Schema also allows you to require certain information. If you
ask the user to authorize the `photos` scope for example, you can configure the
JSON Schema in such a way that `photos` must be part of the identity data or the
flow will fail.
```yaml title="path/to/my/kratos/config.yml"
# $ kratos -c path/to/my/kratos/config.yml serve
selfservice:
strategies:
oidc:
providers:
- id: github
# ...
mapper_url: file://path/to/my/kratos/github.data-mapper.jsonnet
# You can also load this from a HTTP(S) source:
mapper_url: https://domain.com/path/to/my/kratos/github.data-mapper.jsonnet
# Or inline it using base64 encoding:
mapper_url: base64://bG9jYWwgY2xhaW1zID0gc3RkLmV4dFZhcignY2xhaW1zJyk7CmlmIHN0ZC5sZW5ndGgoY2xhaW1zLnN1YikgPT0gMCB0aGVuCiAgZXJyb3IgJ2NsYWltIHN1YiBub3Qgc2V0JwplbHNlCiAgewogICAgaWRlbnRpdHk6IHsKICAgICAgdHJhaXRzOiB7CiAgICAgICAgZW1haWw6IGNsYWltcy5zdWIsCiAgICAgICAgW2lmICJ3ZWJzaXRlIiBpbiBjbGFpbXMgdGhlbiAid2Vic2l0ZSIgZWxzZSBudWxsXTogY2xhaW1zLndlYnNpdGUsCiAgICAgIH0sCiAgICB9LAogIH0=
```

You will also need to project data coming from the provider onto your own data
model. You can express this using a JSON Path
([learn more about the syntax](../reference/json-schema-json-paths.md)) in your
JSON Schema. Let's assume you want to map field `username` from the provider to
field `traits.name` in your identity:
ORY Kratos adds an external variable called `claims` to the data mapper. It
contains all the claims (e.g. username, email, ...) for the OpenID Connect or
OAuth2 Provider. Keep in mind that the claims will vary per provider and per
flow - depending on what permissions the user grants you (e.g. "App XYZ cannot
see my private email"). Your Jsonnet code must return a JSON object that looks
like:

```json5
{
$id: 'https://example.com/social.schema.json',
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties: {
username: {
type: 'string',
'ory.sh/kratos': {
mappings: { identity: { traits: [{ path: 'name' }] } },
},
identity: {
traits: {
/* ... */
},
},
required: ['username'],
}
```

If the OpenID Connect provider returns
:::note

For more information on Jsonnet check out our
[Jsonnet Documentation](../../reference/jsonnet.mdx).

To debug Jsonnet payloads, use the `--dev` flag and set `log.level` to `debug`
(e.g. `LOG_LEVEL=debug kratos serve --dev`). Logs with detailed payloads will be
emitted once you complete an OpenID Connect / OAuth2 login or registration.

:::

The Jsonnet code snippet

```jsonnet title="path/to/my/kratos/github.data-mapper.jsonnet"
# claims contains all the data sent by the upstream.
local claims = std.extVar('claims');
{
identity: {
traits: {
email: claims.email, // If email is not set the Jsonnet snippet will fail with an error.
[if "website" in claims then "website" else null]: claims.website, // The website claim is optional.
},
},
}
```

returns

```json
{
"sub": "123123123",
"username": "john.doe"
"identity": {
"traits": {
"email": "foo@ory.sh",
"website": "https://www.ory.sh"
}
}
}
```

for example (`sub` is the OpenID Connect field for the identity's ID), that
would be transformed to identity:
when the ID Token body (or the OAuth2 equivalent) returned by the OpenID Connect
provider contains:

```json
{
"sub": "some-identity-id-4hA8gk",
"email": "foo@ory.sh",
"website": "https://www.ory.sh"
}
```

which is then being used for the identity's traits.

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 ORY
Kratos to link the identity to the "social sign in profile" for future login
flows:

```yaml
# This is the YAML representation of an identity
id: '9f425a8d-7efc-4768-8f23-7647a74fdf13'
credentials:
oidc:
id: oidc
identifiers:
- example:123123123
- example:some-identity-id-4hA8gk
config:
- provider: example
identifier: 123123123
identifier: some-identity-id-4hA8gk
traits_schema_url: http://foo.bar.com/person.schema.json # This come from the default identity schema url.
traits_schema_url: http://foo.bar.com/person.schema.json # This comes from the default identity schema url.
traits:
name: john.doe # This is extracted from `username` using
email: foo@ory.sh # This is extracted from `username` using
website: https://www.ory.sh # This is extracted from `username` using
```
### External Variable `claims`

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

```go
package oidc
type Claims struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Name string `json:"name,omitempty"`
GivenName string `json:"given_name,omitempty"`
FamilyName string `json:"family_name,omitempty"`
LastName string `json:"last_name,omitempty"`
MiddleName string `json:"middle_name,omitempty"`
Nickname string `json:"nickname,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
Profile string `json:"profile,omitempty"`
Picture string `json:"picture,omitempty"`
Website string `json:"website,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
Gender string `json:"gender,omitempty"`
Birthdate string `json:"birthdate,omitempty"`
Zoneinfo string `json:"zoneinfo,omitempty"`
Locale string `json:"locale,omitempty"`
PhoneNumber string `json:"phone_number,omitempty"`
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
}
```
104 changes: 104 additions & 0 deletions docs/docs/reference/jsonnet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
id: jsonnet
title: Data Mapping with Jsonnet
---

Some modules like the
[OpenID Connect and OAuth2 Strategy](../concepts/credentials/openid-connect-oidc-oauth2.mdx)
support [Jsonnet](https://jsonnet.org), allowing you to easily write code that
modifies your identity's data and load it into ORY Kratos.

We highly recommend checking out the official
[learning Jsonnet tutorial](https://jsonnet.org/learning/tutorial.html).

## Formatting Jsonnet Code

Format Jsonnet code snippets using:

```shell script
$ kratos help jsonnet format

# e.g.:
$ kratos jsonnet format --write path/to/files/*.jsonnet
```

## Linting Jsonnet Code

Lint Jsonnet code snippets using:

```shell script
$ kratos help jsonnet lint

# e.g.:
$ kratos jsonnet lint path/to/files/*.jsonnet
```

The command will exit with an exit code of `1` and print all found lint errors
to stderr if the code snippet has lint issues.

## Testing Jsonnet Code

This is an anticipated future feature. For progress check out
[kratos#391](https://github.com/ory/kratos/issues/391).

## Tips & Tricks

The purpose of this section is to provide you with examples for common use
cases.

### Optionality

When you're unsure that a field will be set in the `claims` variable use the
following to make the trait field also optional:

```jsonnet
local claims = std.extVar('claims');
{
identity: {
traits: {
email: claims.sub,
[if "website" in claims then "website" else null]: claims.website,
},
},
}
```

### Defaults

Set defaults for the `claims` variable:

```jsonnet
local claims = {
website: 'i am the default website value'
} + std.extVar('claims');
{
identity: {
traits: {
website: claims.website
}
}
}
```

### Raising Errors

You can raise errors in the Jsonnet code. Keep in mind that these will be shown
as system errors, not validation errors, and that the user will end up at the
Error UI!

```jsonnet
local claims = std.extVar('claims');
if std.length(claims.sub) == 0 then
error 'claim sub not set'
else
{
identity: {
traits: {
// ...
},
},
}
```

0 comments on commit 088b30f

Please sign in to comment.