Openid Connect: a concrete implementation from an Oauth2 Server – Part 1

Context

The objective is to set up an OpenId Connect Server using the Authorization Code Flow from an Oauth2 server based on FOSOAuthServerBundle.

Existing solution based on Oauth2

Architecture

The architecture of existing solution based on Oauth2 is as follow:

Architecture with Oauth2 Server (based on library FOSOAuthServerBundle)
Architecture of existing Oauth2 Server based on library FOSOAuthServerBundle

This solution is not optimised because there is a strong coupling between the application and the library FOSOAuthServerBundle.

Flow diagram

Oauth2 Flow Diagram 
(based on library FOSOAuthServerBundle)
OpenId Connect Flow Diagram (based on authorization code flow)

The user ID is sent in authorization response flow, which is not a good practice because of security risks

Problematic

The Oauth2 server (single sign-on portal) is used by several client applications.
Each of these applications must be upgraded to be compatible with the new Openid Connect server.
The upgrade will be done gradually by application. In order to keep backward compatibility, the two versions Oauth2 and Openid Connect must coexist.
The existing Oauth2 Server is implemented with a very coupled architecture, which is not a good practice.
The current solution contains a security risk, related to sending user id through the authorization response flow.

In order to resolve these needs, we decided to use the existing library FOSOAuthServerBundle to set up Openid Connect server with a hexagonal architecture.

Design of the solution based on OpenId Connect Server

Specifications

The supported scope is openid.

Authorization endpoint

Example of authorization request

GET https://example.com/oauth/v2/auth?
client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&response_type=code
&scope=openid
&state=DHw7wtwBfcY1Acxx9JevQ
&nonce=alyFOAkQwbTA-p0iR3fotLWSmXoRkOp2mTaiS4qhdXA

Example of authorization response

HTTP 302 Found
Location: https://client.example.org/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=DHw7wtwBfcY1Acxx9JevQ&nonce=alyFOAkQwbTA-p0iR3fotLWSmXoRkOp2mTaiS4qhdXA

Token endpoint

Example of token request

POST /oauth/v2/token HTTP
Host: https://example.com
Content-Type: application/x-www-form-urlencoded
Accept: */*
 
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
&client_id=s6BhdRkqt3

Example of token response

HTTP 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
 
{
    "access_token": "SlAV32hkKG",
    "expires_in": 3600,
    "token_type": "bearer",   
    "refresh_token": "8xLOxBtZp8",
    "id_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjI1NXJkc2VlODk2ZWRzZWRmNTU4MjIyd3NkcG5ydjk1MnljdjVzbGtmNCJ9.eyJzdWIiOiIxMjM0NSIsInVzZXJuYW1lIjoib3VtYXIua29uYXRlIiwibmFtZSI6Ik91bWFyIEtPTkFURSIsImdpdmVuX25hbWUiOiJPdW1hciIsImZhbWlseV9uYW1lIjoiS09OQVRFIiwiZW1haWwiOiJvdW1hci5rb25hdGVAb3VtYXJrb25hdGUuY29tIiwicm9sZXMiOlsiUk9MRTEiLCJST0xFMiIsIlJPTEUzIl0sImlzcyI6Imh0dHBzOlwvXC9leGFtcGxlLmNvbVwvIiwiaWF0IjoxNjM2MDM0NzQ0LCJleHAiOjE2MzYwMzgzNDQsImF1ZCI6InM2QmhkUmtxdDMifQ.NfxIy3hyx7n7ShGI8hfgvsrMMHKZafdnCePDs1cEV5BE4ccJ_wctP6zFrd1vCrQLla2nVuksWAixW3XUofyBn41OCi4Pb9RxWQXpRr19tGUek0H_qmnlwOGJQq-1hif6r7LJ8I9EJkkl8em6r1mh0kfMJalAzK04yNOwd0DAGl0"
}

Using jwt.io to explore id_token

Example of refresh token request

POST /oauth/v2/token HTTP
Host: https://example.com
Content-Type: application/x-www-form-urlencoded
Accept: */*
 
client_id=s6BhdRkqt3
&client_secret=some_secret12345
&grant_type=refresh_token
&refresh_token=8xLOxBtZp8

Example of refresh token response

HTTP 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
 
{
"access_token": "TlBN45jURg",
"token_type": "Bearer",
"refresh_token": "9yNOxJtZa5",
"expires_in": 3600
}

Configuration endpoint

Configuration request

GET https://example.com/.well-known/openid-configuration

Configuration response

HTTP 200 OK
Content-Type: application/json
Cache-Control: no-store

{
    "issuer": "https://example.com/",
    "authorization_endpoint": "https://example.com/oauth/v2/auth",
    "token_endpoint": "https://example.com/oauth/v2/token",
    "userinfo_endpoint": "https://example.com/oidc/user",
    "jwks_uri": "https://example.com/oidc/certs",
    "response_types_supported": [
        "id_token"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "scopes_supported": [
        "openid"
    ],
    "token_endpoint_auth_methods_supported": [
        "client_secret_post",
        "client_secret_basic"
    ],
    "claims_supported": [
        "aud",
        "email",
        "email_verified",
        "exp",
        "iat",
        "iss",
        "locale",
        "name",
        "picture",
        "sub"
    ],
    "grant_types_supported": [
        "authorization_code"
    ]
}

JSON Web Key Configuration

Configuration request

GET https://example.com/oidc/certs

Configuration response

HTTP 200 OK
Content-Type: application/json
Cache-Control: no-store

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "kid": "255rdsee896edsedf558222wsdpnrv952ycv5slkf4",
            "alg": "RS256",
            "n": "lgKzV5hiLXySJNQeRYFJbQsYQIA1VN8MVuDqvh2Rpp1Unp1kIifCDaaQ4THg--pfI0_hy791WrAVGmrrHh_-oTjs14nDDe8ce58ouJVRVCtkCwbrtXogi5sOwTJytf5tMEZYcUF_PdJ6Mxtur9N0oVs82oEexitCfimH8DCtF08"
        },
        {
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "kid": "dn6qc35nb3fslcligviquo4m2bbt78la2xq4r2isb3",
            "alg": "RS256",
            "n": "rUWPHBGnTqCkqizwUjEQ0iis4ojs68nQ_N9PDIhUL5a1CUWmmlrwF_r131ivrvWdU_cDRU_ci5EOvv7NndNqr1CGUn6DLOamXmrD6-3g34dSg7vi5bJXFsYOCCm1Utms4iP8FmoWf-Bw-5dKDbxKQuV7yGyUvLqasaSmwLwqH9E"
        },
        {
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "kid": "ec3qbwumf4qnbsra0g3tiy7h56mnmpjrssnte85z21",
            "alg": "RS256",
            "n": "kY5ikXPoXMZoRcPhRvA675bHCNMz4bfoIsMUjnE5eNEhNdmgQ632tbuhBS313Cb8koy6dwm08uFLXbT9Aqb8D1yIgZb1AamMntcml2hsnVnCaA44lJTy4lxRBosFQWRcVbHB16RpysZkYc0abbqKNkPsqyJfBsluhvNByFAH5s8"
        }
    ]
}

There is one key configuration per environment: dev, test and prod.

Flow diagram

OpenId Connect Flow Diagram 
(based on authorization code flow)
OpenId Connect Flow Diagram (based on authorization code flow)

Architecture

Architecture with OpenID Connect Server using Authorization Code Flow
Architecture with OpenID Connect Server using Authorization Code Flow (with hexagonal architecture)

Details of architecture

Details of architecture of interactions with OpenID Connect server using Authorization Code Flow
Details of architecture of interactions with OpenID Connect server using Authorization Code Flow (with hexagonal architecture)

Details of infrastructure side

Component choices are as follows:

Oauth2 Server: FOSOAuthServerBundle
JWT: firebase/php-jwt
JWK: Strobotti/php-jwk

Go further

If you need to fresh user infos, you must implement an api endpoint. It returns id_token without security constraints.

Example of user infos request

GET /oidc/user HTTP
Host: https://example.com

Headers
Authorization: Bearer SlAV32hkKG

SlAV32hkKG represents the acces_token
Bearer represents the token header name

Example of user infos response

HTTP 200 OK
Content-Type: application/json
 
{
    "sub": "12345",
    "username": "oumar.konate",
    "name": "Oumar KONATE",
    "given_name": "Oumar",
    "family_name": "KONATE",
    "email": "oumar.konate@oumarkonate.com",
    "roles": [
        "ROLE1",
        "ROLE2",
        "ROLE3"
    ]
}

Soon a new article to give the technical details of the implementation in PHP.

2 thoughts on “Openid Connect: a concrete implementation from an Oauth2 Server – Part 1”

  1. Great article!
    Do you have any intentions for Part 2?

    “Soon a new article to give the technical details of the implementation in PHP.” I suppose it was meant for that.

Leave a Reply

Your email address will not be published. Required fields are marked *