Skip to main content

Understanding OIDC Authentication Flows

· 8 min read
Josh Kaplan

In this article, we'll discuss authentication flows in OpenID Connect (OIDC). We'll work through a hands-on example using Keycloak to demonstrate how OIDC works in practice. More information on Keycloak's authorization flows can be found here or in the OIDC Spec.

Set Up

Keycloak Server

To begin, we chose to use Keycloak as our Identity Provider (IDP) and OpenID Connect (OIDC) provider. Keycloak is an open source identity and access management solution that makes it easy to secure applications and services with little to no code. It is a single sign-on provider that allows you to authenticate users across all applications and services in your organization.

It's also free and open source. For that reason we chose to stand it up on Docker, which allowed us to quickly stand up a demo environment that allowed use to share all the configuration details, URLs, tokens, etc. in this post without having to worry about exposing any sensitive information. We were then to simply tear down the environment at the end of this effort.

docker-compose.yml
version: '3.2'
name: 'lab'

services:
keycloak:
image: quay.io/keycloak/keycloak:20.0.3
container_name: keycloak.lab
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HEALTH_ENABLE: true
command: start-dev --log-level=INFO
Insecure Configuration Warning

Note that we are running Keycloak in dev mode in this example. We are also using insecure credentials and serving content with HTTP not HTTPS. We did this to make it easier to quickly demonstrate key concepts without getting bogged down in configuration details.

Do not run in dev mode in production.

After this, we can simple run docker compose up to run the Keycloak server.

Configuring Keycloak

Step 1. Create a Realm

First we need to create a realm. Realms are Keycloaks way of grouping and isolating users, applications, and configuration. In this case we are creating a realm called "Demo".

View showing realm creation screen in keycloak

Step 2. Create a User

Next we need to create a user. This user will be used to log into our client application. We can click on the "Users" tab and then click "Add User" to create a new user.

View to create user

We also need to set default credentials for that user. On the "Credentials" tab we can set a new password.

Update user credentials

In this case we are using the username demo and the password 1234. Make sure to uncheck the "Temporary" checkbox so that the password is not reset on the next login.

Step 3. Create a Client

Next we need to create a client. This client will represent our client application. We can click on the "Clients" tab and then click "Create" to create a new client.

Creating a client

In this case will use the default settings initially, but we'll need to update the "Valid Redirect URIs" to include the URL of our client application. In this case we are using http://localhost:3000/*.

In the client settings we need to set the "Valid Redirect URIs" field and "Web Origins" field. We use "*" for web origins to allow all hosts to use this client. We set the valid redirect URIs to http://localhost* to support our local React application.

Configuring client

Insecure Configuration Warning

Note that we are not using a secure configuration in this example. We are redirecting to localhost using HTTP. We also allow all hosts to use this client. We did this to make it easier to quickly demonstrate key concepts without getting distracted by configuration details.

Do not use this in production.

The Client Application

To complete our setup, we need to create a client application that will authenticate with Keycloak. In this case we are using a React application, but the same principles apply to any application. We'll glance past much of the detail here, but the core elements are the keycloak configuration and the user login.

We've created a simple keycloak.js file that exports a keycloak client. This client is configured with the realm, url, and client id that we created in the previous step.

keycloak.js
import Keycloak from "keycloak-js";

const keycloak = new Keycloak({
realm: "Demo",
url: "http://localhost:8080",
clientId: "demo",
});

export default keycloak;

We then use this client in our main React app. The below code is a simplification of our app the demonstrates how we use the keycloak client to authenticate the user and retrieve the user's profile and token. We then set this user information as a state variable in our app and pass it to other components via a context provider.

App.js
import keycloak from './keycloak';

function App() {
const [user, setUser] = useState<UserIF | undefined>()

const initKeycloak = useCallback(async() => {
let token: string | undefined = keycloak.token
if (token) {
return
}
const res = await keycloak.init({
onLoad: 'login-required',
checkLoginIframe: false,
enableLogging: true
})

// If authenticated, set the user state
if (res) {
setUser({
profile: { ...keycloak.tokenParsed }
token: keycloak.token
})
}
// Otherwise, clean up state
else {
setUser(null)
}
}, [])

useEffect(() => {
if (!keycloak.token) {
initKeycloak()
}
}, [initKeycloak])

return (
<UserContext.Provider value={user}>
/*...*/
</UserContext.Provider>
)
}

In summary, the React app checks if the user is already authenticated by checking for a token. If the token does not exist yet, it will call keycloak.init() to initialize keycloak which will trigger the OIDC flow to authenticate the user. The next section describes this flow in more detail.

Authorization Code Flow

We'll now walk through the authorization code flow to understand how it works. Authorization Code Flow is one type of authentication flow in OIDC, but it is the most common.

Once we go to http://localhost:3000 to access our web application, the first thing that happends is we are redirected to the Keycloak server to authenticate.

This is an important part of the authorization code flow because the client application never sees the user's credentials. Instead, the user is redirected to the IDP to authenticate. This is a key security feature of OIDC. The redirect URI is provided to the IDP as part of the authentication request so that the OIDC provider knows where to redirect the user back to after authentication.

Let's take a look at the network requests that occur once we log in. The first request is to the Keycloak server to authenticate. This is a POST request to http://localhost:8080/realms/Demo/login-actions/authenticate. The user's credentials are passed to the server as part of the request body.

Notice we get a 302 redirect response back from the server. This is the server redirecting us to the redirect URL that we provided in the authentication request. In this case, the callback URL is http://localhost:3000/. We can see this in the Location response header.

Also note the Set-Cookie header. That sets the KEYCLOAK_IDENTITY token in the browser. This is critical for the next step in the flow.

Next, we are redirected back to the client application, specifically the redirect URI. This is the callback URL that we provided in the authentication request. We get a 200 OK response and next the React JS bundle is loaded (e.g. bundle.js). This contains all our client application code that logs in the user.

Finally, the client application makes another request to Keycloak to get the user's token.

The code that was provided to the client application in the callback URL is passed to the Keycloak server as part of the request body as well as the identity token as a cookie.

Together these are used to authenticate the request and return the user's token. The server then responds with a 200 OK response and the user's identity, access, and refresh tokens.

Now the client application has access to the tokens with it can use to authenticate future requests to the server. The access token can now be passed as an authentication header when making API calls to a backend service. The backend service can then validate the token with the IDP to authenticate the user.

Implicit Flow

Without going through all the details again, we'll briefly discuss the implicit flow. The implicit flow is similar to the authorization code flow, but does not require the extra step to request the token.

In implicit flow, the IDP server includes the token as part of the redirect.

Direct Grant

The direct grant is simpler, but less secure. With direct grant, the client application sends the user's credentials directly to the IDP server and gets the token back in response.

A Note on Terminology

Direct Grant is Keycloak's term for this flow. The OAuth 2.0 spec refers to this as the Resource Owner Password Credentials Grant. The spec is available here.

It's simpler because it does not require the extra redirect step. But it's less secure because the client application has access to the user's credentials. It also means the application needs to have a log in page.

Summary

In this article, we discussed authentication flows in OpenID Connect (OIDC). We provided a hands-on example of Authorization Code Flow using Keycloak with a simple React application. We also briefly discussed the Implicit Flow and Direct Grant.

Use the examples above to try out the different flows yourself and explore the topic further.