Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Ruby on Rails  Module 9 – APIs with Rails

API authentication, CORS and rate limits

Protect a Rails 8.1 API with bearer tokens, an explicit rack-cors allowlist and a per-token rate limit that answers 429 with Retry-After.

  • Advanced
  • 20 minutes
  • Examples run with Ruby 4.0.7 and rails 8.1.4
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A CORS policy lists the origins a browser may trust · A bearer token in the Authorization header names the client · A rate limit answers 429 when one client sends too much · Practise: change the policy and predict the answers. With a pass: 2 in-depth parts and 4 interview answers, about 12 minutes.

What you will learn

  • Configure rack-cors with an explicit list of origins, methods and headers
  • Protect every API action with a bearer token and the 401 challenge of RFC 6750
  • Store API tokens as digests with scopes, and revoke a token at once
  • Limit payments per token with rate_limit and make a retried payment run once

Before you start

What you’ll learn · Before you start

What you will learn

  • Configure rack-cors with an explicit list of origins, methods and headers
  • Protect every API action with a bearer token and the 401 challenge of RFC 6750
  • Store API tokens as digests with scopes, and revoke a token at once
  • Limit payments per token with rate_limit and make a retried payment run once

Before you start

On this page

The batches API of the previous lesson answers anyone who asks, and two front ends are about to call it: the guardians’ mobile app and the staff dashboard at https://admin.kaksha.example.com. Before they ship it needs three guards: a CORS policy that tells browsers which sites’ scripts may read its answers, a bearer token that names the calling client, and a rate limit: A filter that counts the requests of one client against a key in a cache store and refuses them once the count rises above the number allowed in the window, by default with a 429 response. More in “Filters, errors and HTTP features” that stops a looping client from sending 50 payments a second.

A CORS policy lists the origins a browser may trust

A browser lets a page’s script read a response from another origin (scheme, host and port) only when the response names the page’s origin in Access-Control-Allow-Origin, or * for a request sent without credentials. That rule is CORS: Cross-Origin Resource Sharing: a protocol where the browser sends an Origin header and the server returns Access-Control-Allow-Origin to say which origins may read the response.. A request with an Authorization header gets a preflight request: An OPTIONS request a browser sends before a cross-origin request whose method or headers are not on the CORS safelist, such as one with an Authorization header; the answer says whether the real request may follow. first: an OPTIONS request that names the method and headers the script wants to use.

In Rails the rack-cors gem answers both. A rails new --api app has the gem commented out in its Gemfile and a commented example in config/initializers/cors.rb; this API turns both on with an allowlist: A rule that accepts only values on a known-good list and refuses everything else, rather than trying to list the values to reject, which always misses one. More in “Validations, errors and normalization” of two origins:

config/initializers/cors.rb Ruby · lab/config/initializers/cors.rb
# Cross-origin requests from the two Kaksha front ends, and from no other origin. rack-cors compares the request's
# Origin header with each entry as a whole: scheme, host and port. An origin that matches no entry gets no CORS
# headers at all, so the browser keeps the response away from that page's script.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "https://kaksha.example.com", "https://admin.kaksha.example.com"

    resource "/api/*",
      headers: %w[Authorization Content-Type Idempotency-Key],
      methods: %i[get post options head],
      expose: %w[Retry-After],
      max_age: 600
  end
end

The session sends a preflight and a GET with a valid token from the dashboard, and the same two from https://unknown.example:

Two origins, a preflight and a GET each Ruby · sessions/cors_session.rb
# A CORS preflight and a real request from the allowed dashboard origin and from an origin the policy does not list.
require_relative "session"

Session.run(<<~SH)
  cd ../kaksha_api
  cp -R ../lab/. .
  bin/rails db:migrate > /dev/null
  bin/rails test test/integration/cors_test.rb --seed 1
SH

Output

$ cd ../kaksha_api
$ cp -R ../lab/. .
$ bin/rails db:migrate > /dev/null
$ bin/rails test test/integration/cors_test.rb --seed …
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed …

# Running:

# preflight from https://admin.kaksha.example.com -> 200
  access-control-allow-origin: https://admin.kaksha.example.com
  access-control-allow-methods: GET, POST, OPTIONS, HEAD
  access-control-expose-headers: Retry-After
  access-control-max-age: 600
  access-control-allow-headers: authorization
# preflight from https://unknown.example -> 200, CORS headers: 0
# GET from https://admin.kaksha.example.com -> 200
  access-control-allow-origin: https://admin.kaksha.example.com
  access-control-allow-methods: GET, POST, OPTIONS, HEAD
  access-control-expose-headers: Retry-After
  access-control-max-age: 600
  vary: Origin
# GET from https://unknown.example -> 200, body: {"data":[],"next_cursor":null}
  vary: Origin
.

Finished in …s, … runs/s, … assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby cors_session.rb

Interactive · message by message A preflight, then the real request Step through what a browser asks before a cross-origin GET with a token, and what the API answers.
A preflight, then the real request, step 6 of 6. Messages so far: a page's script to Rack::Cors: OPTIONS, Origin: admin…; Rack::Cors to a page's script: 200, allow-origin: admin…; a page's script to the controllers: GET + Authorization; the controllers to a page's script: 200, allow-origin + vary; a page's script to Rack::Cors: OPTIONS, Origin: unknown; the controllers to a page's script: 200 to non-browsers.a page's scriptbrowser blocks itRack::Corsfirst middlewarethe controllers/api/v1/batchesOPTIONS, Origin: admin…200, allow-origin: admin…GET + Authorization200, allow-origin + varyOPTIONS, Origin: unknown200 to non-browsers

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The browser asks first. A script on https://admin.kaksha.example.com wants GET /api/v1/batches with an Authorization header. That header makes the request one a browser checks first, so it sends an OPTIONS preflight naming the method and the header.
  2. Rack::Cors answers it. The origin is on the list, so Rack::Cors answers 200 itself with the allowed origin, methods, request headers and a max age of 600 seconds. The preflight never reaches a controller.
  3. The real GET follows. The browser now sends the GET with its token. Rack::Cors passes it on, and the controller checks the token as it would for any client.
  4. The response carries the grant. The 200 comes back with access-control-allow-origin naming the dashboard and vary: Origin, so a cache keeps one copy per origin. The browser hands the body to the script.
  5. An unlisted origin asks. A page on https://unknown.example sends the same preflight. Rack::Cors answers 200 with no CORS headers at all, and the browser does not send the real request.
  6. The server still answers others. A client that skips the browser's check, such as curl, gets the full 200 body from the same API. CORS decides what a browser lets a page read; the token check decides who gets data.

The dashboard’s preflight got 200 with its origin, the methods, the authorization header and a max age of 600 seconds. The unknown origin’s preflight got 200 with no CORS headers, so a browser stops there. Its direct GET still got the 200 body, with only vary: Origin: CORS decides what a browser lets a page read, never who may call the API.

A list that allows everyone

origins { |source, _env| true } reads like a list and allows every origin. With credentials: true and cookies that browsers send cross-site, it lets any site’s script read whatever those cookies give access to. Name each origin, and turn credentials on only for an endpoint that really uses cookies, which then needs CSRF protection back too.

Checkpoint

Check: CORS and rack-cors

  1. Question 1 of 2 A teammate writes origins { |source, _env| true } with credentials true. What does that allow?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A script on any site can read the API's answers to requests sent with the user's cookies

    List each origin by name, and turn credentials on only for endpoints that really use cookies.

  2. Question 2 of 2 In the recorded run, what did the preflight from https://unknown.example get back?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 200 with no CORS headers at all, so a browser would not send the real request

    A CORS refusal is the absence of headers; the browser, not the server, stops the page's script.

A bearer token in the Authorization header names the client

A bearer token: A credential presented in the Authorization header as Bearer <token>; whoever holds the string can call the API as the identity the token represents, so its secrecy is the only protection. works for whoever holds it: the client sends Authorization: Bearer <token> with each request, and RFC 6750 says how a server refuses one. ActionController::API leaves the token helpers out, so the API’s concern includes them and checks the header before every action. API authentication here means one token per client app, checked on every request:

app/controllers/concerns/api_token_authentication.rb Ruby · lab/app/controllers/concerns/api_token_authentication.rb
# Bearer-token authentication for the API controllers (RFC 6750). ActionController::API does not include the
# token helpers, so this concern includes them.
module ApiTokenAuthentication
  extend ActiveSupport::Concern
  include ActionController::HttpAuthentication::Token::ControllerMethods

  included do
    before_action :authenticate_api_token!
  end

  private

  attr_reader :current_token

  def authenticate_api_token!
    @current_token = authenticate_with_http_token { |plain, _options| ApiToken.authenticate(plain) }
    return if @current_token

    # A token that was sent and failed gets error="invalid_token"; no token, or another scheme such as Basic,
    # gets the bare challenge (RFC 6750 section 3.1).
    tried = request.authorization.to_s.match?(/\A(?:Bearer|Token)\s/)
    response.headers["WWW-Authenticate"] = %(Bearer realm="kaksha"#{', error="invalid_token"' if tried})
    render_problem(status: 401, title: "Unauthorized",
                   detail: tried ? "The token is unknown or revoked." : "Send Authorization: Bearer <token>.")
  end
end
No token, a made-up token and a valid one Ruby · sessions/bearer_session.rb
# Bearer tokens on /api/v1/batches: no header, a token the API never issued, and a valid token in two schemes.
require_relative "session"

Session.run(<<~SH)
  cd ../kaksha_api
  cp -R ../lab/. .
  bin/rails db:migrate > /dev/null
  bin/rails test test/integration/bearer_test.rb --seed 1
SH

Output

$ cd ../kaksha_api
$ cp -R ../lab/. .
$ bin/rails db:migrate > /dev/null
$ bin/rails test test/integration/bearer_test.rb --seed …
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed …

# Running:

# no Authorization header -> 401
  www-authenticate: Bearer realm="kaksha"
# a token the API never issued -> 401
  www-authenticate: Bearer realm="kaksha", error="invalid_token"
# Authorization: Bearer <the issued token> -> 200
# Authorization: Token <the issued token> -> 200
.

Finished in …s, … runs/s, … assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby bearer_session.rb

Interactive · message by message Four requests, three answers Step through the four requests of the recorded test and the header each answer carried.
Four requests, three answers, step 4 of 4. Messages so far: a client to the token check: GET, no header; a client to the token check: Bearer not-a-real-token; the token check to the action: token accepted; a client to the action: Token <the same value>.a clientguardian appthe token check401, invalid_tokenthe action200GET, no headerBearer not-a-real-tokentoken acceptedToken <the same value>

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. No Authorization header. The client sends nothing. The API answers 401 with WWW-Authenticate: Bearer realm="kaksha", which tells the client which scheme to use and nothing more.
  2. A token nobody issued. The header carries a token the API never issued. The answer is 401 again, now with error="invalid_token", because the client did try to authenticate.
  3. A valid bearer token. The issued token passes the check, and the action runs and answers 200.
  4. The same token as Token. Rails' token helper reads both Authorization: Bearer and Authorization: Token, so the same value sent with the older Token scheme also answers 200.

No header got 401 with the bare challenge Bearer realm="kaksha"; a made-up token got error="invalid_token" too, as RFC 6750 asks once a client has tried. The issued token answered 200 in the Bearer and the older Token scheme alike. The table stores only a SHA-256 token digest: A one-way hash of a token, usually SHA-256, stored in the database instead of the plain token so a leaked table does not hand the attacker every credential. of each token, never the token. Each token carries token scope: A list of permissions a token carries, usually stored as a space-separated string; the controller checks the scope before acting, so a read-only token cannot create resources., and one without the scope an action needs gets 403. A revoked token fails on its next request.

A rate limit answers 429 when one client sends too much

Rails 8.1’s rate_limit is a class method that adds a before_action, so rate limits are one line per controller. The payments endpoint counts per token, not per IP address, because many guardians share one school network:

app/controllers/api/v1/payments_controller.rb (the limit) Ruby · snippets/payments_rate_limit.rb
# The limit line of app/controllers/api/v1/payments_controller.rb: five payments a minute, counted per token.
class Api::V1::PaymentsController < Api::V1::BaseController
  rate_limit to: 5, within: 1.minute, by: -> { current_token.id }, with: -> { too_many_requests }, only: :create
end
Interactive · message by message Five payments a minute, counted per token Step through six payments from one token inside one minute.
Five payments a minute, counted per token, step 4 of 4. Messages so far: the front desk app to rate_limit: POST payment 1; rate_limit to the cache store: increment; rate_limit to the front desk app: 429 Too Many Requests.the front desk appone tokenrate_limita before_actionthe cache storecount 1 of 5POST payment 1increment429 Too Many Requests

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The first payment. Before the action runs, rate_limit adds 1 to a counter in the cache store under a key built from the controller path and the token's id, and the counter expires one minute after it starts.
  2. Payments 2 to 5. Each later payment in the same minute adds 1. Up to 5 the action runs and answers 201.
  3. The sixth payment. The sixth increment returns 6, which is more than 5, so rate_limit calls its with: handler instead of the action, and the client gets 429 Too Many Requests.
  4. A new minute. When the counter expires, the next payment starts a new count at 1. Another token has its own counter all along.

The sixth payment in a minute meets the with: handler instead of the action and gets 429 Too Many Requests, the status RFC 6585 defines. The limit runs before the action, so a refused payment never reaches the database. The counter lives in the controller’s cache store, so a store that keeps nothing limits nothing. A 429 may carry Retry-After: A response header, defined in RFC 9110, that tells a client how long to wait before its next request, in seconds or as a date; a 429 or a 503 answer may carry it., the seconds to wait. An Idempotency-Key: A request header in which a client names one attempt of a request that must not run twice, such as a payment, so the server can run it once and answer retries with the stored response. header lets a client retry a payment without paying twice.

Checkpoint

Check: Bearer tokens and the 401 challenge · Rate limits and 429

  1. Question 1 of 2 A request arrives with no Authorization header. What did the recorded API answer?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 401 with WWW-Authenticate: Bearer realm="kaksha" and no error code

    No token: a bare challenge; a bad token: the same 401 with error="invalid_token".

  2. Question 2 of 2 With rate_limit to: 5, within: 1.minute, how many payments from one token in the same minute answer 201 before the first 429?

    Type a number.

    A hint

    The limit refuses a request once the count is above to.

    Show the answer: Question 2 of 2

    Answer: 5 payments

    Requests 1 to 5 run the action; the sixth increment returns 6, which is above 5, so it gets 429.

Practise: change the policy and predict the answers

Work on your own copy of the lab and of the CORS and bearer tests, and write down what you expect before each run.

  1. Add http://localhost:5173 to origins in cors.rb for a front-end developer’s machine, add a preflight from that origin to cors_test.rb, and predict its headers.
  2. In bearer_test.rb, send Authorization: Basic a2Frc2hhOmxvZ2lu and predict the status and the challenge.

Practice that adapts to you

Practise this lesson

7 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 A teammate says the CORS policy keeps unknown clients away from the API. What does the recorded run say?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: The GET from the unknown origin still got the 200 body; CORS only decides what a browser lets a page read

    CORS is a browser rule about reading answers, never a server-side access control.

  2. Question 2 of 7 The mobile app moves its web version to https://m.kaksha.example.com. What must change, and where?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: Add that exact origin to origins in config/initializers/cors.rb; the scheme, host and port must match

    A list of exact origins is easy to read and easy to review; each new front end is one new line.

  3. Question 3 of 7 Which Authorization schemes did Rails' token helper accept in the recorded run?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: Both Bearer and Token

    authenticate_with_http_token reads both schemes; clients should send Bearer, the scheme RFC 6750 defines.

  4. Question 4 of 7 An API-only controller calls authenticate_with_http_token and gets NoMethodError. Why?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: ActionController::API does not include ActionController::HttpAuthentication::Token::ControllerMethods

    An API-only controller starts with fewer modules; include the token helpers where you authenticate.

  5. Question 5 of 7 Why does the API answer a made-up token with error="invalid_token" but a request without any token with no error code?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: RFC 6750 asks servers not to add an error code when the client sent no credentials, and to say invalid_token when a sent token failed

    The error code tells a client whether to ask a user for credentials or to replace a token.

  6. Question 6 of 7 Why does the payments limit use by -> { current_token.id } rather than the default, the client's IP address?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: Many guardians share one school network, so an IP limit would let one busy family block everyone else

    Count what identifies the client you want to limit; behind shared networks that is the token.

  7. Question 7 of 7 Which status does a request get when it goes over the rate limit?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: 429 Too Many Requests

    429 names the condition exactly, and it may carry Retry-After.

Hashed tokens, scopes and revocation, run end to end Pro: opens with a Pro, Premium or Ultimate pass

A bearer token is only as safe as the table behind it: what the table stores, what each token may do, and how fast a lost one stops working.

In this part:

  • Only a digest of each token reaches the database
  • Why a lookup by digest leaks nothing useful, and where a constant-time compare belongs
  • Scopes turn a valid token into a 403
  • Revocation stops a token on its next request
  • A table of tokens, a JWT or an OAuth server

1 diagram · 3 runnable examples (Ruby) · about 9 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Limits that count, a 429 to act on, and payments that run once Pro: opens with a Pro, Premium or Ultimate pass

A limit is only as good as the counter behind it, and a client that retries after a timeout pays twice unless the server remembers the first request.

In this part:

  • A limit counts only in a store that keeps counts
  • A 429 the client can act on
  • An Idempotency-Key makes a retried payment run once

1 diagram · 4 runnable examples (Ruby) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

Warm-up: why should rack-cors list each origin instead of echoing the request’s Origin header?

An echoed origin allows every site while looking like a policy. A browser trusts whatever the response names, so any page’s script could read the API’s answers, and with cookies allowed it could read what the signed-in user can see. A list of the API’s own front ends lets only them read cross-origin, and CORS never replaces authentication: other clients reach the API anyway.

Four harder questions on tokens, origins and limits, with first steps and follow-ups in the module’s bank:

  1. Mid to senior

    An API that serves signed-in users' data echoes the Origin header into Access-Control-Allow-Origin and allows credentials. Walk through what a page on another site can do, say which users are exposed, and give the fix.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A backup of the api_tokens table leaks. The table stores a SHA-256 digest of each token. What can someone holding the backup do, and what should the team do next?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    A rate limit of 10 requests a minute refuses the eleventh request in development, but its test lets all eleven through. The controller code is the same everywhere. Find the cause and fix the test without slowing the whole suite.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A team plans to replace its database tokens with JWTs. Explain what happens to revocation, and design a way to stop a stolen JWT within one minute of an incident.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does a CORS policy control, and what does it not stop?

    Which origins' scripts a browser lets read the API's answers. A client that is not a browser still gets the body: CORS never decides who may call the API, so authentication is still needed.

  2. Why must a CORS policy name each origin instead of allowing whatever origin asks?

    An origin check that accepts everything allows every site while looking like a policy. With credentials on and cookies sent cross-site, any site's script could read what the signed-in user can see. Turn credentials on only for an endpoint that really uses cookies, and give it CSRF protection back.

  3. Which request gets a CORS preflight, and how did an unknown origin's preflight come back?

    A cross-origin request with an Authorization header is preceded by an OPTIONS request naming the method and headers the script wants. The unknown origin got 200 with no CORS headers, so the browser went no further.

  4. How does an API answer a request with no bearer token, and one with a made-up token?

    Both get 401. Without a token the answer is a bare challenge naming the realm; once a client has sent a token, the challenge adds error="invalid_token", as RFC 6750 asks. ActionController::API leaves out the token helpers, so the API's concern includes them.

  5. What does the token table store for each client, and what do scopes and revocation do?

    Only a SHA-256 digest of each token, never the token itself. A token without the scope an action needs gets 403, and a revoked token fails on its next request.

  6. Why does the payments endpoint count its rate limit per token instead of per IP address, and what does a refused call get?

    Many guardians share one school network, so one address can hold many honest clients. The call over the limit gets 429 Too Many Requests before the action runs, so it never reaches the database; the response may carry Retry-After.

Key takeaways

  • rack-cors lets a browser hand cross-origin answers only to the origins it lists, answers preflights itself, and never stops a client that is not a browser.
  • API authentication with authenticate_with_http_token reads Authorization: Bearer, and RFC 6750 gives the refusals: a bare challenge without a token, invalid_token for a bad one.
  • API tokens are stored as digests, carry scopes and can be revoked at once.
  • Rate limits with rate_limit count per token in the cache store and answer 429, with Retry-After and an Idempotency-Key for clients that retry safely.

Terms in this lesson

bearer token
A credential presented in the Authorization header as Bearer <token>; whoever holds the string can call the API as the identity the token represents, so its secrecy is the only protection.
constant-time comparison
A comparison that takes the same time whether strings match or differ, so an attacker cannot learn bytes of a secret by measuring how quickly the server says no.
CORS
Cross-Origin Resource Sharing: a protocol where the browser sends an Origin header and the server returns Access-Control-Allow-Origin to say which origins may read the response.
Idempotency-Key
A request header in which a client names one attempt of a request that must not run twice, such as a payment, so the server can run it once and answer retries with the stored response.
JWT
JSON Web Token: a signed, optionally encrypted, self-describing credential that carries claims (user id, expiry, scopes) in its payload, verified by checking the signature without a database lookup.
preflight request
An OPTIONS request a browser sends before a cross-origin request whose method or headers are not on the CORS safelist, such as one with an Authorization header; the answer says whether the real request may follow.
Retry-After
A response header, defined in RFC 9110, that tells a client how long to wait before its next request, in seconds or as a date; a 429 or a 503 answer may carry it.
token digest
A one-way hash of a token, usually SHA-256, stored in the database instead of the plain token so a leaked table does not hand the attacker every credential.
token scope
A list of permissions a token carries, usually stored as a space-separated string; the controller checks the scope before acting, so a read-only token cannot create resources.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.