OAuth2 & OIDC: How Login Actually Works
5 min readI was actually working on some IDP related stuff, so i was trying to do some POC and then i thought of writing this down.
Everyone would have been aware of “Login with Google/okta” etc, this writing isnt going to be explaining what really an IDP or Oauth2 is from a theoretical perspective, rather i will focus on explaining how typically things work between different services when IDPs or Oauth are involved
Some basics of OAuth2#
oauth2 is basically a set of rules should be followed in order to acheive this oauth2, it has some defined protocol/steps that lets any application to use the source application resources without needing to have separate accounts.
Some of the common terms of oauth2
- Resource Owner: actual person, the user who is logging in.
- Authorization Server: This is the identity provider, keycloak, google, okta etc…u can also build ur own.
- Client: The application that asks the source application(which already has IDP)
- Resource Server: Mostly the source application, who owns some of the protected APIs. eg: after google login, if the client asks google to get details of its drive, gmail etc.. then google is the resource server for client.
As said above oauth2 itself is just a spec/protocol.. things like keycloak, okta, auth0 etc are just implementations of this spec, OIDC(OpenID connect) adds one more layer which is basically the authentication part.
Now lets look at a real example,
Two companies, one login button#
Consider we have two companies companya and companyb, companya also uses companyb internally for some things
companya manages its user using an IDP called keycloak, also companya doesnt want separate login flow
for its users when they are trying to use companyb software, basically companya wants their own IDP
to be used when using companyb, so that companya users can just use the same login and password.
Initially companya would have set it up their users already in keycloak, now if companya wants companyb
to use the same IDP of their, then they need to first do the following
companyacreates a client entry inside its Keycloak, typically informing IDP that a new client is onboarded, this client is basicallycompanybcompanyagenerates a client_secret for the above client, and hands that secret tocompanyb(note this secret should only be known to companyb, and companyb should store this securely in their backend)companyaalso putscompanybredirect URI, the url that keycloak will redirect after a login is successful.
How a login request actually flows#
Now comes the actual flow part, when companya users trying to access companyb software
-
companybfrom their end they may have a typical login screen, where they may have a buttonLogin with companyaIDPwhen a user press this button,companybwill basically redirect tocompanyaIDP login screen, while redirectingcompanybalso appendsclient_id,redirect_uriand somestatefor verification. -
now users login with their
companyacredentials as usual(companybhas no idea what are the creds and how this works), once its successful, the IDP automatically redirects tocompanybredirect_uri with a tempcodeattached to the query string of the redirect_uri. -
now
companybgets thatcodefrom the query string, and attaches itsclient_secret(the one thatcompanyagives, after setting up the client) and calls/tokenendpoint ofcompanyaIDP provider, this endpoint will give back anid_token, the actual JWT secret. -
the above JWT secret is the one,
companybcan get the details of the user, this user details can be fetched via parsing the JWT token usingcompanyaIDP public key.
Why B can’t just trust the token blindly#
What actually proves the token is the signature part in the JWT token. And this is where
asymmetric keys come in:
companya IDP provider has PRIVATE key and public key, signing the token is done by the
private key and verifiying is done by the companyb using companya IDP public key
Where to get the public key from? an unauthenticated URL called the JWKS endpoint (JSON Web Key Set). It’s just a JSON file:
```json { “keys”: [ { “kid”: “abc123”, “kty”: “RSA”, “alg”: “RS256”, “n”: “xGOr-H7…”, “e”: “AQAB” } ] } ```
companyb fetches this public key using jwks endpoint and then decodes and verifies the JWT token.
Why this check needed?
when token is created by companya IDP, it basically base64 the payload and signs that with RS256
and attaches that signature also in the token like base64(header).base64(payload).signature),
you can literally get the payload without verifying itself, but this may break a huge security hole.
consider a token is created with payload { role: sales } and its signed with RS256 with IDP private
key, now consider an attacker changed this token to { role: admin } in the JWT payload part alone
and sends, now if companyb decodes this without verifiying, we may actualy believed that he is admin
and would have given admin privileges, but if we have did the verification it would have failed
since the original signature is not associated with the new payload itself.
What is resource server here#
now consider after getting the token companyb further needs some other protected resource access
from companya, in this case companyb would further call GET /api/resource to companya with
the token that it already got and access the resources.
This is how a typical IDP based auth system works, and this example is written wrt keycloak in mind,
but typically all IDPs work the same way endpoints can change, but concept remains exactly the same.
You can also spin up ur own IDP system with all the rules followed from the oauth2 spec.
refer sample repo, where i written keycloak based companya and companyb example