Introduction to OIDC Provide Curity From Installing to Auth Flows

As OIDC is becoming more and more popular, I decided to try as many free OIDC providers as I can just to see how they works. In my previous post I show you some basics of Keycloak. In this post I will follow the same style to give you a brief introduction to Curity.

Curity is not entirely a free IDP, you must obtain a license to use this product. You should be able to obtain a community license using your company’s email address.

Please make sure you have a license before reading further.

Installation with docker

As usual, I will use Traefik as reverse proxy to handle TLS and expose port 80 and 443 to the public.

Installation is pretty straight forward. Because we must use TLS to communicate with Curity, I need to tell Traefik to ignore TLS verification. To do that I need to useserversTransports and it is only available when we are using file and Kubernetes CRD providers.[^1]

Install Traefik

If you don’t know how to use Traefik with file provider, please check my previous post. I even made a video to show you how it works.

Here are all the files you need for the demo.

Please note this is meant to be used on a server which means it has http redirect to https and it request valid SSL from let’sEncrypt automatically. If you use it for local testing, you need to modify the settings to not obtain SSL and maybe stop the redirect.

  • Create files

    1
    2
    3
    4
    5
    6
    mkdir -p data/configurations
    touch docker-compose.yaml
    touch data/traefik.yaml
    touch data/acme.json
    touch data/configurations/dynamic.yaml
    chmod 600 data/acme.json
  • dock-compose.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    version: '2.1'
    services:
    traefik:
    image: traefik:2.5
    container_name: traefik
    restart: always
    security_opt:
    - no-new-privileges:true
    ports:
    - 80:80
    - 443:443
    volumes:
    - /etc/localtime:/etc/localtime:ro
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - ./data/traefik.yaml:/traefik.yaml:ro
    - ./data/acme.json:/acme.json
    # Add folder with dynamic configuration yaml
    - ./data/configurations:/configurations
    networks:
    - proxy

    networks:
    proxy:
    external: true
  • traefik.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    api:
    dashboard: true

    entryPoints:
    web:
    address: :80
    http:
    # Remove below 3 lines to stop http -> https redirect
    redirections:
    entryPoint:
    to: websecure

    websecure:
    address: :443
    http:
    middlewares:
    - secureHeaders@file
    - nofloc@file
    tls:
    certResolver: letsencrypt

    pilot:
    dashboard: false

    providers:
    file:
    filename: /configurations/dynamic.yaml

    certificatesResolvers:
    letsencrypt:
    acme:
    email: [email protected]
    storage: acme.json
    keyType: EC384
    httpChallenge:
    entryPoint: web
  • dynamic.yaml

    Let me explain this a little bit. serversTransports is used to skip upstream TLS verification. Because Curity container is running inside the same network as Traefik, Traefik can find it using its container name curity. Port 6749 is for Curity’s admin UI and port 8443 is for the IDP url.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    http:
    serversTransports:
    mytransport:
    insecureSkipVerify: true
    routers:
    traefik-dashboard:
    service: api@internal
    middlewares:
    - "user-auth"
    rule: "Host(`traefik.yourdomain.com`)"
    curity-ui:
    service: curity-ui-service
    middlewares:
    rule: "Host(`curity-ui.yourdomain.com`)"
    curity:
    service: curity-service
    middlewares:
    rule: "Host(`curity.yourdomain.com`)"

    services:
    curity-ui-service:
    loadBalancer:
    serversTransport: mytransport
    servers:
    - url: "https://curity:6749"
    curity-service:
    loadBalancer:
    serversTransport: mytransport
    servers:
    - url: "https://curity:8443"

Install Curity

Installing Curity is pretty easy, you can start an instance with below docker compose file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '2.1'

services:
curity:
image: curity.azurecr.io/curity/idsvr
container_name: curity
restart: unless-stopped
environment:
PASSWORD: curity
networks:
- proxy

networks:
proxy:
external: true

Once you’ve started Traefik and Curity, you can reach Curity admin UI at https://curity-ui.yourdomain.com/admin and Curity endpoint is https://curity.yourdomain.com.

Start using Curity

Once you can see the Curity log in page, let’s start. The default username is admin and password is curity as I set on Curity’s docker-compose.yaml file above.

First Configuration

It should be pretty straight forward to follow the the prompts to load your license and select default self-signed certificate etc.

You can follow the official video here if you want to see how it is done. Once it is done, you need several steps before you can use it.

Authentication Service

I will only use html form authenticator to demo the OIDC usage.

  1. Click Profiles -> Authentication Service -> Authenticators
  2. New Authenticator -> Select HTML form with name demo
  3. Select default-account-manager and default-credential-manager under Account Manager and Credential Manager respectively
  4. Go to Changes -> commit -> OK

Token Service

General Settings

  1. Click Profiles -> Token Service
  2. Select default-account-manager under Account manager.
  3. Click OpenID Connect -> Configure Endpoints-> Make sure you enable all the endpoints you need. For easy demo, I enabled all except JWKs URI.
  4. Go to Changes -> commit -> OK

Create Client

  1. Click Clients -> + New Client -> name as demo-client

  2. Go to Capabilities section and select the flows you want to use. I will select below options.

    • Code flow
    • client-credentials
    • Resource Owner Password Flow
    • Introspection
    • Token Exchange

    There are some settings we need.

    1. Redirect URIs -> Add https://localhost/callback
    2. Allowed Origins -> Add https://localhost -> Click Next
    3. Authentication Method-> Select secret -> enter demosecret as our secret -> Click Next
    4. User Authentication -> select demo -> Click Done
  3. Under Oauth/OpenID Settings, add openid to Scope. We will use it for authentication code flow later.

  4. Go to Changes -> commit -> OK

Now we should be able to use client_credential flows.

(Optional) You can see the token you are getting are opaque tokens. If you prefer to use JWT token, you can go to Token Issuers and toggle Use Access Token As JWT to on. When you tried to commit the changes, you will get below error.

1
Path: /base:profiles/profile{token-service as:oauth-service}/settings/as:authorization-server/client-capabilities/assisted-token

To solve the issue, click Goto Element button, toggle Assisted Token option to off and commit again. You should get access_token in JWT now.

Create Users

I hate to say it but I think Curity is making user management much more complicated than it should be.

First of all, there is NO WAY you can manage your user from UI. It is quite difficult for users to know what to do to create new users.

Secondly, they are following SCIM standard for user management but it is not turned on by default. Users must enable it manually.

Thirdly, users can add whatever information they want to a user. This freedom might cause serious issue if it was not formalised and restricted by the end users. For example, some users have name and address added but some don’t. This might cause issue when you try to use map user claims to token. Feel free to read the SCIM RFC if you are interested.

With that being said, if you are using HTML form as authenticator, you have two methods to add users.

Use Browser (Preferred)

This is much easier than the other one, users can go to https://<curity_domain>/authn/registration/<html_authenticator_name> to register their account on browser. In our example, the URL for registering new user is https://curity.yourdomain.com/authn/registration/demo.

Use RESTfulAPI to manage users

As I mentioned above, users must add User Management from admin UI first.

  1. Let’s go back to Profiles -> Token Service.

  2. You should find + Add related profile button at the bottom of left panel. Click that and choose Create User Management Profile.

  3. Enter Name demo-um and URL Prefix /user-management, click Next

  4. token-service should be selected under Token Service and No Authorization Manager is ticked by default. Nothing need to be change, click Next.

  5. Select every default option on this page, click Next

  6. Select default for Deployment, click Next

  7. Commit

Now we can use RESTfulAPI to manage users.

Please note you need an access token from other authentication flow to use the endpoint.

I am using client_credential to get access token in this example.

Let’s say the access_token I have is 90efd0e2-0ba9-4d64-9598-a293c94d4e65.

  • To list all users, we can use below.

    1
    2
    3
    curl --request GET \
    --url https://curity.li.lan/user-management/admin/Users \
    --header 'Authorization: Bearer 90efd0e2-0ba9-4d64-9598-a293c94d4e65'
  • To Create a new user. You can use below. Feel free to add/remove any attributes you like.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    curl --request POST \
    --url https://curity.li.lan/user-management/admin/Users \
    --header 'Authorization: Bearer 90efd0e2-0ba9-4d64-9598-a293c94d4e65' \
    --header 'Content-Type: application/json' \
    --data '{
    "agreeToTerms": "on",
    "active": true,
    "userName": "<Username>",
    "password": "<Password>",
    "phoneNumbers": [
    {
    "value": "<Phone_Number>",
    "primary": true
    }
    ],
    "emails": [
    {
    "value": "<Email>",
    "primary": true
    }
    ],
    "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:User"
    ],
    "name": {
    "givenName": "<Given_Name>",
    "familyName": "<Family_Name>"
    }
    }'
  • Delete User. We can get <User_ID> from above get all users call and then send a cURL delete request.

    1
    2
    3
    curl --request DELETE \
    --url https://curity.li.lan/user-management/admin/Users/<User_ID> \
    --header 'Authorization: Bearer 90efd0e2-0ba9-4d64-9598-a293c94d4e65'

Updating a user is even more complicated… I suggest you to delete and re-add the user instead. Please check official doc for more information.

Now we are ready to test the authentication flows.

Authentication flows

I will cover 4 authentication flows in my examples and I’ve already created a user with both username and password as test.

Password

Password flow as its name suggested is used to authenticate with username and password. Unlike the other IDPs, Curity does not support openid scope for this flow. Users can’t get an id_token with it.

1
2
3
4
5
6
7
8
curl --request POST \
--url https://curity.yourdomain.com/oauth/v2/oauth-token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=password \
--data client_id=demo-client \
--data client_secret=demosecret \
--data username=test \
--data password=test

Client credentials

Client credential flow is machine to machine flow. The access_token returned is bond to the client we created.

1
2
3
4
5
6
curl --request POST \
--url https://curity.yourdomain.com/oauth/v2/oauth-token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=demo-client \
--data client_secret=demosecret

Introspection

Some companies requires the token to be generated locally and user must include the access_token to their requests. Curity has introspection endpoint which we can use to verify the token and get the information related to the token.

1
2
3
4
5
6
7
curl --request POST \
--url https://curity.yourdomain.com/oauth/v2/oauth-introspect \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=introspect \
--data client_id=demo-client \
--data client_secret=demosecret \
--data token=<TOKEN_FROM_OTHER_FLOW>

Authorization code

Users need to use a browser for this flow. Please copy below URL to notepad, change domain, client_id, redirect_uri to match your settings. Once we open this link on browser, we should be redirected to a log in page. Please enter username and password to log in.

1
https://curity.yourdomain.com/oauth/v2/oauth-authorize?client_id=demo-client&response_type=code&redirect_uri=https://localhost/callback&scope=openid

We will be redirected back to https://localhost/callback , the URL should look like below.

1
https://localhost/callback?code=FJkRwmV6l9cEQZ0jAvy61uSeUQozLdE6&session_state=iInaq3R3hDkdfR3AbrXk1T7tbLPLRw%2BgOXu8qlvrH8A%3D.1oh0CU0HPUsw

FJkRwmV6l9cEQZ0jAvy61uSeUQozLdE6 is the code we need to redeem our tokens.

1
2
3
4
5
6
7
8
curl --request POST \
--url https://curity.yourdomain.com/oauth/v2/oauth-token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=authorization_code \
--data redirect_uri=https://localhost/callback \
--data code=FJkRwmV6l9cEQZ0jAvy61uSeUQozLdE6 \
--data client_id=demo-client \
--data client_secret=demosecret

Now we should get id_token, access_token and refresh_token.

That’s all I want to show you in this post.

My plan is to cover more IDPs in future. Started from the self-hosted ones first and then move on to cloud IDPs like Okta, Azure AD or Auth0. Although you don’t need to worry about deployment for those IDPs, they have their own challenges when you try to use it to suit your needs. If you want me to cover on cloud IDPs, please leave your comment.

That’s all for today, see you next time.