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:
This solution is not optimised because there is a strong coupling between the application and the library FOSOAuthServerBundle.
Flow diagram
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
Architecture
Details of 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.
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.
Hi Calin! Yes, i intend to do the 2e part of this article as soon