Understanding OAuth2, OpenID and OpenID Connect (OIDC), how they relate, how the communications are established, and how to architecture your application with the given access, refresh and id tokens is confusing.
There was a time when securing your application was not so complicated to implement. Users were presented with a login form asking for their usernames and passwords. The server validated those credentials against the storage, mostly a SQL database or a LDAP/Ad directory. Once authenticated, a session was maintained with a cookie storing the ID used to track and load the user information.
Using OAuth2 and OpenID Connect can be quite simple with traditional applications. There exist multiple libraries and frameworks hiding the different steps involved. To the end-users, we are all used to being redirected into an external provider, being GitHub, Google, Facebook, and the likes. The process is familiar and well accepted. However, it starts to be more complicated when you want to replicate the steps involved or when you wish to build alternative architecture including mobile applications, microservices architectures, single-page applications, and static front-ends such as with the JAMstack paradigm. Public applications (SPA) share the particularity to be unsecured by nature and to speak with multiple API servers.
Part 1, OAuth2 and OpenID Connect, a gentle and working introduction focuses on integrating your first application with an OpenID Connect server (Dex) and experienced the Authorization Code Flow with an external provider. Oauth and OpenID Connect strategies are complicated and confusing, reading that part will shed some light.
Part 2, OAuth2 and OpenID Connect for microservices and public applications, provides a deep dive into the OpenID code flow by describing, explaining, and illustrating each step. Once completed, you will be able to apply them to your client and public applications (mobile, SPA, …) without the need for extra tools.
It is not so complicated. OpenID is a protocol for authentication while OAuth is for authorization. With OpenID, authentication is delegated. With OAuth, authorization is delegated as well.
OAuth is OAuth2. No one is using such a thing as OAuth1. OpenID Connect, also named OIDC, brings the features of OpenID and transform OAuth into an authentication protocol. Thus, it is a thin layer on top of OAuth. Most OAuth2 servers are also OpenID Connect servers.
Since we are speaking about delegation, we will use Dex a federated OpenID Connect provider. Dex can authenticate users against its internal database and external databases, think LDAP. It can also delegate the authentication to other OpenID providers, think GitHub, Google, … Thus, Dex can act as a facade placed in front of multiple providers.
OAuth speaks about the following actors: the Resource Owner (RO), the client, the Authorisation Server (AS), and the Resource Server (RS).
The Resource Owner is the end-user but it could also be the organization, a project, or any other entity. The client is the application that needs to authenticate the user. It can be a web app, a mobile app, … The OAuth server, which is also the OpenID Connect server, is called the Authorization Server (AS) and it is in charge of issuing the tokens and determine who the end-user is. Finally, there is the Resource Server who is serving the resource, such as the API.
JWT, for JSON Web Token, pronounced “jot”, is a special format used by OAuth2 to serialize tokens. It is not the only format but certainly the most commonly used.
OAuth2 comes with multiple flows. There are even more in OpenID Connect. Most of them are light declinations of the Authorization Code Flow. Not all of them are as secure and supported by your OAuth server.
We will work on the Authorization Code Flow because it is by far the most common. If you are interested in how to use it with a public client application, read the follow-up article, OAuth2 and OpenID Connect for microservices and public applications (Part 2). It shows how PKCE extends the flow to eliminate the presence of the secret key to make it suitable for public applications.
But first, we need an OAuth server. We will use Dex, an identity service that uses OpenID Connect to federate the authentication for other apps. It comes with several connectors such as LDAP, SAML 2.0, OIDC, GitHub, Google, … It is very lightweight and written in Go. It is part of the Cloud Native Computing Foundation (CNCF) and it is a popular choice with Kubernetes.
Installation from source is fairly simple, assuming
go is installed on your host:
go get github.com/dexidp/dex cd $GOPATH/src/github.com/dexidp/dex make
The server comes with a predefined configuration located in the “examples/config-dev.yaml ” folder. We will reference it to start the server:
./bin/dex serve examples/config-dev.yaml
The configuration defines an example application client with its
staticClients: - id: example-app redirectURIs: - 'http://127.0.0.1:5555/callback' name: 'Example App' secret: ZXhhbXBsZS1hcHAtc2VjcmV0
It is designed to match the settings of the client application inside the “examples/example-app” folder. You can start it with:
make examples ./bin/example-app
Dex federates multiple authentication providers and it also comes with a default internal storage. In the example configuration, this storage is configured with a single user whose email and password are “firstname.lastname@example.org” and “password”.
Navigate the example by opening your browser at
http://localhost:5555/. Hit login and choose “Login with Email” and use those credentials. On completion, you shall see the token obtained from DEX as well as its claims.
You just experienced a basic authentication code flow. The client requested the user to validate his identity (signup, login, …). He is redirected to the Authorisation Server who asks him to log in. This step could be transparent. For example, the user is already logged in, ask for username and password and even use two-factor authentication (2FA). Once authenticated, the Authorisation Server asks for consent. He allows the client application to access resources on your behalf. This step is also optional.
We will now activate the GitHub provider in Dex. Chances are that everyone reading this article until this line already has a GitHub account. Otherwise, create an account or adjust those instructions to a connector of your choice such as Google. Login in to your GitHub account and register a new OAuth application. With the following attributes:
- Application name:
- Homepage URL:
- Authorization callback URL:
In the next screen, generate a new client secret, import it into your Dex configuration as well as the client ID. In “examples/config-dev.yaml”, under the
connectors section, add:
- type: github # Required field for connector id. id: github # Required field for connector name. name: GitHub config: # Credentials can be string literals or pulled from the environment. clientID: 07e429328dXXXXXXXXXX clientSecret: a376ab209ea2e1127bcdcc63e06672XXXXXXXXXX redirectURI: http://127.0.0.1:5556/dex/callback
Now, restart the Dex server, login, and choose “Login with GitHub”.
You have just integrated your first application with an OpenID Connect server. Indeed, two OpenID Connect servers. Your client application interacts with the Dex OAuth server which is itself a client to the GitHub OAuth server. You also experienced the Authorization Code Flow. In the follow-up article, OAuth2 and OpenID Connect for microservices and public applications (Part 2), we will go into more details, explaining and illustrating each step of the flow to understand and reproduce them in your code. It will also describe PKCE, a little extension to the Authorization Code Flow, which removes the usage of the secret key and make it suitable for public application.