# OIDC

beta Contributors are welcome

oidc-provider (opens new window) is an OAuth 2.0 Authorization Server with OpenID Connect and many additional features and standards implemented.

Certification

Filip Skokan has certified (opens new window) that oidc-provider (opens new window) conforms to the following profiles of the OpenID Connectâ„¢ protocol

  • OP Basic, Implicit, Hybrid, Config, Dynamic, Form Post, and 3rd Party-Init
  • OP Front-Channel Logout, Back-Channel Logout, RP-Initiated Logout, and Session Management
  • OP FAPI R/W MTLS and Private Key

# Features

Ts.ED provides decorators and services to create an OIDC provider with your Ts.ED application.

  • Create interactions policies,
  • Create views,
  • Use adapters to connect oidc-provider with redis/mongo/etc...
  • Automatically create jwks keys on startup

# Installation

Before using the @tsed/oidc-provider package, we need to install the oidc-provider (opens new window) module.

npm install --save oidc-provider
npm install --save @tsed/oidc-provider @tsed/jwks @tsed/adapters
1
2

Then we need to follow these steps:

  • Configure the oidc server,
  • Create the Accounts provider
  • Create the Interactions controller,
  • Create our first Login interaction and views,

TIP

Select "OpenID Connect provider" upon initialization with the Ts.ED CLI and the following will be automatically generated.

# Configuration

To use oidc-provider with Ts.ED it requires some other Ts.ED features to work properly.

  • Adapters to manage database connection,
  • Views to display pages.

Use tsed init yourApp to create a TSed application and adjust Server.ts:

...
import {Configuration} from "@tsed/di";
import {Accounts} from "./services/Accounts"; 
import {InteractionsCtrl} from "./controllers/oidc/InteractionsCtrl"; 

export const rootDir = __dirname;

@Configuration({
  httpPort: 8083,
  mount: {
   "/": [InteractionsCtrl]
  },
  adapters: {
    lowdbDir: join(rootDir, "..", '.db'),
    Adapter: FileSyncAdapter
  },
  oidc: {
    // path: "/oidc",
    Accounts: Accounts,
    jwksPath: join(__dirname, "..", "..", "keys", "jwks.json"),
    clients: [
      {
        client_id: "client_id",
        client_secret: "client_secret",
        redirect_uris: [
          "http://localhost:3000"
        ],
        response_types: ["id_token"],
        grant_types: ["implicit"],
        token_endpoint_auth_method: "none"
      }
    ],
    claims: {
      openid: ["sub"],
      email: ["email", "email_verified"]
    },
    features: {
      // disable the packaged interactions
      devInteractions: {enabled: false},
      encryption: {enabled: true},
      introspection: {enabled: true},
      revocation: {enabled: true}
    }
  },
  views: {
    root: `${rootDir}/views`,
    extensions: {
      ejs: "ejs"
    }
  }
})
...
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# Options

import {Type} from "@tsed/core";
import {JwksKeyParameters} from "@tsed/jwks";
import {Configuration} from "oidc-provider";
import {OidcAccountsMethods} from "./OidcAccountsMethods";

export interface OidcSettings extends Configuration {
  /**
   * Path on which the oidc-provider instance is mounted.
   */
  path?: string;
  /**
   * Issuer URI. By default Ts.ED create issuer with http://localhost:${httpPort}
   */
  issuer?: string;
  /**
   * Path to store jwks keys.
   */
  jwksPath?: string;
  /**
   * Generate jwks from given certificates
   */
  certificates?: JwksKeyParameters[];
  /**
   * Secure keys.
   */
  secureKey?: string[];
  /**
   * Enable proxy.
   */
  proxy?: boolean;
  /**
   * Injectable service to manage accounts.
   */
  Accounts?: Type<OidcAccountsMethods>;
  /**
   * Injectable service to manage clients.
   */
  // Clients?: Type<OidcClientsMethods>;
}

declare global {
  namespace TsED {
    interface Configuration {
      oidc: OidcSettings;
    }
  }
}
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
37
38
39
40
41
42
43
44
45
46
47

Documentation on other options properties can be found on the oidc-provider (opens new window) documentation page.

WARNING

It is advised to set path to /oidc to prevent oidc-provider becoming the default exception handler on all routes. In future versions of Ts.ED this will be the default value.

# TLS proxy

The OpenID Connect specification does not allow unsecured HTTP requests and oidc-provider blocks them by default. While there is a workaround (opens new window), the proper way is to use a TLS offloading proxy in front of your app. When developing, the easiest way is to use Caddy (opens new window). To use it, set proxy: true in your options and then run:

caddy reverse-proxy --from localhost:8443 --to localhost:8083
1

# Accounts

oidc-provider requires an Account model to find an account during an interaction. The model can be used in conjunction with the adapter to fetch an account.

    TIP

    Claims method is used by oidc-provider to expose this information in the userInfo endpoint.

    TIP

    We use the $onInit hook to create the first account automatically. You can adapt the script to your needs.

    # Interactions

    Interactions are the user flows in oidc-provider. For example, the login page is considered by oidc-provider as an interaction. We can define many interactions during the authentication flow, for example:

    • Login,
    • E-mail verification,
    • Password recovery,
    • Sharing account data consent,
    • etc.

    To have a working OIDC server with Ts.ED, we need to create at least a login and a consent interaction. To start, we have to create the Interactions controller which will be responsible to run all of our future custom interactions.

    In your controller's directory, create the oidc/InteractionsCtrl.ts file and copy the following code:

    import {Get} from "@tsed/common";
    import {Interactions, OidcCtx} from "@tsed/oidc-provider";
    import {Name} from "@tsed/schema";
    import {ConsentInteraction} from "../../interactions/ConsentInteraction";
    import {CustomInteraction} from "../../interactions/CustomInteraction";
    import {LoginInteraction} from "../../interactions/LoginInteraction";
    
    @Name("Oidc")
    @Interactions({
      path: "/interaction/:uid",
      children: [
        LoginInteraction,
        ConsentInteraction,
        CustomInteraction
      ]
    })
    export class InteractionsCtrl {
      @Get("/")
      async promptInteraction(@OidcCtx() oidcCtx: OidcCtx) {
        return oidcCtx.runInteraction();
      }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    Note

    The controller Interactions exposes the routes to display any interaction. Here we expose the route GET /interation/:uid

    The uid is the unique session id used by oidc-provider to identify the current user flow.

    Now that we have our interactions controller, we can create our first interaction.

    Create a new directory interactions. We will store all custom interactions in this directory.

      TIP

      $prompt is a special hook called by your Interactions controller.

      At this step, you can start the OIDC server and check the logs server to see if the well-known configuration has been correctly exposed:

      [2021-01-04T07:35:31.523] [INFO ] [TSED] - WellKnown is available on http://0.0.0.0:8083/.well-known/openid-configuration
      
      1

      Try also to open the link in your browser!

      Now, we need to add the Views to display our login page. Create a views directory on root level and create the following files:

        The login page is ready to be displayed. To test it, open the following link:

        http://localhost:8083/auth?client_id=client_id&response_type=id_token&scope=openid&nonce=foobar&redirect_uri=http://localhost:3000
        
        
        1
        2
        Oidc login page

        # Alter OIDC policy

        Ts.ED emits a special $alterOidcPolicy event when @tsed/oidc-provider links interactions with OIDC policy. You can change the policy configuration by adding $alterOidcPolicy on InteractionsCtrl:

        import {Get} from "@tsed/common";
        import {Interactions, OidcCtx, DefaultPolicy} from "@tsed/oidc-provider";
        import {LoginInteraction} from "../../interactions/LoginInteraction";
        
        @Interactions({
          path: "/interaction/:uid",
          children: [
            LoginInteraction // register its children interations 
          ]
        })
        export class InteractionsCtrl {
          @Get("/")
          async promptInteraction(@OidcCtx() oidcCtx: OidcCtx) {
            return oidcCtx.runInteraction();
          }
        
          $alterOidcPolicy(policy: DefaultPolicy) {
            // do something
           
            return policy
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        # Debug

        Use DEBUG=oidc-provider:* for debugging oidc-provider.

        # Support oidc-provider

        If you or your business uses oidc-provider (opens new window), please consider becoming a sponsor, so we can continue maintaining it and adding new features carefree.

        # Author

          # Maintainers

            Last Updated: 10/23/2021, 5:14:48 AM

            Other topics