6.1. ID Broker architecture and flows#

This document is the result of researching different possibilities on how to implement SSO between the student and teachers, service providers and the school authorities.

Warning

Because of the research setup, http:// is used everywhere, and not https://, which MUST be used in a real setup.

Warning

There are also other errors in the details of the recorded flow, which are going to be corrected. WIP.

6.1.1. Theory#

One should be somewhat familiar with SAML and OAuth2/OpenID Connect (OIDC) flows before reading the details of the ID Broker auth architecture. Also Understanding JWT helps.

6.1.2. Requirements for the auth flow#

One requirement is that the school authorities can keep on using SAML. We found that we can’t just start with SAML and then ‘hand over’ to OAuth2 (see below Auth code flow II - First SAML). Instead, we need to interweave OAuth2 and SAML flows.

6.1.3. ID Broker Flow#

This presents the mix of SAML and OAuth2.

6.1.3.1. Mapping of terms and roles#

  • student - the student/browser using the application.

  • School portal - the portal where the student clicks on a link to the Service Provider (SP) (e.g. Bettermarks).

  • School IDP (SAML) - the Identity Provider (IDP) that speaks SAML and is provided by UCS.

  • SSO Broker (Keycloak) - the SSO Broker runs APIs, and uses Keycloak to manage authentication.

  • Self-disclosure API - an API that provides useful information to the service provider (e.g. Bettermarks).

  • service provider - the actual application that wants to consume data.

6.1.3.2. Flowchart - “OIDC first”#

The following figure shows the flow from OIDC, to SAML, to OIDC.

_images/flows_oidc_first.svg

Fig. 6.1 OAuth2 / SAML / OAuth2 flow#

click to get a larger version of the diagram

6.1.3.3. Details with messages#

6.1.3.3.1. 1. visit site#

The initiate step is always the user/student visiting her school portal. We need this in order to get information about who the IDP is.

6.1.3.3.3. 3. Request to protected resource on client#

She follows the link to the protected resource.

http://10.205.2.110:5000/private

6.1.3.3.4. 4. redirect:SSO Broker#

The protected resource doesn’t know her, so an OIDC flow gets initiated. This would be sent to the authorization server in OAuth2, and for us it is the SSO Broker. Scope describes what the testapp is asking authorization for:

Location: https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/protocol/openid-connect/auth?
 client_id=python-client&
 redirect_uri=http%3A%2F%2F10.205.2.110%3A5000%2Foidc_callback&
 scope=openid+email+profile&
 access_type=offline&
 response_type=code&
 kc_idp_hint=ExampleSA2&
 state=eyJjc3JmX3Rva2VuIjogInJrT2dDb3ZUR25scElTR055eGtKdC1RT2tFYUY2dUhRIiwgImRlc3RpbmF0aW9uIjogImV5SmhiR2NpT2lKSVV6VXhNaUo5LkltaDBkSEE2THk4eE1DNHlNRFV1TWk0eE1UQTZOVEF3TUM5d2NtbDJZWFJsSWcuZkhPblgyZE4xN0k3WFlxUlViQThldFptcEFmS2U0eWpQaGxWckdERmZMQnFieUk4MmlYZ2ZCNGs3RkFCWVJtdFdaeTN1dmRFaHg1MVV0cWUzRWsxd0EifQ%3D%3D
  • kc_idp_hint is based on idp_hint value in session

6.1.3.3.5. 5. auth request#

She follows the redirect:

https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/protocol/openid-connect/auth?
 client_id=python-client&
 redirect_uri=http%3A%2F%2F10.205.2.110%3A5000%2Foidc_callback&
 scope=openid+email+profile&
 access_type=offline&
 response_type=code&
 kc_idp_hint=ExampleSA2&
 state=eyJjc3JmX3Rva2VuIjogInJrT2dDb3ZUR25scElTR055eGtKdC1RT2tFYUY2dUhRIiwgImRlc3RpbmF0aW9uIjogImV5SmhiR2NpT2lKSVV6VXhNaUo5LkltaDBkSEE2THk4eE1DNHlNRFV1TWk0eE1UQTZOVEF3TUM5d2NtbDJZWFJsSWcuZkhPblgyZE4xN0k3WFlxUlViQThldFptcEFmS2U0eWpQaGxWckdERmZMQnFieUk4MmlYZ2ZCNGs3RkFCWVJtdFdaeTN1dmRFaHg1MVV0cWUzRWsxd0EifQ%3D%3D

The state contains:

{
    "csrf_token": "rkOgCovTGnlpISGNyxkJt-QOkEaF6uHQ",
    "destination": "eyJhbGciOiJIUzUxMiJ9.Imh0dHA6Ly8xMC4yMDUuMi4xMTA6NTAwMC9wcml2YXRlIg.fHOnX2dN17I7XYqRUbA8etZmpAfKe4yjPhlVrGDFfLBqbyI82iXgfB4k7FABYRmtWZy3uvdEhx51Utqe3Ek1wA"
}

which means

{
  "destination": {
    "headers": {
      "alg": "HS512"
    },
    "payload": "http://10.205.2.110:5000/private",
    "signature": "fHOnX2dN17I7XYqRUbA8etZmpAfKe4yjPhlVrGDFfLBqbyI82iXgfB4k7FABYRmtWZy3uvdEhx51Utqe3Ek1wA"
  },
  "csrf_token": "rkOgCovTGnlpISGNyxkJt-QOkEaF6uHQ"
}

6.1.3.3.6. 6. redirect: Keycloak SAML login#

Keycloak doesn’t hasn’t got the browser authenticated yet. So the user gets a redirect to the SAML endpoint.

Location: https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/login?
 session_code=U7QqTnIs1CTL7ATh1cf_XFCaCi95oPE4v7Uwc5baH2s&
 client_id=python-client&
 tab_id=_NzbfJl6_b0
  • set session cookies

6.1.3.3.7. 7. follow redirect#

The user follows to the SAML endpoint, asking for a login.

https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/login?
 session_code=U7QqTnIs1CTL7ATh1cf_XFCaCi95oPE4v7Uwc5baH2s&
 client_id=python-client&
 tab_id=_NzbfJl6_b0

6.1.3.3.8. 8. redirect: School IDP#

The endpoint doesn’t know the user yet. But because of step (5) Keycloak knows to which IDP she needs to be sent. The request contains a proper SAML request.

Location: https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/SSOService.php
{
    "SAMLRequest": "< see below, base 64 encoded >",
    "RelayState": "4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client"
}

Which means

<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                    xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
                    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                    AssertionConsumerServiceURL="https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint"
                    Destination="https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/SSOService.php"
                    ForceAuthn="false"
                    ID="ID_cc9da054-144b-4c42-8dbc-2008860f2f01"
                    IssueInstant="2022-02-01T12:29:59.874Z"
                    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
                    Version="2.0">
    <saml:Issuer>https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint/descriptor</saml:Issuer>
    <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
        <dsig:SignedInfo>
            <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <dsig:Reference URI="#ID_cc9da054-144b-4c42-8dbc-2008860f2f01">
                <dsig:Transforms>
                    <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </dsig:Transforms>
                <dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <dsig:DigestValue>P0P7K5x7yvgkllsr33iGeRtmLonN2R/T7oim2rb/vbw=</dsig:DigestValue>
            </dsig:Reference>
        </dsig:SignedInfo>
        <dsig:SignatureValue>...</dsig:SignatureValue>
        <dsig:KeyInfo>
            <dsig:X509Data>
                <dsig:X509Certificate>...</dsig:X509Certificate>
            </dsig:X509Data>
            <dsig:KeyValue>
                <dsig:RSAKeyValue>
                    <dsig:Modulus>...</dsig:Modulus>
                    <dsig:Exponent>AQAB</dsig:Exponent>
                </dsig:RSAKeyValue>
            </dsig:KeyValue>
        </dsig:KeyInfo>
    </dsig:Signature>
    <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>

6.1.3.3.9. 9. login#

And of she goes to the UCS system, where the SAML auth is configured.

POST https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/SSOService.php
{
    "SAMLRequest": "< see above, base 64 encoded >",
    "RelayState": "4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client"
}

6.1.3.3.10. 10. Redirect: login form#

Here she gets a redirect to the login form…

Location: https://ucs-sso.school2.intranet/simplesamlphp/module.php/core/loginuserpass.php?
 AuthState=_008efe5d4af5d17b53125e25abd5cf49d80b6b4215%3Ahttps%3A%2F%2Fucs-sso.school2.intranet%2Fsimplesamlphp%2Fsaml2%2Fidp%2FSSOService.php%3Fspentityid%3Dhttps%253A%252F%252Flogin.keycloak.idbroker.intranet%252Fauth%252Frealms%252FID-Broker%252Fbroker%252FExampleSA2%252Fendpoint%252Fdescriptor%26cookieTime%3D1643718600%26RelayState%3D4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client

6.1.3.3.11. 11. Request login form#

…which she requests…

Location: https://ucs-sso.school2.intranet/simplesamlphp/module.php/core/loginuserpass.php?
 AuthState=_008efe5d4af5d17b53125e25abd5cf49d80b6b4215%3Ahttps%3A%2F%2Fucs-sso.school2.intranet%2Fsimplesamlphp%2Fsaml2%2Fidp%2FSSOService.php%3Fspentityid%3Dhttps%253A%252F%252Flogin.keycloak.idbroker.intranet%252Fauth%252Frealms%252FID-Broker%252Fbroker%252FExampleSA2%252Fendpoint%252Fdescriptor%26cookieTime%3D1643718600%26RelayState%3D4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client

6.1.3.3.12. 12. login form#

And gets as a plain html form. She fills out the form…

6.1.3.3.13. 13. login post#

And posts the form to the UCS server.

POST https://ucs-sso.school2.intranet/simplesamlphp/module.php/core/loginuserpass.php?
{
    "username": "84h5x0g7ex",
    "password": "univention",
    "AuthState": "_008efe5d4af5d17b53125e25abd5cf49d80b6b4215:https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/SSOService.php?spentityid=https%3A%2F%2Flogin.keycloak.idbroker.intranet%2Fauth%2Frealms%2FID-Broker%2Fbroker%2FExampleSA2%2Fendpoint%2Fdescriptor&cookieTime=1643718600&RelayState=4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client",
    "submit": ""
}

6.1.3.3.14. 14. redirect SSO Broker#

Upon successful login the user needs to do a POST to the Keycloak server. But we can’t redirect to there, because http doesn’t allow POST redirects. Hence, this is done in JavaScript.

6.1.3.3.15. 15. follow redirect (POST)#

So we have this POST to Keycloak with the SAML ticket in the body. We have left out the certificates for readability.

POST https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint
{
    "SAMLResponse": "< see below, base 64 encoded >",
    "RelayState": "4vuHEB1r-lkWNvElsxy4si8qgTCfnTM77J8Z7AIb5P8.qt_dqhNpMEY.python-client"
}
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                ID="_93d030b688ccac198bc06adda4a76ff67eb8d18a25"
                Version="2.0"
                IssueInstant="2022-02-01T12:30:02Z"
                Destination="https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint"
                InResponseTo="ID_cc9da054-144b-4c42-8dbc-2008860f2f01">
    <saml:Issuer>https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/metadata.php</saml:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#_93d030b688ccac198bc06adda4a76ff67eb8d18a25">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>1dFmdN5RyX9MpzoWNdn7H0UvYHdACURaVVeIHVQ/4SQ=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>...</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>...</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns:xs="http://www.w3.org/2001/XMLSchema"
                    ID="_f0579590dbcee44e77ca6f5b15924d446de19e5e41"
                    Version="2.0"
                    IssueInstant="2022-02-01T12:30:02Z">
        <saml:Issuer>https://ucs-sso.school2.intranet/simplesamlphp/saml2/idp/metadata.php</saml:Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
                <ds:Reference URI="#_f0579590dbcee44e77ca6f5b15924d446de19e5e41">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                    <ds:DigestValue>wEtCbO7niag4S3jSyksx2v1oMN/ZXsemkngzug8aSrc=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>...</ds:SignatureValue>
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>...</ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </ds:Signature>
        <saml:Subject>
            <saml:NameID SPNameQualifier="https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint/descriptor"
                        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_b5c01dce5bcb1c6df188f00994452edb2ac344ff8c</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2022-02-01T12:35:02Z"
                                            Recipient="https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint"
                                            InResponseTo="ID_cc9da054-144b-4c42-8dbc-2008860f2f01"/>
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2022-02-01T12:29:32Z" NotOnOrAfter="2022-02-01T12:35:02Z">
            <saml:AudienceRestriction>
                <saml:Audience>https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/ExampleSA2/endpoint/descriptor</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2022-02-01T12:30:02Z"
                            SessionNotOnOrAfter="2022-02-02T00:30:02Z"
                            SessionIndex="_76728957c11b9423982865245e078b2555e7478eeb">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="entryUUID"
                            NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
                <saml:AttributeValue xsi:type="xs:string">602ac394-17a6-103c-89a6-49b4f56b1bc0</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

6.1.3.3.16. 16. redirect:#

TODO description

https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/login-actions/first-broker-login?
 client_id=python-client&
 tab_id=qt_dqhNpMEY

6.1.3.3.17. 17. redirect:#

TODO description

https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/broker/after-first-broker-login?
 session_code=KHfAMlxOVSIyISKwo4EuK4l--gTxk6QkCe26dI-UCS8&
 client_id=python-client&
 tab_id=qt_dqhNpMEY

6.1.3.3.18. 18. redirect: service provider#

Now, with the correct SAML ticket being sent to Keycloak the browser is authenticated. From the OAuth2 point of view this has happened “behind the scenes”. The OAuth2 flow continues with a session, and an auth code, and a redirect to the testapp.

Location: http://10.205.2.110:5000/oidc_callback?
 state=eyJjc3JmX3Rva2VuIjogImliT19ncWVXRFdNYnFtTUxnNUczZmVjM3BjLXREYnRXIiwgImRlc3RpbmF0aW9uIjogImV5SmhiR2NpT2lKSVV6VXhNaUo5LkltaDBkSEE2THk4eE1DNHlNRFV1TWk0eE1UQTZOVEF3TUM5d2NtbDJZWFJsSWcuZkhPblgyZE4xN0k3WFlxUlViQThldFptcEFmS2U0eWpQaGxWckdERmZMQnFieUk4MmlYZ2ZCNGs3RkFCWVJtdFdaeTN1dmRFaHg1MVV0cWUzRWsxd0EifQ%3D%3D&
 session_state=e31ac91a-a628-425f-9b36-aece4a1ce2fd&
 code=bbd3bdc4-f5dd-486b-bcd7-e2b70823cadb.e31ac91a-a628-425f-9b36-aece4a1ce2fd.343f8446-c6e1-4911-b890-c37a52fd47e9

6.1.3.3.19. 19. request#

The browser follows the redirect to the testapp, delivering the auth code.

http://10.205.2.110:5000/oidc_callback?
 state=eyJjc3JmX3Rva2VuIjogImliT19ncWVXRFdNYnFtTUxnNUczZmVjM3BjLXREYnRXIiwgImRlc3RpbmF0aW9uIjogImV5SmhiR2NpT2lKSVV6VXhNaUo5LkltaDBkSEE2THk4eE1DNHlNRFV1TWk0eE1UQTZOVEF3TUM5d2NtbDJZWFJsSWcuZkhPblgyZE4xN0k3WFlxUlViQThldFptcEFmS2U0eWpQaGxWckdERmZMQnFieUk4MmlYZ2ZCNGs3RkFCWVJtdFdaeTN1dmRFaHg1MVV0cWUzRWsxd0EifQ%3D%3D&
 session_state=e31ac91a-a628-425f-9b36-aece4a1ce2fd&
 code=bbd3bdc4-f5dd-486b-bcd7-e2b70823cadb.e31ac91a-a628-425f-9b36-aece4a1ce2fd.343f8446-c6e1-4911-b890-c37a52fd47e9

6.1.3.3.20. 20. request#

The testapp now wants to exchange the auth code for an access token and an id token. So the testapp queries Keycloak directly to do just that.

POST https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker/protocol/openid-connect/token

6.1.3.3.21. 21. response#

The testapp actually gets three tokens - the access token, the id token and also a refresh token, which allows it to refresh the access once the access token has expired.

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJuRHBjM2szTkFzZGc4YndJbGRZRnk5MjVQYWxBR2g5bDNnYjJxZUhhdnYwIn0.eyJleHAiOjE2NDM4MDE1ODQsImlhdCI6MTY0MzgwMTI4NCwiYXV0aF90aW1lIjoxNjQzNzk3MjkyLCJqdGkiOiIzNjk2OTJmYi0yNTA2LTQ0NDYtOTlhMC1hMzYxZGJkMWM2YTQiLCJpc3MiOiJodHRwczovL2xvZ2luLmtleWNsb2FrLmlkYnJva2VyLmludHJhbmV0L2F1dGgvcmVhbG1zL0lELUJyb2tlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmOmQ3ZTRjZTE0LWNmMDctNGUxZi04MDNjLWRlOTk2ZGQyNGRhNzo1YmY2NDRlNi0xNzhlLTEwM2MtODQ1MS00OWI0ZjU2YjFiYzAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJweXRob24tY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjEzNGVhZTgzLTk3MGEtNGQzYS04NGE1LWE4ZTMzMWJlN2QyMiIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtaWQtYnJva2VyIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiMTM0ZWFlODMtOTcwYS00ZDNhLTg0YTUtYThlMzMxYmU3ZDIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoic3R1ZGVudCBvbmUgb25lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiNWJmNjQ0ZTYtMTc4ZS0xMDNjLTg0NTEtNDliNGY1NmIxYmMwIiwiZ2l2ZW5fbmFtZSI6InN0dWRlbnQgb25lIiwiZmFtaWx5X25hbWUiOiJvbmUifQ.tip2fcB6it5EcMfUXRbEoZfvc9ofpBjbrGXQJHD6yXYyWnImGJsWBd8-6wfukxwIu433sVCIFy5k0bYkyqO9OHDs5RZJ4o6FTN8EEy4rN1Ne0r4WR_3kAoJPxiQMrC9K-6_1GgtsOQyAbQns74zVeBj2zis1JlcusSNyIDidQ6beqsVBQalfdgnzrhejvh3LnVjPOedNtrFurUdbZ-JIsWsv3uwo_OX6ZrtvUCvBzNEFT85gmq1uPqFTk4stW7jpVZSc5-UKMcFNjVsMCHTDRaTffIL2WiUVPWQh239M7Q4NSPN7JBDm_pMBtowOBgVEAda9YeXDgJUlrqc2cYjdZGX1GQ6dGtApeVCiCOv6OpmhFQmT4-fKKmpqatc_Aia5NinJ21PstJDRIXe4F8T43KYtfAwxjwAcMxGzyiuvdDphrbDDTNxBsUH7IFDUw1QCyuzUavus3r8vw0z9XOiNINq_-BuXnQrC0fkPLhqzshFnZGFJ8vJO24Fn2vhkaSh3CKctfusSEB1SRA9Kl2icT5BRZ_bNeEvyXk-hzmZ_NhCiVdOPfIw4i_Cfb8KRd6BW0Nk68BSmWdTV1AAFRF3x9UB5sEOZooLK4gYwkX0kEduZ-aGxK6t2Tz45XLCR_vsTonKe7o6F5D47b-VVhm4N8PQHQgq28Bd8VCZlyTqipUU",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3ZWEwMDYzNi0yZTBlLTQ0YzMtOGRhMS1kMGM1M2U5ZTg0MTAifQ.eyJleHAiOjE2NDM4MDMwODQsImlhdCI6MTY0MzgwMTI4NCwianRpIjoiMmZhNjdmZGUtNzUwNy00NDYzLTlhYzAtZGJmMDIzMTg3ZDkzIiwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5rZXljbG9hay5pZGJyb2tlci5pbnRyYW5ldC9hdXRoL3JlYWxtcy9JRC1Ccm9rZXIiLCJhdWQiOiJodHRwczovL2xvZ2luLmtleWNsb2FrLmlkYnJva2VyLmludHJhbmV0L2F1dGgvcmVhbG1zL0lELUJyb2tlciIsInN1YiI6ImY6ZDdlNGNlMTQtY2YwNy00ZTFmLTgwM2MtZGU5OTZkZDI0ZGE3OjViZjY0NGU2LTE3OGUtMTAzYy04NDUxLTQ5YjRmNTZiMWJjMCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJweXRob24tY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjEzNGVhZTgzLTk3MGEtNGQzYS04NGE1LWE4ZTMzMWJlN2QyMiIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiIxMzRlYWU4My05NzBhLTRkM2EtODRhNS1hOGUzMzFiZTdkMjIifQ.DkTZc2H_yqN8QJPV4YuUk1tqdbdpzCd56sCKZ7pDDQQ",
    "token_type": "Bearer",
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJuRHBjM2szTkFzZGc4YndJbGRZRnk5MjVQYWxBR2g5bDNnYjJxZUhhdnYwIn0.eyJleHAiOjE2NDM4MDE1ODQsImlhdCI6MTY0MzgwMTI4NCwiYXV0aF90aW1lIjoxNjQzNzk3MjkyLCJqdGkiOiJhODBiZjhkMy0zMjE5LTQ2MGQtYWU2Ny1lMzU0YzZlOWE4OGYiLCJpc3MiOiJodHRwczovL2xvZ2luLmtleWNsb2FrLmlkYnJva2VyLmludHJhbmV0L2F1dGgvcmVhbG1zL0lELUJyb2tlciIsImF1ZCI6InB5dGhvbi1jbGllbnQiLCJzdWIiOiJmOmQ3ZTRjZTE0LWNmMDctNGUxZi04MDNjLWRlOTk2ZGQyNGRhNzo1YmY2NDRlNi0xNzhlLTEwM2MtODQ1MS00OWI0ZjU2YjFiYzAiLCJ0eXAiOiJJRCIsImF6cCI6InB5dGhvbi1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiMTM0ZWFlODMtOTcwYS00ZDNhLTg0YTUtYThlMzMxYmU3ZDIyIiwiYXRfaGFzaCI6Ik5FWmZtU0YwcVBzWVA4LWhuZ1VodmciLCJhY3IiOiIwIiwic2lkIjoiMTM0ZWFlODMtOTcwYS00ZDNhLTg0YTUtYThlMzMxYmU3ZDIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoic3R1ZGVudCBvbmUgb25lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiNWJmNjQ0ZTYtMTc4ZS0xMDNjLTg0NTEtNDliNGY1NmIxYmMwIiwiZ2l2ZW5fbmFtZSI6InN0dWRlbnQgb25lIiwiZmFtaWx5X25hbWUiOiJvbmUifQ.gVaJTVNwWwNRh04aat9BV8ObWaVuGZtSlOMybLktzwERjrG8TBJ8wpk9rWzaym7vl34QJ30cLmCLdLgHHXN_rYpLiAkE6_L6sjfIwPqPHSCzMFCtohpnwdwHI-tA0ii3eR6ms4EqutXNVrZNluR3ApI9GW8owGEit4SQQstBm_qwYYMR5dfkAuIxh24g1U2NvGO5ZrtBU2o7zc0M26hgWQ6y9MAaMa-PYaVecK-u21d_MkWbccm2Kg-j2ErNDX4N_1l6WELdZjppzSnH-zYY-TV7FbhV_jgS2FPoVt3SDOhsH6CBjxm4J5t48Vowjlf0vk0V6lm_ys5batZ5Z1fvcd9-DcY8RjmHuvU_T5ocKrrK2dKcKNnjrnBZUiwQzoszjNY9t02HLmAI8mfL5nzyO-OM6jX9YDa0jy_6QkeInQXN4Dgh2YNd333F9lPrnJph5oul2V9-5lV6scD2hPRSwrnPVBAsVcW14WA2nttvYEQoJ-J637oXokP2XCqftcd5zYFV3mjzDEyuH0GMvkO8kpK6_4s2hh9KFDzeIWTRoFfSbLhq5b_rpSasGzEb3tfR20YlCTAQo1j06iBsAkBdebopn8TKU_eLFpExxc90pXOooNmznWGmZT36buIUQJIfiJh0C2WIWvU7au_gVKzU9cFuu54F2xUmQA97JEKsDCU",
    "not-before-policy": 0,
    "session_state": "134eae83-970a-4d3a-84a5-a8e331be7d22",
    "scope": "openid email profile"
}

6.1.3.3.22. Overview over some 3letter abbreviations#

Before we look at the details of the tokens, here are some of the abbreviations used in the tokens.

Table 6.1 Abbreviations used in tokens#

Field

example

name

details

acr

0

Authentication Context Class Reference

0: identified by session cookie, 1: fresh login with username & password, 2: fresh login with username & password & second factor

alg

RS256

algorithm used to sign the JWT token

aud

account

Audience

Audience(s) that this ID Token is intended for, e.g. Bettermarks. Can be more then one!

azp

python-client

Authorized party

The party to which the ID Token was issued. This Claim is only needed when the ID Token has a single audience value and that audience is different than the authorized party

exp

1643801311

Expiration time

secs since epoch

iat

1643801011

Issued At

secs since epoch

iss

https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker

Issuer

jti

975616e5-f847-43c8-a6a1-9e77d97a401c

JWT ID

can be used to prevent reuse of the token

kid

nDpc3k3NAsdg8bwIldYFy925PalAGh9l3gb2qeHavv0

Key Identifier

scope

openid email profile

Scope Values

sub

f:d7e4ce14-cf07-4e1f-803c-de996dd24da7:5bf644e6-178e-103c-8451-49b4f56b1bc0

Subject

A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client

typ

Bearer

Type

media type of this complete JWT

More details:

6.1.3.3.23. access token#

Details of the access token

{
    "header": {
        "alg": "RS256",
        "typ": "JWT",
        "kid": "nDpc3k3NAsdg8bwIldYFy925PalAGh9l3gb2qeHavv0"
    },
    "payload": {
        "exp": 1643801311,
        "iat": 1643801011,
        "auth_time": 1643797292,
        "jti": "975616e5-f847-43c8-a6a1-9e77d97a401c",
        "iss": "https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker",
        "aud": "account",
        "sub": "f:d7e4ce14-cf07-4e1f-803c-de996dd24da7:5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "typ": "Bearer",
        "azp": "python-client",
        "session_state": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "acr": "0",
        "allowed-origins": [
            ""
        ],
        "realm_access": {
            "roles": [
                "offline_access",
                "default-roles-id-broker",
                "uma_authorization"
            ]
        },
        "resource_access": {
            "account": {
                "roles": [
                    "manage-account",
                    "manage-account-links",
                    "view-profile"
                ]
            }
        },
        "scope": "openid email profile",
        "sid": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "email_verified": false,
        "name": "student one one",
        "preferred_username": "5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "given_name": "student one",
        "family_name": "one"
    },
    "signature": "MRg4+jDWPQyK5F0egZfNpD6IU5VJ20IDUTerywCpvJ4Uwr8/DhBZqsmSL8EO7As4ZBgP0gZjHvGkYOrx90MtCxdzF8ygNauIMV6/rhdzfynL3mJv12qza1iT3QcW3wNlxF3t/2oUBEfQ5Jtm2SJkAjjZ3Nm3EmtiqWboiVEykzPPrpdWeixBt9UldYb2nU/ME7XuCcFcQ98O7exB0dom4IMf+74O22ETCeBObIrIFeHfrSU1f2uZOLkwwFSKpa+wFeLln4K2GoO63F4RIdlWsOnSVj+fZtzRYDHzbXIiVErFj7ig4tAXLDQgfBp8nE1l0K3COOghnpiLhutocAU17yKaZuxiHeP7LOJxlG43SdGpo6S3AqeGIbd0coe9n3tNMQX2YU3yFpFA20F3zxv1dRiLtn4axY/hSsDcQAnS5KxlJRysVcr8JqjliVpZDgTAh5NNNff/CXyZDe6/pXRf3i6K+2nlPMxqpDs1hdKdDti8/Lyf+P9NFz7WlTkddueTA5NFECGzhCPXgZXDo6qn0I1ASs3+bnQNhD5zUEDTUYGq4OclJAQbwENiI3WxrunHAlIFiuqDSZPEtse4U8vLtAjV1Gp/BgRq7okF7aBAVGDIHqr09rmaSGgb8KTp4ooKw9SPY1ki4PBW2fQMs9PNTmNcmTknLO5E1h7rBF/ByaE="
}

6.1.3.3.24. ID Token#

Details of the id token.

{
    "header": {
        "alg": "RS256",
        "typ": "JWT",
        "kid": "nDpc3k3NAsdg8bwIldYFy925PalAGh9l3gb2qeHavv0"
    },
    "payload": {
        "exp": 1643801311,
        "iat": 1643801011,
        "auth_time": 1643797292,
        "jti": "b2be83a3-5064-4d72-8d34-bc4150976cdc",
        "iss": "https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker",
        "aud": "python-client",
        "sub": "f:d7e4ce14-cf07-4e1f-803c-de996dd24da7:5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "typ": "ID",
        "azp": "python-client",
        "session_state": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "at_hash": "lj9X-mi3DZFlQsxzwdFecw",
        "acr": "0",
        "sid": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "email_verified": false,
        "name": "student one one",
        "preferred_username": "5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "given_name": "student one",
        "family_name": "one"
    },
    "signature": "eHEx/LtqtraYg3mn1UYSWzaBIpIoz7g05svjqzvsxtvEsL+5PhSUPfNlmYreL5CfALU5ev9A0PMrMvLiapTMtwCVu2KxddLzBh6HeMSZgrdn0L+KPtIswVI0M3UvpTyeExRabfCchXwS+MOdm9FAZvcMf82WiU/A6UDeknfkcfXsPO/Smd6lDmBqoTCjJsv4FrPCquWlBsfTNWy9ZINqo0i0l9k6AOFzEObDDNkvJcJevYxPtuTDxa9RBQ49cRRoF7aAZuua7Gd5Q4Z60lJlYUlqIy4nGbnF2xp+E2JEqVNFdNCQRVTSJMx8oUAz95KLzyH+goHYMv5pSeLPpJ9Fyy6r/VY7rpeDn2hnkCiklqPPWgKlQzPNpo9z3D8jUJ3hGKakP7dqbPdeJy/Kqw0Zwh0IKhdFUnaoUmg3VmdqUPNDTsZfPfT8NMATL7HeDzuqrFH142Bw15WEuQWsZGguLSJcC4HmGvJtvmKv0WT6Un76M+gaJUvZnVE6+B1X5RnSBUQrzEfOIc0iX1a8hT8tMffCBe9XsYbPDIWA8bp1H5O5ibALQZxMxbuoSZKWN0J2rI3iFAwmJYdSA72VFdlv69oc3oZBBYF4teKo8PO2bv0zs4O6NL273c3Rs4PhZJ3ETALv8fBBMXHIr7SP5PbMgP5qtC7+c7SDr1vol1qNg7Y="
}

6.1.3.3.25. Refresh Token#

And the details of the refresh token.

{
    "header": {
        "alg": "RS256",
        "typ": "JWT",
        "kid": "nDpc3k3NAsdg8bwIldYFy925PalAGh9l3gb2qeHavv0"
    },
    "payload": {
        "exp": 1643801311,
        "iat": 1643801011,
        "auth_time": 1643797292,
        "jti": "b2be83a3-5064-4d72-8d34-bc4150976cdc",
        "iss": "https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker",
        "aud": "python-client",
        "sub": "f:d7e4ce14-cf07-4e1f-803c-de996dd24da7:5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "typ": "ID",
        "azp": "python-client",
        "session_state": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "at_hash": "lj9X-mi3DZFlQsxzwdFecw",
        "acr": "0",
        "sid": "134eae83-970a-4d3a-84a5-a8e331be7d22",
        "email_verified": false,
        "name": "student one one",
        "preferred_username": "5bf644e6-178e-103c-8451-49b4f56b1bc0",
        "given_name": "student one",
        "family_name": "one"
    },
    "signature": "eHEx/LtqtraYg3mn1UYSWzaBIpIoz7g05svjqzvsxtvEsL+5PhSUPfNlmYreL5CfALU5ev9A0PMrMvLiapTMtwCVu2KxddLzBh6HeMSZgrdn0L+KPtIswVI0M3UvpTyeExRabfCchXwS+MOdm9FAZvcMf82WiU/A6UDeknfkcfXsPO/Smd6lDmBqoTCjJsv4FrPCquWlBsfTNWy9ZINqo0i0l9k6AOFzEObDDNkvJcJevYxPtuTDxa9RBQ49cRRoF7aAZuua7Gd5Q4Z60lJlYUlqIy4nGbnF2xp+E2JEqVNFdNCQRVTSJMx8oUAz95KLzyH+goHYMv5pSeLPpJ9Fyy6r/VY7rpeDn2hnkCiklqPPWgKlQzPNpo9z3D8jUJ3hGKakP7dqbPdeJy/Kqw0Zwh0IKhdFUnaoUmg3VmdqUPNDTsZfPfT8NMATL7HeDzuqrFH142Bw15WEuQWsZGguLSJcC4HmGvJtvmKv0WT6Un76M+gaJUvZnVE6+B1X5RnSBUQrzEfOIc0iX1a8hT8tMffCBe9XsYbPDIWA8bp1H5O5ibALQZxMxbuoSZKWN0J2rI3iFAwmJYdSA72VFdlv69oc3oZBBYF4teKo8PO2bv0zs4O6NL273c3Rs4PhZJ3ETALv8fBBMXHIr7SP5PbMgP5qtC7+c7SDr1vol1qNg7Y="
}

6.1.3.3.26. 22. request student_details#

Now the testapp (a.k.a as client in OAuth2 terms) could do something magical with the access token, e.g. ask the resource server for student details. We haven’t documented this step, but one thing is required: the access token needs to be passed along with the request.

6.1.3.3.27. 23. response#

Before fetching data the resource server (e.g. the Self-disclosure API) would need to validate the signature of the access token.

import jwt

# see step 19
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJuRHBjM2szTkFzZGc4YndJbGRZRnk5MjVQYWxBR2g5bDNnYjJxZUhhdnYwIn0.eyJleHAiOjE2NDM4MDEzMTEsImlhdCI6MTY0MzgwMTAxMSwiYXV0aF90aW1lIjoxNjQzNzk3MjkyLCJqdGkiOiI5NzU2MTZlNS1mODQ3LTQzYzgtYTZhMS05ZTc3ZDk3YTQwMWMiLCJpc3MiOiJodHRwczovL2xvZ2luLmtleWNsb2FrLmlkYnJva2VyLmludHJhbmV0L2F1dGgvcmVhbG1zL0lELUJyb2tlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmOmQ3ZTRjZTE0LWNmMDctNGUxZi04MDNjLWRlOTk2ZGQyNGRhNzo1YmY2NDRlNi0xNzhlLTEwM2MtODQ1MS00OWI0ZjU2YjFiYzAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJweXRob24tY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjEzNGVhZTgzLTk3MGEtNGQzYS04NGE1LWE4ZTMzMWJlN2QyMiIsImFjciI6IjAiLCJhbGxvd2VkLW9yaWdpbnMiOlsiIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtaWQtYnJva2VyIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiMTM0ZWFlODMtOTcwYS00ZDNhLTg0YTUtYThlMzMxYmU3ZDIyIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoic3R1ZGVudCBvbmUgb25lIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiNWJmNjQ0ZTYtMTc4ZS0xMDNjLTg0NTEtNDliNGY1NmIxYmMwIiwiZ2l2ZW5fbmFtZSI6InN0dWRlbnQgb25lIiwiZmFtaWx5X25hbWUiOiJvbmUifQ.MRg4-jDWPQyK5F0egZfNpD6IU5VJ20IDUTerywCpvJ4Uwr8_DhBZqsmSL8EO7As4ZBgP0gZjHvGkYOrx90MtCxdzF8ygNauIMV6_rhdzfynL3mJv12qza1iT3QcW3wNlxF3t_2oUBEfQ5Jtm2SJkAjjZ3Nm3EmtiqWboiVEykzPPrpdWeixBt9UldYb2nU_ME7XuCcFcQ98O7exB0dom4IMf-74O22ETCeBObIrIFeHfrSU1f2uZOLkwwFSKpa-wFeLln4K2GoO63F4RIdlWsOnSVj-fZtzRYDHzbXIiVErFj7ig4tAXLDQgfBp8nE1l0K3COOghnpiLhutocAU17yKaZuxiHeP7LOJxlG43SdGpo6S3AqeGIbd0coe9n3tNMQX2YU3yFpFA20F3zxv1dRiLtn4axY_hSsDcQAnS5KxlJRysVcr8JqjliVpZDgTAh5NNNff_CXyZDe6_pXRf3i6K-2nlPMxqpDs1hdKdDti8_Lyf-P9NFz7WlTkddueTA5NFECGzhCPXgZXDo6qn0I1ASs3-bnQNhD5zUEDTUYGq4OclJAQbwENiI3WxrunHAlIFiuqDSZPEtse4U8vLtAjV1Gp_BgRq7okF7aBAVGDIHqr09rmaSGgb8KTp4ooKw9SPY1ki4PBW2fQMs9PNTmNcmTknLO5E1h7rBF_ByaE"

# This is the public key from Keycloak, from  https://login.keycloak.idbroker.intranet/auth/realms/ID-Broker
keycloak_public_key = "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6hjhC3946VIzORQ7SLRVznM7ei0CEDFSRnPy63PwzUeIspBHwUzGAAsuCt9JD+kI6Q+vWH8KzYH+cAg5A6QcPQjltJk35RvpMokiooUuXhe46cfaRvtlPLjx0llwsxbd0ZvKClUs+5TecchEWsT/BKmjZo2u+YCEumpDMg+2rarzHFyFLkXAOsR9kQkbvrDGnOJz5cpjn0m2lp4KJ/y4+oKAoer3YQxDWDwplPsCTv3wsPF/u5p8P5/QJElesxwkMQEsGGqii4yduBZ+O4a1NzF7l+WGeYtjtdeHkcgzJfjbrh7orP/7Eoqtb+5LT1alSRbH3ejstNjS+OrnTCS+jZ5+aQdGn3TO+sNDgw2FIIWG4USCteBiLgr+GK6X9YFv3wX+7lDGVjGul+DDjnfv66TBk1N8268vouE5/ln1wdd/OFEetEBTkETGcNyz8hvSBCAQ8BaqWe40gFlMKs9FcjfI4BTFjj92oaoZixlCn+vJ6anbA0vWS952RsQSEvxjK4N77PaoKfL2F8KUh7aUmhOchYQG+CG56dWgJ0DNpvKJlRjWtz1nFvOFJBFcxAhsgOBJSc9RGsY6rVW0/2GDIUeCkYNIjmSOBAnC8SPOA0rsEjT3rE37meWgiKfG57YUFA/8aYipXtiPufKBhvPBL+MAM+ZW8Rf2RioyVE7FoRcCAwEAAQ=="

# The key needs to have a header and a footer...
keycloak_with_headers = "-----BEGIN PUBLIC KEY-----\n" + keycloak_public_key + "\n-----END PUBLIC KEY-----"

# ... and can then be verified directly.
# Once can disable the expiration date verification, when debugging a session later on.
# Of course, you wouldn't do this in production.
verified_token = jwt.decode(
    access_token,
    key=keycloak_with_headers,
    audience='account',
    algorithms=['RS256'],
    # options=dict(verify_exp=False) # disable expiration checking
)

6.1.3.3.28. 24. follow redirect URL#

The user follows the redirect to the protected resource.

http://10.205.2.110:5000/private

6.1.3.3.29. 25. content#

Which she can now access because of the id_token in the content of /private.

6.1.4. Alternatives#

We have thought about alternatives, which we note here.

6.1.4.1. Auth code flow II - First SAML#

First SAML, then OIDC - doesn’t work:

  1. no login session with Keycloak (bomb in step 5). Could be worked around using a mini login app

  2. if the Keycloak session expires and somehow the service provider needs to restart the OIDC session, they don’t have the IDP information and hence can’t redirect automatically to the right SAML login server

_images/flows_saml_first.svg

6.1.4.2. Client credentials flow#

This flow would give the client application (service provider) complete access to all student data on the resource server. We hope that we won’t need it.