DEV Community

Kim Maida
Kim Maida

Posted on • Edited on • Originally published at maida.kim

Authorization and Authentication For Everyone

Authentication and authorization are necessary for many of the applications we build. Maybe you've developed apps and implemented authentication and authorization in them — possibly by importing a third party auth library or by using an identity platform.

Maybe you got the job done, but you really weren't clear on what was happening behind the scenes, or why things were being done a certain way. If you'd like to build a foundational understanding of what goes on behind the scenes when using OAuth 2.0 and OpenID Connect standards, read on!


Authentication is hard. Why is this? Auth standards are well defined — but challenging to get right. And that's okay! We're going to go through it in an approachable way. We'll address the concepts of identity step by step, building on our knowledge as we go along. By the time we're done, you should have a foundation and know where you might want to dig deeper.

This post is meant to be read from beginning to end. We'll build on top of each concept to layer knowledge when it makes sense to introduce new topics. Please keep that in mind if you're jumping around in the content.


Introduction

When I told family or friends that I "work in identity," they often assumed that meant I was employed by the government issuing driver's licenses, or that I helped people resolve credit card fraud.

However, neither were true. I formerly worked for Auth0, a company that manages digital identity. (I'm now a member of the Auth0 Ambassadors program, and a Google Developer Expert in SPPI: Security, Privacy, Payments, and Identity.)

Digital Identity

Digital identity refers to a set of attributes that define an individual user in the context of a function delivered by a particular application.

What does that mean?

Say you run an online shoe retail company. The digital identity of your app's users might be their credit card number, shipping address, and purchase history. Their digital identity is contextual to your app.

This leads us to...

Authentication

In a broad sense, authentication refers to the process of verifying that a user is who they say they are.

Once a system has been able to establish this, we come to...

Authorization

Authorization deals with granting or denying rights to access resources.

Standards

You may recall that I mentioned that auth is guided by clearly-defined standards. But where do these standards come from in the first place?

There are many different standards and organizations that govern how things work on the internet. Two bodies that are of particular interest to us in the context of authentication and authorization are the Internet Engineering Task Force (IETF) and the OpenID Foundation (OIDF).

IETF (Internet Engineering Task Force)

The IETF is a large, open, international community of network designers, operators, vendors, and researchers who are concerned with the evolution of internet architecture and the smooth operation of the internet.

Really, that's a fancy way to say that dedicated professionals cooperate to write technical documents that guide us in how things should be done on the internet.

OIDF (OpenID Foundation)

The OIDF is a non-profit, international organization of people and companies who are committed to enabling, promoting, and protecting OpenID technologies.

Now that we are aware of the specs and who writes them, let's circle back around to authorization and talk about:

OAuth 2.0

OAuth 2.0 is one of the most frequently mentioned specs when it comes to the web — and also one that is often mis-represented or misunderstood. How so?

OAuth is not an authentication spec. OAuth deals with delegated authorization. Remember that authentication is about verifying the identity of a user. Authorization deals with granting or denying access to resources. OAuth 2.0 grants access to applications on the behalf of users. (Don't worry, we'll get to the authentication part in a little bit!)

Before OAuth

To understand the purpose of OAuth, we need to do go back in time. OAuth 1.0 was established in December 2007. Before then, if we needed to access third party resources, it looked like this:

Before OAuth

Let's say you used an app called HireMe123. HireMe123 wants to set up a calendar event (such as an interview appointment) on your (the user's) behalf. HireMe123 doesn't have its own calendar; it wants to use another service called MyCalApp to add events.

Once you were logged into HireMe123, HireMe123 would ask you for your MyCalApp login credentials. You would enter your MyCalApp username and password into HireMe123's site.

HireMe123 then used your MyCalApp login to gain access to MyCalApp's API, and could then create calendar events using your MyCalApp credentials.

Sharing Credentials is Bad!

This approach relied on sharing a user's personal credentials from one app with a completely different app, and this is not good. How so?

For one thing, HireMe123 had much less at stake in the protection of your MyCalApp login information. If HireMe123 didn't protect your MyCalApp credentials appropriately and they ended up stolen or breached, someone might write some nasty blog articles, but HireMe123 wouldn't face a catastrophe the way MyCalApp would.

HireMe123 also had way too much access to MyCalApp. HireMe123 had the same amount of access that you did, because they used your credentials to gain that access. That meant that HireMe123 could read all your calendar events, delete events, modify your calendar settings, etc.

Enter OAuth

This leads us to OAuth.

OAuth 2.0 is an open standard for performing delegated authorization. It's a specification that tells us how to grant third party access to APIs without exposing credentials.

Using OAuth, the user can now delegate HireMe123 to call MyCalApp on the user's behalf. MyCalApp can limit access to its API when called by third party clients without the risks of sharing login information or providing too much access. It does this using an:

Authorization Server

An authorization server is a set of endpoints to interact with the user and issue tokens. How does this help?

Let's revisit the situation with HireMe123 and MyCalApp, only now we have OAuth 2.0:

Authorization with OAuth 2.0

MyCalApp now has an authorization server. Let's assume that HireMe123 has already registered as a known client with MyCalApp, which means that MyCalApp's authorization server recognizes HireMe123 as an entity that may ask for access to its API.

Let's also assume you're already logged in with HireMe123 through whatever authentication HireMe123 has set up for itself. HireMe123 now wants to create events on your behalf.

HireMe123 sends an authorization request to MyCalApp's authorization server. In response, MyCalApp's authorization server prompts you — the user — to log in with MyCalApp (if you're not already logged in). You authenticate with MyCalApp.

The MyCalApp authorization server then prompts you for your consent to allow HireMe123 to access MyCalApp's APIs on your behalf. A prompt opens in the browser and specifically asks for your consent to let HireMe123 add calendar events (but no more than that).

If you say yes and grant your consent, then the MyCalApp authorization server will send an authorization code to HireMe123. This lets HireMe123 know that the MyCalApp user (you) did indeed agree to allow HireMe123 to add events using the user's (your) MyCalApp.

MyCalApp will then issue an access token to HireMe123. HireMe123 can use that access token to call the MyCalApp API within the scope of permissions that were accepted by you and create events for you using the MyCalApp API.

Nothing insidious is happening now! MyCalApp is asking the user to log in with MyCalApp. HireMe123 is not asking for the user's MyCalApp credentials. The issues with sharing credentials and too much access are no longer a problem.

What About Authentication?

At this point, I hope it's been made clear that OAuth is for delegated access. It doesn't cover authentication. At any point where authentication was involved in the processes we covered above, login was managed by whatever login process HireMe123 or MyCalApp had implemented at their own discretion. OAuth 2.0 didn't prescribe how this should be done: it only covered authorizing third party API access.

So why are authentication and OAuth so often mentioned in the same breath?

The Login Problem

The thing that happened after OAuth 2.0 established a way to access third party APIs was that apps also wanted to log users in with other accounts. Using our example: let's say HireMe123 wanted a MyCalApp user to be able to log into HireMe123 using their MyCalApp account, despite not having signed up for a HireMe123 account.

But as we mentioned above, OAuth 2.0 is for delegated access. It is not an authentication protocol. That didn't stop people from trying to use it like one though, and this presented problems.

Problems with Using Access Tokens for Authentication

If HireMe123 assumes successfully calling MyCalApp's API with an access token means the user can be considered authenticated with HireMe123, we run into problems because we have no way to verify the access token was issued to a particular individual.

For example:

  • Someone could have stolen the access token from a different user
  • The access token could have been obtained from another client (not HireMe123) and injected into HireMe123

This is called the confused deputy problem. HireMe123 doesn't know where this token came from or who it was issued for. If we recall: authentication is about verifying the user is who they say they are. HireMe123 can't know this from the fact that it can use this access token to access an API.

As mentioned, this didn't stop people from misusing access tokens and OAuth 2.0 for authentication anyway. It quickly became evident that formalization of authentication on top of OAuth 2.0 was necessary to allow logins with third party applications while keeping apps and their users safe.

OpenID Connect

This brings us to the specification called OpenID Connect, or OIDC. OIDC is a spec on top of OAuth 2.0 that says how to authenticate users. The OpenID Foundation (OIDF) is the steward of the OIDC standards.

OIDC is an identity layer for authenticating users with an authorization server. Remember that an authorization server issues tokens. Tokens are encoded pieces of data for transmitting information between parties (such as an authorization server, application, or resource API). In the case of OIDC and authentication, the authorization server issues ID tokens.

ID Tokens

ID tokens provide information about the authentication event and they identify the user. ID tokens are intended for the client. They're a fixed format that the client can parse and validate to extract identity information from the token and thereby authenticate the user.

OIDC declares a fixed format for ID tokens, which is:

JSON Web Token (JWT)

JSON Web Tokens, or JWT (sometimes pronounced "jot"), are composed of three URL-safe string segments concatenated with periods .

Header Segment

The first segment is the header segment. It might look something like this:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9

The header segment is a JSON object containing a signing algorithm and token type. It is base64Url encoded (byte data represented as text that is URL and filename safe).

Decoded, it looks something like this:

{
  "alg": "RS256",
  "typ": "JWT"
}
Enter fullscreen mode Exit fullscreen mode

Payload Segment

The second segment is the payload segment. It might look like this:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

This is a JSON object containing data claims, which are statements about the user and the authentication event. For example:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}
Enter fullscreen mode Exit fullscreen mode

This is also base64Url encoded.

Crypto Segment

The final segment is the crypto segment, or signature. JWTs are signed so they can't be modified in transit. When an authorization server issues a token, it signs it using a key.

When the client receives the ID token, the client validates the signature using a key as well. (If an asymmetric signing algorithm was used, different keys are used to sign and validate; if this is case, only the authorization server holds the ability to sign tokens.)

Don't worry if this seems confusing. The details of how this works shouldn't trouble you or keep you from effectively using an authorization server with token-based authentication. If you're interested in demystifying the terms, jargon, and details behind JWT signing, check out my article on Signing and Validating JSON Web Tokens (JWT) for Everyone.

Claims

Now that we know about the anatomy of a JWT, let's talk more about the claims, those statements from the Payload Segment. As per their moniker, ID tokens provide identity information, which is present in the claims.

Authentication Claims

We'll start with statements about the authentication event. Here are a few examples of these claims:

{
  "iss": "https://{you}.authz-server.com",
  "aud": "RxHBtq2HL6biPljKRLNByqehlKhN1nCx",
  "exp": 1570019636365,
  "iat": 1570016110289,
  "nonce": "3yAjXLPq8EPP0S",
  ...
}
Enter fullscreen mode Exit fullscreen mode

Some of the required authentication claims in an ID token include:

  • iss (issuer): the issuer of the JWT, e.g., the authorization server
  • aud (audience): the intended recipient of the JWT; for ID tokens, this must be the client ID of the application receiving the token
  • exp (expiration time): expiration time; the ID token must not be accepted after this time
  • iat (issued at time): time at which the ID token was issued

A nonce binds the client's authorization request to the token it receives. The nonce is a cryptographically random string that the client creates and sends with an authorization request. The authorization server then places the nonce in the token that is sent back to the app. The app verifies that the nonce in the token matches the one sent with the authorization request. This way, the app can verify that the token came from the place it requested the token from in the first place.

Identity Claims

Claims also include statements about the end user. Here are a few examples of these claims:

{
  "sub": "google-oauth2|102582972157289381734",
  "name": "Kim Maida",
  "picture": "https://gravatar[...]",
  "twitter": "https://twitter.com/KimMaida",
  "email": "kim@gatsbyjs.com",
  ...
}
Enter fullscreen mode Exit fullscreen mode

Some of the standard profile claims in an ID token include:

  • sub (subject): unique identifier for the user; required
  • email
  • email_verified
  • birthdate
  • etc.

We've now been through a crash-course on the important specifications (OAuth 2.0 and OpenID Connect) and it's time to see how to put our identity knowledge to work.

Authentication with ID Tokens

Let's see OIDC authentication in practice.

Note that this is a simplified diagram. There are a few different flows depending on your application architecture.

authentication with ID tokens in the browser

Our entities here are: the browser, an application running in the browser, and the authorization server. When a user wants to log in, the app sends an authorization request to the authorization server. The user's credentials are verified by the authorization server, and if everything checks out, the authorization server issues an ID token to the application.

The client application then decodes the ID token (which is a JWT) and verifies it. This includes validating the signature, and we must also verify the claims. Some examples of claim verification include:

  • issuer (iss): was this token issued by the expected authorization server?
  • audience (aud): is our app the target recipient of this token?
  • expiration (exp): is this token within a valid timeframe for use?
  • nonce (nonce): can we tie this token back to the authorization request our app made?

Once we've established the authenticity of the ID token, the user is authenticated. We also now have access to the identity claims and know who this user is.

Now the user is authenticated. It's time to interact with an API.

Accessing APIs with Access Tokens

We talked a bit about access tokens earlier, back when we were looking at how delegated access works with OAuth 2.0 and authorization servers. Let's look at some of the details of how that works, going back to our scenario with HireMe123 and MyCalApp.

Access Tokens

Access tokens are used for granting access to resources. With an access token issued by MyCalApp's authorization server, HireMe123 can access MyCalApp's API.

Unlike ID tokens, which OIDC declares as JSON Web Tokens, access tokens have no specific, defined format. They do not have to be (and aren't necessarily) JWT. However, many identity solutions use JWTs for access tokens because the format enables validation.

Access Tokens are Opaque to the Client

Access tokens are for the resource API and it is important that they are opaque to the client. Why?

Access tokens can change at any time. They should have short expiration times, so a user may frequently get new ones. They can also be re-issued to access different APIs or exercise different permissions. The client application should never contain code that relies on the contents of the access token. Code that does so would be brittle, and is almost guaranteed to break.

Accessing Resource APIs

Let's say we want to use an access token to call an API from a Single Page Application. What does this look like?

We've covered authentication above, so let's assume the user is logged into our JS app in the browser. The app sends an authorization request to the authorization server, requesting an access token to call an API.

Accessing an API with an access token

Then when our app wants to interact with the API, we attach the access token to the request header, like so:

# HTTP request headers
Authorization: 'Bearer eyj[...]'
Enter fullscreen mode Exit fullscreen mode

The authorized request is then sent to the API, which verifies the token using middleware. If everything checks out, then the API returns data (e.g., JSON) to the application running in the browser.

This is great, but there's something that may be occurring to you right about now. Earlier, we stated that OAuth solves problems with too much access. So how is that being addressed here?

Delegation with Scopes

How does the API know what level of access it should give to the application that's requesting use of its API? We do this with scopes.

Scopes "limit what an application can do on the behalf of a user." They cannot grant privileges the user doesn't already have. For example, if the MyCalApp user doesn't have permission to set up new MyCalApp enterprise accounts, scopes granted to HireMe123 won't ever allow the user to set up new enterprise accounts either.

Scopes delegate access control to the API or resource. The API is then responsible for combining incoming scopes with actual user privileges to make the appropriate access control decisions.

Let's walk through this with an example.

I'm using the HireMe123 app and HireMe123 wants to access the third party MyCalApp API to create events on my behalf. HireMe123 has already requested an access token for MyCalApp from MyCalApp's authorization server. This token has some important information in it, such as:

  • sub: (my MyCalApp user ID)
  • aud: MyCalAppAPI (audience stating this token is intended for the MyCalApp API)
  • scope: write:events (scope saying HireMe123 has permission to use the API to write events to my calendar)

HireMe123 sends a request to the MyCalApp API with the access token in its authorization header. When the MyCalApp API receives this request, it can see that the token contains a write:events scope.

But MyCalApp hosts calendar accounts for hundreds of thousands of users. In addition to looking at the scope in the token, MyCalApp's API middleware needs to check the sub subject identifier to make sure this request from HireMe123 is only able to exercise my privileges to create events with my MyCalApp account.

delegated authorization with scopes and API access control

In the context of delegated authorization, scopes express what an application can do on the user's behalf. They're a subset of the user's total capabilities.

Granting Consent

Remember when the authorization server asked the HireMe123 user for their consent to allow HireMe123 to use the user's privileges to access MyCalApp?

That consent dialog might look something like this:

consent dialog flow: HireMe123 is requesting access to your MyCalApp account to write:calendars

HireMe123 could ask for a variety of different scopes, for example:

  • write:events
  • read:events
  • read:settings
  • write:settings
  • ...etc.

In general, we should avoid overloading scopes with user-specific privileges. Scopes are for delegated permissions for an application. However, it is possible to add different scopes to individual users if your authorization server provides Role-Based Access Control (RBAC).

With RBAC, you can set up user roles with specific permissions in your authorization server. Then when the authorization server issues access tokens, it can include a specific user's roles in their scopes.


Resources and What's Next?

We covered a lot of material, and it still wasn't anywhere close to everything. I do hope this was a helpful crash course in identity, authorization, and authentication.

To further demystify JWT, read my article Signing and Validating JSON Web Tokens for Everyone.

If you'd like to learn much, much more on these topics, here are some great resources for you to further your knowledge:

Learn More

The Learn Identity video series in the Auth0 docs is the lecture portion of the new hire identity training for engineers at Auth0, presented by Principal Architect Vittorio Bertocci. If you'd like to learn identity the way it’s done at Auth0, it's completely free and available to everyone (you don't even have to pay with a tweet or email!).

The OAuth 2.0 and OpenID Connect specifications are dense, but once you're familiar with the terminology and have foundational identity knowledge, they're helpful, informative, and become much more digestible. Check them out here: The OAuth 2.0 Authorization Framework and OpenID Connect Specifications.

JWT.io is a JSON Web Token resource that provides a debugger tool and directory of JWT signing/verification libraries for various technologies.

The OpenID Connect Playground is a debugger that lets developers explore and test OIDC calls and responses step-by-step.

Thank You!

If you'd like to chat, I'm available on Twitter at @KimMaida, and I also speak at conferences and events — at least, I did before COVID-19. I have delivered this content in presentation format, and hope to do so again once travel and in-person events resume. I hope to see you sometime, and thank you so much for reading!

Top comments (24)

Collapse
 
rmachuca89 profile image
Rodrigo Machuca

Thank you for sharing this outstanding interesting and well written article, and just timely for my particular needs. Keep it up!

Collapse
 
kimmaida profile image
Kim Maida

Thanks, Rodrigo, and you’re welcome! I hope it helps you with what you’re working on!

Collapse
 
kayis profile image
K

Thanks for that article, cleared some things up!

But I also have a question: The docs talk about storing subscription plan data into the users metadata, but don't go into detail how. Would I use a rule or hook for this? Also what is the difference?

Collapse
 
kimmaida profile image
Kim Maida • Edited

In a very general sense, I would recommend rules for adding the user metadata to your ID and/or access tokens (depending on the use case), but without knowing more details, I'm not able to detail a flow for you.

I strongly recommend that you ask this question in the Auth0 Community forum and provide details there such as:

  • What's the source of the subscription plan data? (E.g., a database?)
  • What's the purpose of the data? (E.g., to simply display in the client? To be able to change the plan? To control access based on the plan?)
  • What's your application architecture? (E.g., your API? third party APIs? Backend app? JS app running in the browser? etc.)

The folks at Auth0 should then be able to help you determine the correct flow to get all your data securely where it needs to go.

Collapse
 
bam92 profile image
Abel Lifaefi Mbula

Thank you for your post. It is well written and easy to digest for everyone (I think).

I really appreciate your diagrams. Can you tell how do you create them, please?

What would be your advice for modern developers: should they just forget about or abandon Auth 2.0 and stick to OIDC?

Collapse
 
kasaragaddaanil profile image
kasaragaddaanilkumar

As you mentioned these 2 points as problems with using access tokens for authentication:

  1. Someone could have stolen the access token from a different user
  2. The access token could have been obtained from another client (not HireMe123) and injected into HireMe123

How are they being solved in OpenID Connect?
Even in OpenID connect, what if I stole a JWT token and present it to authorization server, it will still work right? It is the same for point 2 as well right?

Collapse
 
poxrud profile image
Phil

If a token is stolen it can be used by someone else. Just like if a cookie is stolen it too can be used by someone else. It is up to the develop to make sure their application is secure against attacks.

Collapse
 
kimmaida profile image
Kim Maida

OIDC addresses this by using something called a nonce, which is explained in the section on authentication claims. A nonce provides a way for a client to know that the token it receives is the one being returned from the exact authorization request that was issued.

Collapse
 
harkinj profile image
harkinj

Great article.
2 small questions :
'The client application then decodes the ID token (which is a JWT) and verifies it. This includes validating the signature ' - to do this must the client app have the public key of the issuer installed? Does the client also need to communicate/interface with the auth server at the point of verification/validation?
Thanks for your time.

Collapse
 
kimmaida profile image
Kim Maida

Hello, thanks for your questions!

1) Yes, the client app must have access to the decryption key in order to validate the signature; otherwise, it won't be able to decrypt the signature to see its contents. If asymmetric key cryptography is being used, then it will be a public key; if symmetric, there is only one key, and that same key must be kept private on both the client and authorization server (not recommended).

2) No, the client does not need to communicate with the auth server during validation. It should already have the key, and everything else it needs to perform validation is contained within the JWT itself.

It's strongly recommended that you not implement validation manually, but rather, that you use an SDK or library. If you'd like to learn a lot more about this, I also wrote Signing and Validating JSON Web Tokens (JWT) for Everyone.

Collapse
 
yiremani profile image
Sigonz

that was awesome

Collapse
 
kimmaida profile image
Kim Maida

Thank you for the kind words, and thank you for reading!

Collapse
 
johncerpa profile image
John Cerpa

Thanks for the info

Collapse
 
nicolasdw profile image
Nicolas-DW

Really helpfull and crystal clear!
Thanks a lot

Collapse
 
silvesterwali profile image
silvesterwali

keep up ....thanks for sharing good one

Collapse
 
cheahengsoon profile image
Eng Soon Cheah

When using the iPhone (Safari) for authentication, always error 401 unauthorized for Windows Identity, any idea to solve this?