OIDC Authentication Solution Guide

This document is designed to help developers understand and utilize our OIDC (Open ID Connect) authentication solution. We'll cover APIs, the authorization flow, and token handling.

Overview

OpenID Connect is a simple identity layer on top of the OAuth 2.0 protocol. It allows clients to verify the identity of the end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user in an interoperable and REST-like manner.


APIs

1. /authorize API:

This is the starting point of the OIDC flow. Clients redirect end-users to this endpoint for authentication.

GET /oauth2/authorize?client_id=<your-client-id>&response_type=code&scope=openid%20profile%20email&redirect_uri=https%3A%2F%2F<your-redirect-uri>&state=<your-state>&nonce=<your-nonce> HTTP/1.1
Host: <Your_Issuer>

curl -v "https://<Your_Issuer>/oauth2/authorize?client_id=<your-client-id>&response_type=code&scope=openid%20profile%20email&redirect_uri=https%3A%2F%2F<your-redirect-uri>&state=<your-state>&nonce=<your-nonce>"

const clientId = '<your-client-id>';
const responseType = 'code';
const scope = 'openid profile email';
const redirectUri = encodeURIComponent('<your-redirect-uri>');
const state = '<your-state>';
const nonce = '<your-nonce>';
const authServer = '<Your_Issuer>';

const authorizeUrl = `${authServer}/oauth2/authorize?client_id=${clientId}&response_type=${responseType}&scope=${encodeURIComponent(scope)}&redirect_uri=${redirectUri}&state=${state}&nonce=${nonce}`;

window.location.href = authorizeUrl;

Parameters:

  • client_id (required): The client's ID.
  • redirect_uri (required): The URL to which the user will be redirected after authorization.
  • response_type (required): The type of the response. For OIDC, it should be code.
  • scope (required): The scopes for which the client is asking permission. For OIDC, openid is the required scope.
  • state (optional): An arbitrary string that will be returned in the response for CSRF protection.
  • nonce (optional): A string value that is used to associate a client session with an ID Token and mitigate replay attacks.

2. /token API:

This endpoint is used by the client to exchange the authorization code for Access and ID tokens.

POST /oauth2/token HTTP/1.1
Host: <Your_Issuer>
Content-Type: application/x-www-form-urlencoded

client_id=<your-client-id>&client_secret=<your-client-secret>&grant_type=authorization_code&code=<your-auth-code>&redirect_uri=<your-redirect-uri>

curl -X POST <Your_Issuer>/oauth2/token \
-d "client_id=<your-client-id>&client_secret=<your-client-secret>&grant_type=authorization_code&code=<your-auth-code>&redirect_uri=<your-redirect-uri>"

fetch('<Your_Issuer>/oauth2/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: `client_id=<your-client-id>&client_secret=<your-client-secret>&grant_type=authorization_code&code=<your-auth-code>&redirect_uri=<your-redirect-uri>`,
});

package main

import (
	"net/http"
	"net/url"
	"strings"
)

func main() {
    data := url.Values{}
    data.Set("client_id", "<your-client-id>")
    data.Set("client_secret", "<your-client-secret>")
    data.Set("grant_type", "authorization_code")
    data.Set("code", "<your-auth-code>")
    data.Set("redirect_uri", "<your-redirect-uri>")

    client := &http.Client{}
    req, _ := http.NewRequest("POST", "<Your_Issuer>/oauth2/token", strings.NewReader(data.Encode()))
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    resp, _ := client.Do(req)
    // handle resp to check if your request was successful
}

import requests

data = {
    'client_id': '<your-client-id>',
    'client_secret': '<your-client-secret>',
    'grant_type': 'authorization_code',
    'code': '<your-auth-code>',
    'redirect_uri': '<your-redirect-uri>'
}

response = requests.post('<Your_Issuer>/oauth2/token', data=data)

Parameters:

  • client_id (required): The client's ID.
  • client_secret (required): The client's secret.
  • grant_type (required): The type of the grant. For OIDC, it should be authorization_code.
  • code (required): The authorization code received from the /authorize API.
  • redirect_uri (required): The same redirect_uri that was used in the /authorize API.

3. /userinfo API:

This endpoint returns claims about the authenticated end-user.

GET /oauth2/userinfo HTTP/1.1
Host: <Your_Issuer>
Authorization: Bearer <your-access-token>

curl -H "Authorization: Bearer <your-access-token>" <Your_Issuer>/oauth2/userinfo

fetch('<Your_Issuer>/oauth2/userinfo', {
  headers: {
    Authorization: `Bearer <your-access-token>`,
  },
});

package main

import (
	"net/http"
)

func main() {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", "<Your_Issuer>/oauth2/userinfo", nil)
	req.Header.Add("Authorization", "Bearer <your-access-token>")

	resp, _ := client.Do(req)
	// handle resp to check if your request was successful
}

import requests

headers = {
    'Authorization': 'Bearer <your-access-token>',
}

response = requests.get('<Your_Issuer>/oauth2/userinfo', headers=headers)

Header:

  • Authorization (required): The Access token received from the /token API.

Authorization Code Flow

Here is a step-by-step guide on how the OIDC flow works:

  1. Authorization Request: The client redirects the end-user to the /authorize endpoint with the necessary parameters. The user will be asked to authenticate and authorize the client for the given scopes.

  2. Authorization Grant: If the end-user accepts, the server will redirect the user to the redirect_uri specified in the request, with an authorization code added as a query parameter.

  3. Authorization Code Flow: The client then makes a POST request to the /token endpoint, providing the authorization code, client ID, client secret, and redirect URI. The server verifies the code and returns the ID and Access tokens.

  4. ID Token Validation: The client validates the ID token and extracts the user's claims.

  5. Userinfo Request: Optionally, the client can call the /userinfo API to get more claims about the user.


Authorization Code Flow with PKCE (Proof Key for Code Exchange)

PKCE is a mechanism designed to prevent interception attacks during the authorization code exchange step, and is highly recommended for public clients that can't keep their client secrets confidential (like native or SPA applications).

Here is the detailed flow:

  1. Create a Code Verifier and Code Challenge: The client starts by creating a code verifier, which is a cryptographically random string using the characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~", with a minimum length of 43 characters and a maximum length of 128 characters. Then, a code challenge is derived from the code verifier by calculating the SHA256 hash and then base64url encoding it. If the client cannot perform these transformations, it can use the code verifier as the code challenge.

  2. Authorization Request: The client redirects the end-user to the /authorize endpoint with the necessary parameters, including the code_challenge and code_challenge_method parameters. The code_challenge_method should be S256 if transformations were applied to the code verifier, or plain otherwise.

  3. Authorization Grant: The server responds just like in the standard Authorization Code Flow.

  4. Authorization Code Flow: The client makes a POST request to the /token endpoint with the code_verifier parameter. The server will compare the transformed code_verifier with the code_challenge it previously received. If they match, the server will respond with ID and Access tokens.

  5. ID Token Validation: This is the same as the standard Authorization Code Flow.

  6. Userinfo Request: This is also the same as the standard Authorization Code Flow.


Client Credentials Flow

This flow is suitable for machine-to-machine authentication where a client application needs to access service APIs. There is no end-user involved in this flow.

  1. Token Request: The client makes a POST request to the /token endpoint with the client_id, client_secret, grant_type (which should be client_credentials), and scope parameters.

  2. Token Response: The server verifies the client's credentials. If valid, it will respond with an access token.

  3. API Request: The client can use the access token to make requests to the server's APIs. The access token is sent in the Authorization header: Authorization: Bearer {access_token}.

Please note that not all OpenID Connect servers support these additional flows. Always check the server's documentation before implementing these flows.


Getting Started

You can start coding your OIDC client by selecting a client library that supports OpenID Connect and your programming language. Each library will have its own way of handling these flows, but the underlying principles will remain the same.

For more detailed information, you can always refer to the OpenID Connect Specifications and the OAuth 2.0 Framework.

Starting Coding

For detailed implementation steps and code samples, refer to the specific client libraries for various programming languages that support OIDC. Common languages include Python, JavaScript, Java, and more.