Part Two: Security in React and WebApi in ASP.NET Core C# with authentification and authorization by KeyCloak
Part two: Securing a front-end React application
Version 1.0
Date 2022/05/29
By Nicolas Barlatier
If you missed Part One:
Part One: Installing Keycloak with Docker and Administration
The next part is available:
Part three: Securing the ASP.NET Core C# REST Web
The last Part Four:
Part Four: Calling the protected Web API from the React SPA with the access JWT Token Bearer Authorization
To use the front-end React application within Docker, you can read:
GitHub Repository with React and Web API projects
- React Version 18.1.0 with TypeScript
- Web API with ASP.NET Core 5.0
[GitHub — nicoclau/reactwebapiaspnetcorekeycloak: React and REST Web API protected by Keycloak with…
React and REST Web API protected by Keycloak with Authorization Code Flow and JWT Token — GitHub …github.com](https://github.com/nicoclau/reactwebapiaspnetcorekeycloak "github.com/nicoclau/reactwebapiaspnetcoreke..")
You have two commits:
The first commit contains:
- React SPA secured by the keycloak server
- Web API secured by the Access Token with the public key from the keycloak server used to validate it
The two applications don’t communicate yet, they are only secured.
The second commit contains:
- React SPA communicates with the Web API with the JWT Token and the CORS policy is handled
Introduction
The article will show you how to secure a front-end React single page application with authentication and authorization.
It will be divided in three main sections:
- Understanding the Flows of the OAuth 2.0 and OpenID Connect protocols and what Flow is the best for our SPA React Application
- Securing our SPA React Application by adding the Keycloak Javascript adapter in the React
- How to use properly the Keycloak Javascript adapter in our application with a custom KeyCloakService
The first step was to register a “client” in keycloak which represents our application to secure (see the first part to remember how : Part One: Installing Keycloak with Docker and Administration)
We can see below a detailed section about the notion of “client” in keycloak :
[Server Administration Guide
users Users are entities that are able to log into your system. They can have attributes associated with themselves…keycloak.org](https://www.keycloak.org/docs/latest/server_admin/index.html#assembly-managing-clients_server_administration_guide "keycloak.org/docs/latest/server_admin/index..")
All we need to remember is :
Clients are entities that can request authentication of a user.
When we created our client, we have to realise that Keycloak automatically associated the client with a flow by default which will be detailed later on.
Remember when we created the client the protocol OpendId Connect was selected.
OAuth 2.0 and OpendId Connect Flows
So we need understand better the different flows available with:
- OAuth 2.0
- OpenId Connect.
We need to get what are their purpose so we can choose the proper flow for securing our front-end React application and why it is the best choice !
OAuth Flows
Flows in the OAuth 2.0 protocol are actually called “grant types”
- Authorization Code : The Authorization Code grant type is used by Confidential Clients and Public Clients to exchange an authorization code for an access token. After the user returns to the client via the redirect URL, the application will get the authorization code from the URL and use it to request an access token.
- PKCE : is an extension to the Authorization Code flow to prevent CSRF and authorization code injection attacks.
- Implicit Flow with Form Post : intended for Public Clients, or applications which are unable to securely store Client Secrets.
- Client Credentials : used by clients to obtain an access token outside of the context of a user. This is typically used by clients to access resources about themselves rather than to access a user’s resources. ==> we will explain this better later on what that means.
- Device Code : used by browserless or input-constrained devices in the device flow to exchange a previously obtained device code for an access token.
- Refresh Token : used by clients to exchange a refresh token for an access token when the access token has expired. This allows clients to continue to have a valid access token without further interaction with the user.
Now if we remember well we actually use the procotol OpenId Connect which is based on OAuth 2.0.
So we need to see the OpenId Connect flows which are available.
OpenId Connect Flows
OpenId Connect defines four main flows that can be used to authenticate a user:
- Authorization Code Flow for browser-based applications like SPAs (Single Page Applications) or server-side application ==> we will use this option
- Implicit Flow for browser-based application, less secure than the previous one, not recommended and deprecated in OAuth 2.1;
- Client Credentials Grant for REST clients like web services, it involves storing a secret, so the client is supposed to be trustworthy;
- Resource Owner Password Credentials Grant for REST clients like interfaces to mainframes and other legacy systems which cannot support modern authentication protocols, it involves sharing credentials with another service, caution here.
Remember when we created our client we
- set the name : MyApp
- the Client Protocol : openid-connect so we must reason with the protocol Flows
- the root UR: locahost:3000
Creating a new client
We get the following page, we will highlight the 6 parts which are important to know.
Client Administration and Flows
The 6 main parts that keycloak automatically set up. We must master them so we know what is going on behind the scenes.
The parts will show you that keycloak by default:
- Part 1°) : set the client to Enabled : so the client can initiate or not a login and get back the Access Token
- Part 2°): set the Access Type to “public”, it is used for Front-end public clients which can’t safely store the secret to initiate a login.
For example our SPA react application is a public application because the javascript code is directly delivered to the browser of the user. We can hardly think it safe to store the secret in the js file!
What secret are we talking about here ? The secret is used in the Authorization Code Flow where the client exchanges the Autorization Code for the Access Token by sending this Authorization Code with the secret in the request. In our case, the secret will not used but we will use Valide Redirect URIs to make sure not any SPA reaches for the Keycloak server.
Access Type: public
If we changed the Access Type to “confidential” we would get the new following fields in red
New fields : Service Accounts, OIDC CIBA Grant, Authorization Enabled
Only Service Accounts is important: if enabled we actually use the “Client Credentials Grant”.
Service Accounts are not available to public clients as there is no secret available.
it allows us to use the “Client Credentials Grant”
So to understand the differences between the “Authorization Code Grant Type Flow” and the “Client Credentials Flow” see the diagrams:
Authorization Code Grant
The ressource owner accesses the application and we have 2 steps:
- step 1: Authentication and Grant Authorization which returns the Authorization Code to the application
- step 2: The application sends the Authorization Code to get the Access Token
- It is used when we have a Resource Owner (user)
Client Credentials Flow
Here we don’t have the Resource Owner, it is a direct communication between machines so we only have one step here : we send the client ID and secret and we directly get back the Access Token from the Authorization Server (Keycloak here)
This Client Credentials flow is for Apps that can request an access token and access resources on its own. These apps often use services that call APIs without users.
When we used “Confidential” client type and clicked on “save” we would get a new tab available called “Credentials” in our Client MyApp page
We get here the secret
So when we have on application which can save the clientid and secret safely on the server, we can use the “confidential” client and use the “client id” and “secret” in the Authorization Code Flow.
The secret is known only to the application and the authorization server. It is essential the application’s own password.
Basically when we have users needing authentication and authorization with a public application : we use the Authorization Code Flow with the login/password. This flow is enabled by the “Standard Flow” of the Part 3°).
- Part 3°): enabled the “Standard Flow” : it is the standard OpenID Connect authentication based on redirection with authorization code.
It is to enable the “Authorization Code Flow” which will be explained later.
It will be this Authorization Flow which will be used to secure our front-end React SPA. We will explain later on why.
Standard Flow : Enables the Authorization Flow with OpenID Connect protocol
- Part 4°): enabled the “Direct Access Grants” where the client (our application) can get and use the user login/pwd to get directly from the Keycloak the Access Token. In OAuth 2.0 it is like enabling “Resource Owner Password Credentials Grant”
We will see what happens when we disable this Flow after we finish this section.
- Part 5°): added our SPA url with the wildcard *
http://localhost:3000/* as Valide Redirect URIs
We will see that it is very important indeed to protect our public client.
The more precise are the links, the safer. Because public client can’t store the secret used in the Authorization Code flow, we need this protection to avoid another website to access the Access Token! If the url doesn’t match keycloak will returns the following error:
When an unauthorized website url tries to connect to the keycloak server
We can see in the following link complementary explanations:
[Keycloak Authentication Flows, SSO Protocols and Client Configuration
Keycloak In this article, I’m going to introduce the concept of authentication flows. Then, I’ll briefly mention the…thomasvitale.com](https://www.thomasvitale.com/keycloak-authentication-flow-sso-client/ "thomasvitale.com/keycloak-authentication-fl..")
Since the access to the client will be public, for security reasons, we must restrict it by setting the redirect URIs correctly. Keycloak automatically generates them from the Root URL, but we could add more of them. The more specific we are, the better.
- Part 6°): added the URI of our application.
This option handles Cross-Origin Resource Sharing (CORS). If browser JavaScript attempts an AJAX HTTP request to a server whose domain is different from the one that the JavaScript code came from, the request must use CORS. The server must handle CORS requests, otherwise the browser will not display or allow the request to be processed. This protocol protects against XSS, CSRF, and other JavaScript-based attacks.
So basically keycloak enabled for us the two following Flows:
- Authorization Code Flow without the use of the secret (public client)
We will use the Login and Password of the user. - Resource Owner Password Credentials Grant
Hopefully this section was not confusing, please let’s me know be email if you need further details.
Let’s see what happens if we disable the Resource Owner Password Credentials Grant.
We can see that the access token is directly issued once the login/pwd are validated by the Keycloak server.
Resource Owner Password Credentials Grant Diagram
Let’s open our Insomia API client application and send our request:
Resource Owner Password Credentials Grant
We sent in the request
We send :
- grant_type : Application grant types (or flows) are methods through which applications can gain Access Tokens
Below is the list of possible values:
We have to be aware that only highly-trusted applications can use the Resource Owner Password Flow.
Let’s disable on the server this flow:
Turn off Direct Access Grants
Disable and Save
If we try again we get the following error:
Client not allowed for direct access grants
The client MyApp is not allowed anymore to authenticate and authorize itselfs with this grant. It will have to use the other grant : Authorization Code grant.
We will use the Authorization Code Grant for securing our React SPA application
Securing a React SPA application with the OpenID Connect procotol
Basically what is a React SPA application?
It is a front-end application with HTML + CSS + Javascript with special modulation to make its development faster, easier, more efficient and more scalable.
So our code is Javascript on the client side.
Let’s look what is available on the Keycloak website to secure it with the OpenID Connect protocol.
[Securing Applications and Services Guide
Edit descriptionkeycloak.org](https://www.keycloak.org/docs/latest/securing_apps/index.html "keycloak.org/docs/latest/securing_apps/inde..")
Here we can find a complete guide about securing applications and services.
We will look at the Client-Side application so we click on
[Securing Applications and Services Guide
Edit descriptionkeycloak.org](https://www.keycloak.org/docs/latest/securing_apps/index.html#client-adapters "keycloak.org/docs/latest/securing_apps/inde..")
OpenID Connect Client-Side Javascript Adapter
[Securing Applications and Services Guide
Edit descriptionkeycloak.org](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter "keycloak.org/docs/latest/securing_apps/inde..")
Keycloak comes with a client-side JavaScript library that can be used to secure HTML5/JavaScript applications.
the library can be retrieved directly from the Keycloak server at
/js/keycloak.js
Let’s check:
We send a GET Http method request to :
http://localhost:8080/js/keycloak.js
Javascript Keycloak Adapter
it is a big js file of 2405 lines for our server keycloak of the lastest version 18.0.0 when I wrote this blog:
Cool now we know we can use this file to help us securing any front-end web application using javascript.
But how do we use it in our React application?
First we need one file:
- download the keycloak.json by following the instructions:
Click on the
Installation
tab selectKeycloak OIDC JSON
forFormat Option
then clickDownload
. The downloadedkeycloak.json
file should be hosted on your web server at the same location as your HTML pages.
The “Installation” tab helps us to get the configuration files.
We select Keycloak OIDC JSON
We download our configuration file
We add the keycloak.json at the root of the public directory of our React project.
At the end we get the following project structure:
Keycloak.json is in the public directory
Now we are ready to code!
We will use Visual Code but you are free to use any IDE:
Let’s open the terminal in Visual Code
Adding the Keycloak Javascript adapter in the React
We need to add the Javascript Keycloak adapter in our React project.
For this, we will use the npm.
A simple search on google gives:
https://www.npmjs.com/package/keycloak-js
Here we can find it, good news it also contains the built-in typescript declaration, very convenient!
[keycloak-js
Keycloak Adapter. Latest version: 18.0.0, last published: a month ago. Start using keycloak-js in your project by…npmjs.com](https://www.npmjs.com/package/keycloak-js "npmjs.com/package/keycloak-js")
Moreover, we can check it matches with the Keycloak Server version.
Let’s install it with the following command:
C:\DevRoot\myapp>npm i keycloak-js@18.0.0
npm WARN [@apideck/better-ajv-errors](twitter.com/apideck/better-ajv-errors "Twitter profile for @apideck/better-ajv-errors")@0.3.3 requires a peer of ajv@>=8 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.3.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.3.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
+ keycloak-js@18.0.0
added 3 packages from 3 contributors and audited 1434 packages in 13.281s
182 packages are looking for funding
run `npm fund` for details
found 1 high severity vulnerability
run `npm audit fix` to fix them, or `npm audit` for details
keycloak-js
Now we can import the keycloak js adapter features in our application.
For this, we will enjoy Typescript by creating a Service with its interface.
It will make it easier to use it anywhere and change its implementation.
First let’s create a folder that we can call “security” for our keycloak service, that service instance will make it easier to use it anywhere in our React project.
Let’s add the tsx file called “KeycloakService.tsx”
KeyCloakService
We need to know the Keycloak JS API, please go to the link
[keycloak-documentation/javascript-adapter.adoc at main · keycloak/keycloak-documentation
Contribute to keycloak/keycloak-documentation development by creating an account on GitHub.github.com](https://github.com/keycloak/keycloak-documentation/blob/main/securing_apps/topics/oidc/javascript-adapter.adoc "github.com/keycloak/keycloak-documentation/..")
We add the following code:
- First we import the Keycloak class from the keycloak js
- we create an instance keycloakInstance
- We create a function Login with the function to call if the user is authenticated. Here the function will display our main component of our React application.
To understand this part please consult the link
https://www.keycloak.org/docs/latest/securing_apps/index.html#javascript_adapter
Very important to remember, the documentation says at https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_implicit_flow
By default, the JavaScript adapter uses the Authorization Code flow.
So we made sure that the client side can communicate to the server side by using the Authorization Code flow, the Keycloak Javascript Adapter by default uses the Authorization Code flow that we enabled for the client “MyApp” on the Keycloak server configuration
We don’t have to do anything about the Flow in our code.
Now we are ready to make sure it works :) in our index.tsx file before our application is rendered we use
Securing the React App with Keycloak Service
We import our KeyCloakService and we create a function called renderApp.
This renderApp will be called only if the user is authenticated!
So before the React application can be rendered the keycloak service will trigger the CallLogin and we will see what happens.
Let’s run our React Application in VS Code.
Let’s open our package.json
Debug
We see a part called “Debug” with all the scripts we need.
We will use scripts/start
Click on “Debug”
Click on “start” to run react-scripts start
It will open the terminal and run
At the end the brower opens and the terminal returns:
And what do you see in the browser? :)
Redirection from our React App to the Keycloak login form
Our react application is secured by keycloak.
Let’s see the url
And you can see the realm “My Realm” and the client_id is MyApp we set up in our previous blog.
In the url we see the main parts:
- Keycloak js adapter calls the auth endpoint to authenticate the user http://localhost:8080/realms/MyRealm/protocol/openid-connect/auth : Endpoint to authenticate the user with a form
- protocol is openid-connect
- client_id is MyApp
- redirect_uri is our URI: http://locahost:3000
- response_type = code we say to the server we will use the Authorization Code grant
- scope=openid it is to say keycloak will return ID Token and Access Token. ID Token is the extension of the protocol OpenID Connect over the OAuth 2.0 protocol.
At the end of the flow we get in response of the ultimate request of the Authorization Code Flow POST: http://localhost:8080/realms/MyRealm/protocol/openid-connect/token where we request for the token(s) by exchanging with the Authorization Code.
ID Token and Access Token
We sent in the POST body:
- code: the value of Authorization Code
- grant_type: Authorization_Code Flow
- client_id: MyApp
- redirect_uri: to redirect to our application after the user is authenticated
Let’s see with Fiddler the complete dance
Step 1: User not Authenticated, redirection to Keycloak to log in
The important request/response here is on the line 13
Below the complete request with the expected query paramters already studied earlier
GET localhost:8080/realms/MyRealm/protocol/open.. HTTP/1.1
Host: localhost:8080
Connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: localhost:3000
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,es;q=0.8,fr;q=0.7,zh-CN;q=0.6,zh;q=0.5,nl;q=0.4
Cookie: AUTH_SESSION_ID=d1945748-e67b-448f-b204-a0b804da5740; AUTH_SESSION_ID_LEGACY=d1945748-e67b-448f-b204-a0b804da5740; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJjaWQiOiJNeUFwcCIsInB0eSI6Im9wZW5pZC1jb25uZWN0IiwicnVyaSI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC8iLCJhY3QiOiJBVVRIRU5USUNBVEUiLCJub3RlcyI6eyJzY29wZSI6Im9wZW5pZCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvTXlSZWFsbSIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwLyIsInN0YXRlIjoiMWUxZGVhZjItMmI0NC00ZWY5LTk1YTktMzcxYWNlNmVkNGNjIiwibm9uY2UiOiIyYjc3YjVlYy05MTNkLTQwMjEtODZkZi0zYTRmNzc5Mjg5MjgiLCJyZXNwb25zZV9tb2RlIjoiZnJhZ21lbnQifX0.0VclmSPhERNprcUZ9oAaknvuvW5UNFM9clJkO9fOZsA
We get in response the form, here a primary preview in the fiddler
In our brower we get:
Let’s log in and follow the dance with fiddler:
Step 2 and 3: Authorization Code and Tokens
Step 2: Let’s look at the line 26 where we logged and the endpoint authenticate is called. We sent in the POST http call the username and password
POST localhost:8080/realms/MyRealm/login-actions.. HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 45
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,es;q=0.8,fr;q=0.7,zh-CN;q=0.6,zh;q=0.5,nl;q=0.4
Cookie: AUTH_SESSION_ID=d1945748-e67b-448f-b204-a0b804da5740; AUTH_SESSION_ID_LEGACY=d1945748-e67b-448f-b204-a0b804da5740; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJjaWQiOiJNeUFwcCIsInB0eSI6Im9wZW5pZC1jb25uZWN0IiwicnVyaSI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC8iLCJhY3QiOiJBVVRIRU5USUNBVEUiLCJub3RlcyI6eyJzY29wZSI6Im9wZW5pZCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvTXlSZWFsbSIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwLyIsInN0YXRlIjoiYmUzMzJkYmUtZTQ5Mi00MTMzLWIzOTctNDJmYjM4NWEwZDlmIiwibm9uY2UiOiJjOGZjMWFlNi04NGViLTQzMzItOTFlMS05ZWJkZjIzNDQ3YzkiLCJyZXNwb25zZV9tb2RlIjoiZnJhZ21lbnQifX0.IwNPJ-OaVGtX-PgLxr1u849Q84LD4RPI87Bt4TzgcL0
username=myuser&password=myuser&credentialId=
We get in response once the login/password validated by keycloak
HTTP/1.1 302 Found
Referrer-Policy: no-referrer
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Robots-Tag: none
Cache-Control: no-store, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Security-Policy: frame-src 'self'; frame-ancestors 'self'; object-src 'none';
Set-Cookie: KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/MyRealm/; HttpOnly
Set-Cookie: KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/MyRealm/; HttpOnly
Set-Cookie: KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJleHAiOjE2NTM4NzkyODEsImlhdCI6MTY1Mzg0MzI4MSwianRpIjoiMTEyMjVmMjAtYWFjMC00NjJhLWFmODgtNjNiNDQ4ZWIxZTVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzdGF0ZV9jaGVja2VyIjoiZWJmbWZGbzFldHRnVU5ybDhjVjhKZjNsVko2SnQ0Tk9TbC1JWlg2YjRrcyJ9.tqPAjEiivavLO5I8JPl-62Sn66Dvvpd-95Ymsp7vHo0; Version=1; Path=/realms/MyRealm/; SameSite=None; Secure; HttpOnly
Set-Cookie: KEYCLOAK_IDENTITY_LEGACY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJleHAiOjE2NTM4NzkyODEsImlhdCI6MTY1Mzg0MzI4MSwianRpIjoiMTEyMjVmMjAtYWFjMC00NjJhLWFmODgtNjNiNDQ4ZWIxZTVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzdGF0ZV9jaGVja2VyIjoiZWJmbWZGbzFldHRnVU5ybDhjVjhKZjNsVko2SnQ0Tk9TbC1JWlg2YjRrcyJ9.tqPAjEiivavLO5I8JPl-62Sn66Dvvpd-95Ymsp7vHo0; Version=1; Path=/realms/MyRealm/; HttpOnly
Set-Cookie: KEYCLOAK_SESSION=MyRealm/8b5a7866-e968-4b20-9001-5ff5af75fdce/d1945748-e67b-448f-b204-a0b804da5740; Version=1; Expires=Mon, 30-May-2022 02:54:41 GMT; Max-Age=36000; Path=/realms/MyRealm/; SameSite=None; Secure
Set-Cookie: KEYCLOAK_SESSION_LEGACY=MyRealm/8b5a7866-e968-4b20-9001-5ff5af75fdce/d1945748-e67b-448f-b204-a0b804da5740; Version=1; Expires=Mon, 30-May-2022 02:54:41 GMT; Max-Age=36000; Path=/realms/MyRealm/
Set-Cookie: KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0; Path=/realms/MyRealm/; HttpOnly
P3P: CP="This is not a P3P policy!"
X-XSS-Protection: 1; mode=block
Location: localhost:3000/#state=be332dbe-e492-4133-b3..
content-length: 0
It returns the HTTP code 302 to redirect to http://localhost:3000/#state=be332dbe-e492-4133-b397-42fb385a0d9f&session_state=d1945748-e67b-448f-b204-a0b804da5740&code=618cefce-9582-4e5a-bd98-207b22e06a75.d1945748-e67b-448f-b204-a0b804da5740.c6015bba-a545-4114-841c-3a3425c001b2
We can see the authorization code in the url:
code=618cefce-9582–4e5a-bd98–207b22e06a75.d1945748-e67b-448f-b204-a0b804da5740.c6015bba-a545–4114–841c-3a3425c001b2
Step 3: Once we come back to our application the Keycloak JS keeps sending/receiving other request/responses till the line 45 where we get the final step 3 of the Authorization Code Flow: exchanging the Authorization Code to get back the Tokens in response if Keycloak could validate.
POST localhost:8080/realms/MyRealm/protocol/open.. HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 207
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
sec-ch-ua-platform: "Windows"
Content-type: application/x-www-form-urlencoded
Accept: */*
Origin: localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: localhost:3000
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,es;q=0.8,fr;q=0.7,zh-CN;q=0.6,zh;q=0.5,nl;q=0.4
Cookie: AUTH_SESSION_ID=d1945748-e67b-448f-b204-a0b804da5740; AUTH_SESSION_ID_LEGACY=d1945748-e67b-448f-b204-a0b804da5740; KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJleHAiOjE2NTM4NzkyODEsImlhdCI6MTY1Mzg0MzI4MSwianRpIjoiMTEyMjVmMjAtYWFjMC00NjJhLWFmODgtNjNiNDQ4ZWIxZTVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzdGF0ZV9jaGVja2VyIjoiZWJmbWZGbzFldHRnVU5ybDhjVjhKZjNsVko2SnQ0Tk9TbC1JWlg2YjRrcyJ9.tqPAjEiivavLO5I8JPl-62Sn66Dvvpd-95Ymsp7vHo0; KEYCLOAK_IDENTITY_LEGACY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJleHAiOjE2NTM4NzkyODEsImlhdCI6MTY1Mzg0MzI4MSwianRpIjoiMTEyMjVmMjAtYWFjMC00NjJhLWFmODgtNjNiNDQ4ZWIxZTVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJzdGF0ZV9jaGVja2VyIjoiZWJmbWZGbzFldHRnVU5ybDhjVjhKZjNsVko2SnQ0Tk9TbC1JWlg2YjRrcyJ9.tqPAjEiivavLO5I8JPl-62Sn66Dvvpd-95Ymsp7vHo0; KEYCLOAK_SESSION=MyRealm/8b5a7866-e968-4b20-9001-5ff5af75fdce/d1945748-e67b-448f-b204-a0b804da5740; KEYCLOAK_SESSION_LEGACY=MyRealm/8b5a7866-e968-4b20-9001-5ff5af75fdce/d1945748-e67b-448f-b204-a0b804da5740
code=618cefce-9582-4e5a-bd98-207b22e06a75.d1945748-e67b-448f-b204-a0b804da5740.c6015bba-a545-4114-841c-3a3425c001b2&grant_type=authorization_code&client_id=MyApp&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F
We call the endpoint token to exchange the Authorization code to the Tokens
We send in the POST HTTP request
- the Authorization code code=618cefce-9582–4e5a-bd98–207b22e06a75.d1945748-e67b-448f-b204-a0b804da5740.c6015bba-a545–4114–841c-3a3425c001b
- the grant_type =authorization_code
- the client_id=MyApp
- the redirect_uri our React Application URI from where we were redirected from before getting on the keycloak server
In response we get:
HTTP/1.1 200 OK
Referrer-Policy: no-referrer
X-Frame-Options: SAMEORIGIN
Access-Control-Expose-Headers: Access-Control-Allow-Methods
Strict-Transport-Security: max-age=31536000; includeSubDomains
Cache-Control: no-store
Access-Control-Allow-Origin: localhost:3000
Access-Control-Allow-Credentials: true
X-Content-Type-Options: nosniff
Pragma: no-cache
X-XSS-Protection: 1; mode=block
Content-Type: application/json
content-length: 3426
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJFMUk0RHpMWHUzUTRqMm80ZHdSRFBSOVBGUzd6bEw2MjdOaGtiSUl5WkQ0In0.eyJleHAiOjE2NTM4NDM1ODYsImlhdCI6MTY1Mzg0MzI4NiwiYXV0aF90aW1lIjoxNjUzODQzMjgxLCJqdGkiOiI4MjI4NjM1OC1kZmVhLTQwMGMtOGVkNy05Yzg3NjFkMWU1YmQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL015UmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiTXlBcHAiLCJub25jZSI6ImM4ZmMxYWU2LTg0ZWItNDMzMi05MWUxLTllYmRmMjM0NDdjOSIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1teXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6Im15dXNlciJ9.B2F_-pSTh6phS7TZJeS6-ZUgT31uEYQPbtV9oxjVdIKVeQWadWCyV-P2H0gY5oo7VWUhvtcqib_IK3fm41TxDxatlHkysFX5FVsvUoilkIeRp1vQDEhz745NddspVIZzH5u87TdFVuKH5Abv3Vr410sYAQeugzuCi28sbcgIsXzleX_LuUpOmaewz71j5zrkDGTz2qM9xo3D1g74ZuUegXsm_XKjDLqDEKHkJTa8YJvUBaf_iMfM-ZXI91Z41l9zP4rFuYqwFgLxvu-FdgN7jsW4rUGohsqyaNBJ_GCb0F8HYZRGCKuIkgZj-Zn6zkgbQetB7oMoB0ueEAtJd8aO2A","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTllYzkwNi1hYjNlLTQ4YWItOTM2OS0yZDdjYTZhM2QwOWQifQ.eyJleHAiOjE2NTM4NDUwODYsImlhdCI6MTY1Mzg0MzI4NiwianRpIjoiMDg1ZTJmZWQtZDgxZi00OTcxLWE0ZWEtODQxMmJiNWY4MDgzIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9NeVJlYWxtIiwic3ViIjoiOGI1YTc4NjYtZTk2OC00YjIwLTkwMDEtNWZmNWFmNzVmZGNlIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6Ik15QXBwIiwibm9uY2UiOiJjOGZjMWFlNi04NGViLTQzMzItOTFlMS05ZWJkZjIzNDQ3YzkiLCJzZXNzaW9uX3N0YXRlIjoiZDE5NDU3NDgtZTY3Yi00NDhmLWIyMDQtYTBiODA0ZGE1NzQwIiwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInNpZCI6ImQxOTQ1NzQ4LWU2N2ItNDQ4Zi1iMjA0LWEwYjgwNGRhNTc0MCJ9.i1WadD6KDRmAcJAPV2s8aYLd6VXR6YWO0l-a-TVGP9c","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJFMUk0RHpMWHUzUTRqMm80ZHdSRFBSOVBGUzd6bEw2MjdOaGtiSUl5WkQ0In0.eyJleHAiOjE2NTM4NDM1ODYsImlhdCI6MTY1Mzg0MzI4NiwiYXV0aF90aW1lIjoxNjUzODQzMjgxLCJqdGkiOiI0OTRkZDc4YS04YTY5LTQzYzItOWQ3OS05MWFkNzU1ZmU5NjkiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL015UmVhbG0iLCJhdWQiOiJNeUFwcCIsInN1YiI6IjhiNWE3ODY2LWU5NjgtNGIyMC05MDAxLTVmZjVhZjc1ZmRjZSIsInR5cCI6IklEIiwiYXpwIjoiTXlBcHAiLCJub25jZSI6ImM4ZmMxYWU2LTg0ZWItNDMzMi05MWUxLTllYmRmMjM0NDdjOSIsInNlc3Npb25fc3RhdGUiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJhdF9oYXNoIjoiemlUM0NqeDI4WEQtSjVjbWFVV1k1ZyIsImFjciI6IjEiLCJzaWQiOiJkMTk0NTc0OC1lNjdiLTQ0OGYtYjIwNC1hMGI4MDRkYTU3NDAiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6Im15dXNlciJ9.V6ZJKLECBOlKF0qisF19Y8xX9kTf7CZgHLz0efxmr63ZOSRsyZQwLBnoWH00E-pFIGHkdK5ugRGNU36N7xX1tzDvf6sOUIAAj7IUokaAxeYZOtjQL5ZTFgS1qZqEH7kUi27NaCc-z2mOrxF3nD6fSVqZcJ2jx-Wd5-nNvRCMpyEx0j5UtrB4aQP9XJddaJI4P19QjWicNTf1XBeProRKSoFsZ8W3wF0PCaCTV0hsF5rHyyvEP2csAAn1PZ7_Sv9TS0dI2_KjcxiiDCsMuBQnJI53vjAQkpm289dT20BUq6DXnJwLjeYsvO3EGAmpieqUIRTGx6JZ6WvZ-XVQp5_ctA","not-before-policy":0,"session_state":"d1945748-e67b-448f-b204-a0b804da5740","scope":"openid profile email"}
We can see the ID and Access tokens
Below a visual view :
We can also see the expiration of the tokens.
So with fiddler we could make sure that the Authorization Code flow is used.
At the end we can go to our React App page !
Reaching our React App after authenticating our User with Keycloak
Getting User Information on our React Application, Logging out and SSO
In the final part of the part 2 we will show you how to display:
- the user name
- the user role(s)
on our React App.
We will show you how to log out from our React App.
Finally we will prove you that SSO works by logging out outside our React App.
On our welcome page we will :
- add the user name and role(s)
- add a link to log out
Remember in our Keycloak Service we added only one method: CallLogin
We will add a new method GetUserName to get our User Login:
Now let’s use it in our welcome page within the App component:
We get the following message
Let’s see the Role(s) now
Let’s use it
We get the result
So we see how easy it is to get all the users information.
The roles are returned by the Access Token which is parsed and returned by the Keycloak js adapter.
Finally we will add a button to log out the user, but fist we need to add the method in our Keycloak Service:
Let’s add the button to call the logout:
We get the screen:
When we click on Log Out we return to
Finally let’s prove that we use a SSO (Single Sign On).
We log in again by clicking on “Sign In”
We go back to
Let’s go to the Keycloak Server:
We can see for our application MyApp the number of Sessions, therefore the number of Users logged in.
We can click on the MyApp
We can see all the sessions by clicking on “Show Sessions”
If we click on “Logout All” on the Sessions, we will make the user logged out.
All the sessions are removed.
When we go back to our App, the user is not logged anymore.
Now let’s do the reverse we want to logged in our user but by using KeyCloak:
Let’s go to http://localhost:8080/realms/MyRealm/account/
We get the page:
Let’s click on “Sign in”
We log in and we see
Now let’s see what happens when go to our App:
Our user is already logged-in and because the two applications : account (http://localhost:8080/realms/MyRealm/account/) and MyApp are in the same Realm MyRealm, the user can use the two applications with one Single Sign On!
The next part is available:
Part three: Securing the ASP.NET Core C# REST Web
References
[OpenId Connect et OAuth : comment choisir son flow de connexion ?
Dans des précédents articles, nous avons abordé comment sécuriser une API avec OpenIddict. Dans cet article, je vais…blogs.infinitesquare.com](https://blogs.infinitesquare.com/posts/web/open-id-connect-et-oauth-les-differents-flow-de-connexion "blogs.infinitesquare.com/posts/web/open-id-..")
[Single-Page Apps - OAuth 2.0 Simplified
Single-page apps (also known as browser-based apps) run entirely in the browser after loading the JavaScript and HTML…oauth.com](https://www.oauth.com/oauth2-servers/single-page-apps "oauth.com/oauth2-servers/single-page-apps")
[Server Administration Guide
users Users are entities that are able to log into your system. They can have attributes associated with themselves…keycloak.org](https://www.keycloak.org/docs/latest/server_admin/index.html#_authentication-flows "keycloak.org/docs/latest/server_admin/index..")
[OAuth 2.0 Grant Types
This topic explains how OAuth 2.0 grant types work with different app types. The authorization code grant type is the…docs.vmware.com](https://docs.vmware.com/en/Single-Sign-On-for-VMware-Tanzu-Application-Service/1.14/sso/GUID-grant-types.html "docs.vmware.com/en/Single-Sign-On-for-VMwar..")
[Application Grant Types
Application grant types (or flows) are methods through which applications can gain Access Tokens and by which you grant…auth0.com](https://auth0.com/docs/get-started/applications/application-grant-types#available-grant-types "auth0.com/docs/get-started/applications/app..")
[Resource Owner Password Flow
Though we do not recommend it, highly-trusted applications can use the Resource Owner Password Flow (defined in OAuth…auth0.com](https://auth0.com/docs/get-started/authentication-and-authorization-flow/resource-owner-password-flow "auth0.com/docs/get-started/authentication-a..")
[keycloak-documentation/javascript-adapter.adoc at main · keycloak/keycloak-documentation
Contribute to keycloak/keycloak-documentation development by creating an account on GitHub.github.com](https://github.com/keycloak/keycloak-documentation/blob/main/securing_apps/topics/oidc/javascript-adapter.adoc "github.com/keycloak/keycloak-documentation/..")