Web Security: OAuth2 and OIDC

In the previous posts, you saw how HTTPs is useful to protect a two way communication between two parties, and how Hashing, Encryption, Signatures and Digital certificates are essential part of this process.

However, HTTPs is not enough to secure an application, we need to authenticate and authorize the party, to show him the data he is worth to see.

In this post, you will see how security protocols like OAuth2 and OIDC helps to delegate security and how to use Spring Security to apply those protocols.

TRY IT YOURSELF: You can find the source code of this post here.

Web Security Series

Authentication and Authorization

Authentication and authorization are two security features we must implement in any application. They focus on: who are you and what can you do?. We talked about this extensibility in the post Web Security: Authentication and Authorization and we implemented a basic authentication and authorization using Spring Security.

However, securing an application regarding authentication and authorization has some challenges:

  • Everyday, we found new attackers trying to break known protocols and systems.
  • The hardware is faster and could break some cryptography strategies which we rely on.
  • Saving passwords is not an easy task.
  • Adding new security layers like Multi-factor Authentication is not easy either.

Those challenges are not easy to address, but, as security is traversal to any application, those problems are already solved.

As they are known problems, we have known solutions, and there are plenty of protocols and external applications that address those concerns by us.

In the following sections, you will see the OAuth2 framework, which focuses on solving the authentication problem, relying on a third party how knows better how to address the security challenges.

OAuth2 Framework

OAuth2 is a security framework, which defines several workflows of interaction, between a client who wants to authenticate a user to access a resource, and Authorization Server, who knows how to authenticate users.

Let’s use the John and Mary relationship to understand the general workflow: Mary wants to give access to John to her apartment, however, she doesn’t want him to have a key (she doesn’t trust him yet), therefore, Mary wants to rely on a third party to give John a temporal key, in this case, she trusts the building watchman. The following diagram shows the general process Mary wants to use:

Process for John to get into Mary’s apartment

Now, this approach has the following advantages:

  • Mary doesn’t need to give a permanent key to John
  • The watchman can validate John over more items (multi factor authentication) like:
    • John has the valid password?
    • John looks like John?
    • John is trying to get into the apartment with untrusted people?
    • And so on
  • Also, the watchman has user management features like register, update, delete, and so on, then, Mary won’t need to do that, in the case she wants to give access to more people.

Of course, it has some disadvantages:

  • If the watchman is compromised, anyone could enter the apartment
  • John might get mad asking each time for a new temporal key

As you see, relying on a third party to handle the security, could be a good idea.

In the following sections, we will talk about the roles and workflows OAuth2 defines.

OAuth2 Roles

The OAuth2 framework defines some roles that interact in the whole authorization process. Those roles can be mapped to John and Mary relationship:

In the example, the Resource Owner (John) uses a Client (phone) to request access to a Resource Server (Mary’s apartment) through the Authorization Server (watchman). The Authorization Server (watchman) generates a token (key) that the Resource Owner (John) can use to access the Resource Server (Mary’s apartment).

The token is not a role per se, is a temporal key, it can expire, be revoked, hold Resource Owner information and so on.

The interaction between John and the Watchmen flows a workflow defined by OAuth2, we will focus on the Authorization Code workflow. Let’s talk about it.

OAuth2 Authorization Code Workflow

OAuth2 defines three main workflows of interaction: Authorization Code, Implicit, Resource Owner Password Credentials and Client Credentials.

In this post, we will focus on the Authorization Code workflow.

Let’s see the Watchman and John’s interaction using the Authorization Code workflow:

Code workflow

Now, the following are the steps perform by the authorization code workflow:

  1. John (Resource Owner) asks through the phone (Client) to enter Mary’s apartment (Resource Server).
  2. The phone (Client) asks the Watchman (Authorization Server) to authorize it through a unique client_id. A client_id and a client_secret are generated by the Watchman, as he must register the client in the set of valid clients.
  3. The Watchman (Authorization Server) choose how to authenticate John (Resource Owner). In this case, the authentication will be through a login page.
  4. John (Resource Owner) fills the login form.
  5. The Watchman (Authorization Server) validates John’s credentials.
  6. If the validation was right, The Watchman (Authorization Server) responses with a code.
  7. The phone (Client) uses this code, plus its client_id and client_secret, to call the token endpoint of The Watchman (Authorization Server).
  8. The Watchman (Authorization Server) responses with a key. This key can expire or be revoked by the Watchman (Authorization Server).
  9. And John (Resource Owner) is authenticated.

NOTE: This flow showed the use of the two main endpoints/methods the Authorization Server must offer: authorize and token.

Now, as John has a valid temporal key, he can access Mary’s apartment as follows:

Accessing the Resource Server

The following are the steps perform to access Mary’s apartment:

  1. Mary’s apartment (Resource Server) asks The Watchman (Authorization Server) how to validate a key generated by the Watchman. They agree how to validate a temporal key generated by the Watchman (Authorization Server)
  2. The Watchman (Authorization Server) responds with validators (for instance, a lock).
  3. John (Resource Owner) asks through the phone (Client) to access May’s apartment (Resource Server).
  4. The phone (Client) uses the key to access May’s apartment (Resource Server).
  5. May’s apartment (Resource Server) validates that the key fits the lock, if so, John can enter May’s apartment (Resource Server).

As you see, the token endpoint is who gave John a valid key/token. Let’s discuss the structure of that response.

Token Response Example

The following is an example of a token response:

    {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"Barear",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
    }

There, you see the following attributes:

  • access_token: This is the token used to access Resources Server, for instance, the key for Mary’s apartment.
  • token_type: This is the token type, usually Barear or JWT. For instance, the token type in Mary’s apartment is a key.
  • expires_in: The time in seconds when the token will expire. For instance, the key that the Watchman generated, only can be used during one hour.
  • refresh_token: This token is used to request other access_token. (We won’t go deep in this topic)

Now, OAuth2 is a pretty good framework, however, there are missing parts. For those missing parts, OpendID Connect was created. Let’s talk about it.

OpenID Connect (OIDC)

OpenID Connect (OIDC) is an extension of OAuth2. Mainly, OIDC aggregates the following features to OAuth2:

  • JSON Web Tokens (JWT)
  • ID token
  • JSON Web Keys (JWKs)
  • Discovery Endpoint

Let’s discuss them.

Using JSON Web Tokens (JWT)

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

They have the following features:

  • Claims: A claim is a piece of data. You can add any claims you want to the JWT
  • Digitally signed: The JSON is digitally signed and can be verified.
  • Encrypted: The JSON could be encrypted to protect confidential data.
  • Human readable: As the format is JSON, a human can read it without problem

A JWT is composed by three elements:

Header, by default it has the token type and the signature algorithm, for instance:

{
  "alg": "HS256",
  "typ": "JWT"
}

The header should be encoded as base64 to be used later.

Payload, has claims, pieces of data of the user, for instance:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Also, you should encode the payload as base64.

And finally, a Signature, which is generated using the algorithm specified in the header, a secret, and the encoded header and encoded payload, plus a dot in the middle. The signature is also encoded to base64.

The final result should look like:

encodedHeader.encodedPayload.encodedSignature

Now, let’s talk about the ID Token.

ID Token

An ID token contains security information regarding the Resource Owner, using Claims. This token is in JWT format, and it defines a set of standard claims like name, picture, email and so on. You can find them all here.

OIDC aggregates to the token endpoint from OAuth2 a new attribute, named id_token:

 {
   "access_token": "SlAV32hkKG",
   "token_type": "Bearer",
   "refresh_token": "8xLOxBtZp8",
   "expires_in": 3600,
   "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
     yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
     NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
     fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
     AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
     Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
     NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
     QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
     K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
     XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
  }

NOTE: Usually, the access_token is also a JWT

You can see the content of the JWT here.

As the JWT is signed, we need a way to know which public keys we must use to verify the signature, for that, we have JSON Web Keys.

JSON Web Keys (JWK)

As the JWT is signed, we need to validate the signature, and for that, we need the public key of the issuer (the Authorization Server).

JSON Web Keys is a standard format to deliver signature and encryption keys. The following is an example:

  {
    "kty" : "RSA",
    "kid" : "1438289820780",
    "use" : "sig",
    "alg" : "RS256",
    "n" : "idWPro_QiA.........",
    "e" : "AQAB"
  }

There, you see the key type (kty), the algorithm used (alg), the public key (n) and the use (sig), in this case, this key was used to sign.

You can access the JWKs using https://server.example.com/jwks.json in your Authorization Server.

As OIDC adds more information to the Authorization Server like jwks endpoint or sign algorithms, is important to have a way to discover that information. Let’s talk about the discovery endpoint.

Discovery Endpoint

The main goal of the discovery endpoint is allow an application to find OIDC Authorization Servers, and its configuration.

The discovery endpoint looks like https://server.example.com/.well-known/openid-configuration. The response of this endpoint wil have:

 {
   "issuer":
     "https://server.example.com",
   "authorization_endpoint":
     "https://server.example.com/connect/authorize",
   "token_endpoint":
     "https://server.example.com/connect/token",
   "token_endpoint_auth_methods_supported":
     ["client_secret_basic", "private_key_jwt"],
   "token_endpoint_auth_signing_alg_values_supported":
     ["RS256", "ES256"],
   "userinfo_endpoint":
     "https://server.example.com/connect/userinfo",
   "check_session_iframe":
     "https://server.example.com/connect/check_session",
   "end_session_endpoint":
     "https://server.example.com/connect/end_session",
   "jwks_uri":
     "https://server.example.com/jwks.json",
   "registration_endpoint":
     "https://server.example.com/connect/register",
   "scopes_supported":
     ["openid", "profile", "email", "address",
      "phone", "offline_access"],
   "response_types_supported":
     ["code", "code id_token", "id_token", "token id_token"],
   "acr_values_supported":
     ["urn:mace:incommon:iap:silver",
      "urn:mace:incommon:iap:bronze"],
   "subject_types_supported":
     ["public", "pairwise"],
   "userinfo_signing_alg_values_supported":
     ["RS256", "ES256", "HS256"],
   "userinfo_encryption_alg_values_supported":
     ["RSA1_5", "A128KW"],
   "userinfo_encryption_enc_values_supported":
     ["A128CBC-HS256", "A128GCM"],
   "id_token_signing_alg_values_supported":
     ["RS256", "ES256", "HS256"],
   "id_token_encryption_alg_values_supported":
     ["RSA1_5", "A128KW"],
   "id_token_encryption_enc_values_supported":
     ["A128CBC-HS256", "A128GCM"],
   "request_object_signing_alg_values_supported":
     ["none", "RS256", "ES256"],
   "display_values_supported":
     ["page", "popup"],
   "claim_types_supported":
     ["normal", "distributed"],
   "claims_supported":
     ["sub", "iss", "auth_time", "acr",
      "name", "given_name", "family_name", "nickname",
      "profile", "picture", "website",
      "email", "email_verified", "locale", "zoneinfo",
      "http://example.info/claims/groups"],
   "claims_parameter_supported":
     true,
   "service_documentation":
     "http://server.example.com/connect/service_documentation.html",
   "ui_locales_supported":
     ["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
  }

There, you can find all the necessary information to use the OIDC Authorization Server, from authorize, token and jwks endpoints, to signing and encryption options.

After understanding how OAuth2 and OIDC works, let’s use Spring Security to simulate John and Mary’s relationship.

Spring Security Example

Now, let’s use Spring Security to simulate the John and Mary’s apartment relationship. The following image shows how the authorization flow works:

Authorization code flow with Spring

In this case, you see the following changes:

  • Resource Owner: It is John, in this case, using a web browser.
  • Client: REST Spring Boot application. It will execute the authorization code flow to the Authorization Server to get a JWT.
  • Authorization Server: We will use Google as our authorization server. Google supports OIDC and we can use its users database.
  • Resource Server: REST Spring Boot application. It is a RESTFul API that requires a valid JWT issued by the Authorization Server to access the information (Mary’s apartment).

Before seeing the flow working, we should setup a Client in the Authorization Server (Google).

Authorization Server: Google Configuration

We are going to use Google as Authorization Server, which means, Google will manage the users, its data, including passwords and so on.

The first thing we should do is create a OIDC client configuration in Google. For that, follow the below steps:

  1. Create an account here https://console.developers.google.com/
  2. Move to Credentials | Create Credentials
  3. Choose ID OAuth Client | Web Application
  4. Type a name.
  5. Add a new Authorized JavaScript Origin with http://localhost:8080
  6. Add a new Authorized Redirect URI with http://localhost:8080/login/oauth2/code/google
  7. After, you will see the client_id and client_secret. Save them in a text file.

The redirect URI is used by the Authorization Server (Google) to redirect after the authentication is succeed, and it redirects with the authorization code. The format of the redirect URI is standard for Spring Security.

Now, let’s setup the Resource Server (Mary’s Apartment)

Resource Server (Mary’s Apartment)

NOTE: You can find the source code here.

The Resource Server represents Mary’s apartment. This is a Spring Boot application and we need to add the following dependencies:

implementation 
   'org.springframework.boot:spring-boot-starter-web'
implementation 
   'org.springframework.boot:spring-boot-starter-security'
implementation 
   'org.springframework.boot:spring-boot-starter-oauth2-resource-server'

There, we have the starter-web for RESTful API, starter-security for the Security Context and the starter-oauth2-resource-server for the OIDC resource server configuration.

Now, we tell Spring Security which Authorization Server to use in the application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com

There, we just set the issuer-uri and Spring Boot will use the discovery endpoint to get all the information it needs to connect to Google.

Now, we define an RESTful API to access the application:

@RestController
public class ResourceController {

    @GetMapping("enter")
    public String enter(
             @AuthenticationPrincipal Jwt principal) {
        return "Authorized: " 
                  + principal.getClaimAsString("name");
    }
}

We just defined an endpoint /enter to access Mary’s apartment, grabbing the JWT used to authorize and returning the user name.

If you start the Resource Server and try to access http://localhost:8081/enter, you will see the following:

Trying to access the Resource Server without a valid JWT

You need a valid JWT, in the authorization HTTP header. In the following section, we will grab a valid JWT from Google.

NOTE: Remember the agreement between the Watchman and Mary about how to lock the apartment? in this case, Google and our Spring Boot application agree to use JWKs to validate the token, that means, the Spring Boot application will use the discovery endpoint to grab the JWKs URI, and then, use it to grab the JWKs. Those keys are cached in memory and used to validate any JWT included in incoming request.

Client Server (Phone)

NOTE: You can find the source code here.

The Client Server will do the Authorization Code workflow to grab a JWT from the Authorization Server (Google). This is a Spring Boot project, and we should add the following gradle dependencies:

implementation 
   'org.springframework.boot:spring-boot-starter-web'
implementation 
   'org.springframework.boot:spring-boot-starter-security'
implementation 
   'org.springframework.boot:spring-boot-starter-oauth2-client'

There, we have the starter-web for RESTful API, starter-security for the Security Context and the starter-oauth2-client for the OIDC client configuration.

Now, we tell Spring Security which Authorization Server to use in the application.yml:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: xxxxxx
            client-secret: xxxxxx

We set the client-id and client-secret from the Google configuration. By default, Spring Security has frequently used Authorization Servers configurations, like Google or Github, so, we don’t need to set any URL or configuration, Spring Security automatically uses the discovery endpoint to grab the Google configuration.

If you use a different Authorization Server like Linkedin, you should define the configuration manually, for instance:

spring:
  security:
    oauth2:
      client:
        registration:
          linkedin:
            client-id: xxx
            client-secret: xxx
            client-authentication-method: post
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            scope:
              - r_liteprofile
              - r_emailaddress
              - w_member_social
        provider:
          linkedin:
            authorization-uri: https://www.linkedin.com/oauth/v2/authorization
            token-uri: https://www.linkedin.com/oauth/v2/accessToken
            user-info-uri: https://api.linkedin.com/v2/me
            user-name-attribute: id

Now, we define a RESTFul controller to access the Client Server as follows:

@RestController
public class ClientController {
    private final RestTemplate restTemplate;

    public ClientController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("ping")
    public String ping() {
        return "Pong!!";
    }

    @GetMapping("/id-token")
    public String getIDToken(
            @AuthenticationPrincipal OidcUser principal) {
        return principal.getIdToken().getTokenValue();
    }

    @GetMapping("go-in-mary-apartment")
    public String goInMaryApartment(
            @AuthenticationPrincipal OidcUser principal) {
        HttpHeaders headers = new HttpHeaders();

        headers.setBearerAuth(principal.getIdToken().getTokenValue());

        HttpEntity entity = new HttpEntity(null, headers);

        ResponseEntity responseEntity = restTemplate.exchange(
                "http://localhost:8081/enter",
                HttpMethod.GET,
                entity,
                String.class);

        return responseEntity.getBody();
    }
}

We have the following endpoints:

  • /ping: This is just a test endpoint that returns Pong. However, when you execute this endpoint, Spring Security will ask you to login using your Google account, as follows:
Authorization Code with Google

After you login succeed, the endpoint will respond pong.

  • /id-token: This shows the ID Token issued by Google.
ID token

You can see the JWT content using https://jwt.io/ to decode the ID Token, you might see something like this:

ID Token decoded
  • /go-in-mary-apartment: This endpoint will use the ID Token to call another Spring Boot application (Resource Server). The following is the output of that call:
The response from the Resource Server

As the Client sent the JWT to the Resource Server, and that JWT is valid, the Resource Server authorized the access and respond with the JWT user name.

As we have a valid ID Token, you can use it to call directly the Resource Server (Mary’ Apartment) using postman:

Calling the Resource server directly

You see the following response:

Now, we have a Spring Boot application secured by OIDC.

Final Thought

Applying security to an application is hard, we saw:

  • Authentication and Authorization
  • Hashing – Digest
  • Symmetric and Asymmetric Cryptography
  • Digital Signatures
  • Digital Certificates and HTTPs
  • OAuth2 and Opend ID Connect Protocols

All of them, together, add security layers to an application, but, they might not be enough, so, don’t underestimate the security feature you need to use in you application.

At the end, is better to delegate that responsibility to a third party, as we did using Google and Spring Security.

Finally, Spring Security is a robust framework who helps us to handle complex protocols like OAuth2 and OIDC, use it carefully.

Remember, securing an application is not an easy task, so, don’t underestimate it.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

5 comments

Leave a Reply to Web Security: Digital Certificates and HTTPs – The Coders Tower Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s