Hermann  Frami

Hermann Frami

1668006428

Grant: OAuth Proxy

Grant

OAuth Proxy

200+ Supported Providers / OAuth Playground

23andme | 500px | acton | acuityscheduling | adobe | aha | alchemer | amazon | angellist | apple | arcgis | asana | assembla | atlassian | auth0 | authentiq | authing | autodesk | aweber | axosoft | baidu | basecamp | battlenet | beatport | bitbucket | bitly | box | buffer | campaignmonitor | cas | cheddar | clio | cognito | coinbase | concur | constantcontact | coursera | crossid | dailymotion | deezer | delivery | deputy | deviantart | digitalocean | discogs | discord | disqus | docusign | dribbble | dropbox | ebay | echosign | ecwid | edmodo | egnyte | etsy | eventbrite | evernote | eyeem | facebook | familysearch | feedly | figma | fitbit | flattr | flickr | flowdock | formstack | foursquare | freeagent | freelancer | freshbooks | fusionauth | garmin | geeklist | genius | getbase | getpocket | gitbook | github | gitlab | gitter | goodreads | google | groove | gumroad | harvest | hellosign | heroku | homeaway | hootsuite | huddle | ibm | iconfinder | idme | idonethis | imgur | infusionsoft | instagram | intuit | jamendo | jumplead | kakao | keycloak | line | linkedin | live | livechat | logingov | lyft | mailchimp | mailup | mailxpert | mapmyfitness | mastodon | medium | meetup | mendeley | mention | microsoft | mixcloud | moxtra | myob | naver | nest | netlify | nokotime | notion | nylas | okta | onelogin | openstreetmap | optimizely | patreon | paypal | phantauth | pinterest | plurk | podio | procore | producthunt | projectplace | pushbullet | qq | ravelry | redbooth | reddit | runkeeper | salesforce | sellsy | shoeboxed | shopify | skyrock | slack | slice | smartsheet | smugmug | snapchat | snowflake | socialpilot | socrata | soundcloud | spotify | square | stackexchange | stocktwits | stormz | storyblok | strava | stripe | surveymonkey | surveysparrow | thingiverse | ticketbud | tiktok | timelyapp | todoist | trakt | traxo | trello | tripit | trustpilot | tumblr | twitch | twitter | typeform | uber | unbounce | underarmour | unsplash | untappd | upwork | uservoice | vend | venmo | vercel | verticalresponse | viadeo | vimeo | visualstudio | vk | wechat | weekdone | weibo | withings | wordpress | wrike | xero | xing | yahoo | yammer | yandex | zendesk | zoom


Handlers

HTTP Frameworks

Express

var express = require('express')
var session = require('express-session')
var grant = require('grant').express()

var app = express()
// REQUIRED: any session store - see /examples/handler-express
app.use(session({secret: 'grant'}))
// mount grant
app.use(grant({/*configuration - see below*/}))

Koa

var Koa = require('koa')
var session = require('koa-session')
var grant = require('grant').koa()

var app = new Koa()
// REQUIRED: any session store - see /examples/handler-koa
app.keys = ['grant']
app.use(session(app))
// mount grant
app.use(grant({/*configuration - see below*/}))

Hapi

var Hapi = require('hapi')
var yar = require('yar')
var grant = require('grant').hapi()

var server = new Hapi.Server()
server.register([
  // REQUIRED: any session store - see /examples/handler-hapi
  {plugin: yar, options: {cookieOptions: {password: 'grant', isSecure: false}}},
  // mount grant
  {plugin: grant({/*configuration - see below*/})}
])

Fastify

var fastify = require('fastify')
var cookie = require('fastify-cookie')
var session = require('@fastify/session')
var grant = require('grant').fastify()

fastify()
  .register(cookie)
  .register(session, {secret: 'grant', cookie: {secure: false}})
  .register(grant({/*configuration - see below*/}))

Serverless Functions

AWS Lambda

var grant = require('grant').aws({
  config: {/*configuration - see below*/}, session: {secret: 'grant'}
})

exports.handler = async (event) => {
  var {redirect, response} = await grant(event)
  return redirect || {
    statusCode: 200,
    headers: {'content-type': 'application/json'},
    body: JSON.stringify(response)
  }
}

Azure Function

var grant = require('grant').azure({
  config: {/*configuration - see below*/}, session: {secret: 'grant'}
})

module.exports = async (context, req) => {
  var {redirect, response} = await grant(req)
  return redirect || {
    status: 200,
    headers: {'content-type': 'application/json'},
    body: JSON.stringify(response)
  }
}

Google Cloud Function

var grant = require('grant').gcloud({
  config: {/*configuration - see below*/}, session: {secret: 'grant'}
})

exports.handler = async (req, res) => {
  var {response} = await grant(req, res)
  if (response) {
    res.statusCode = 200
    res.setHeader('content-type', 'application/json')
    res.end(JSON.stringify(response))
  }
}

Vercel

var grant = require('grant').vercel({
  config: {/*configuration - see below*/}, session: {secret: 'grant'}
})

module.exports = async (req, res) => {
  var {response} = await grant(req, res)
  if (response) {
    res.statusCode = 200
    res.setHeader('content-type', 'application/json')
    res.end(JSON.stringify(response))
  }
}

Examples

express / koa / hapi / fastify / aws / azure / gcloud / vercel

ES Modules and TypeScript


Configuration

Configuration: Basics

{
  "defaults": {
    "origin": "http://localhost:3000",
    "transport": "session",
    "state": true
  },
  "google": {
    "key": "...",
    "secret": "...",
    "scope": ["openid"],
    "nonce": true,
    "custom_params": {"access_type": "offline"},
    "callback": "/hello"
  },
  "twitter": {
    "key": "...",
    "secret": "...",
    "callback": "/hi"
  }
}
  • defaults - default configuration for all providers
    • origin - where your client server can be reached http://localhost:3000 | https://site.com ...
    • transport - a transport used to deliver the response data in your callback route
    • state - generate random state string
  • provider - any supported provider google | twitter ...
    • key - consumer_key or client_id of your OAuth app
    • secret - consumer_secret or client_secret of your OAuth app
    • scope - array of OAuth scopes to request
    • nonce - generate random nonce string (OpenID Connect only)
    • custom_params - custom authorization parameters
    • callback - relative route or absolute URL to receive the response data /hello | https://site.com/hey ...

Configuration: Description

KeyLocationDescription
Authorization Server  
request_url[oauth.json]OAuth 1.0a only, first step
authorize_url[oauth.json]OAuth 2.0 first step, OAuth 1.0a second step
access_url[oauth.json]OAuth 2.0 second step, OAuth 1.0a third step
oauth[oauth.json]OAuth version number
scope_delimiter[oauth.json]String delimiter used for concatenating multiple scopes
token_endpoint_auth_method[provider]Authentication method for the token endpoint
token_endpoint_auth_signing_alg[provider]Signing algorithm for the token endpoint
Client Server  
origindefaultsWhere your client server can be reached
prefixdefaultsPath prefix for the Grant internal routes
statedefaultsRandom state string for OAuth 2.0
noncedefaultsRandom nonce string for OpenID Connect
pkcedefaultsToggle PKCE support
responsedefaultsResponse data to receive
transportdefaultsA way to deliver the response data
callback[provider]Relative or absolute URL to receive the response data
overrides[provider]Static configuration overrides for a provider
dynamic[provider]Configuration keys that can be overridden dynamically over HTTP
Client App  
key client_id consumer_key[provider]The client_id or consumer_key of your OAuth app
secret client_secret consumer_secret[provider]The client_secret or consumer_secret of your OAuth app
scope[provider]List of scopes to request
custom_params[provider]Custom authorization parameters and their values
subdomain[provider]String to embed into the authorization server URLs
public_key[provider]Public PEM or JWK
private_key[provider]Private PEM or JWK
redirect_urigeneratedAbsolute redirect URL of the OAuth app
Grant  
namegeneratedProvider's name
[provider]generatedProvider's name as key
profile_url[profile.json]User profile URL

Configuration: Values

KeyLocationValue
Authorization Server  
request_url[oauth.json]'https://api.twitter.com/oauth/request_token'
authorize_url[oauth.json]'https://api.twitter.com/oauth/authenticate'
access_url[oauth.json]'https://api.twitter.com/oauth/access_token'
oauth[oauth.json]2 1
scope_delimiter[oauth.json]',' ' '
token_endpoint_auth_method[provider]'client_secret_post' 'client_secret_basic' 'private_key_jwt'
token_endpoint_auth_signing_alg[provider]'RS256' 'ES256' 'PS256'
Client Server  
origindefaults'http://localhost:3000' https://site.com
prefixdefaults'/connect' /oauth ''
statedefaultstrue
noncedefaultstrue
pkcedefaultstrue
responsedefaults['tokens', 'raw', 'jwt', 'profile']
transportdefaults'querystring' 'session' 'state'
callback[provider]'/hello' 'https://site.com/hi'
overrides[provider]{something: {scope: ['..']}}
dynamic[provider]['scope', 'subdomain']
Client App  
key client_id consumer_key[provider]'123'
secret client_secret consumer_secret[provider]'123'
scope[provider]['openid', '..']
custom_params[provider]{access_type: 'offline'}
subdomain[provider]'myorg'
public_key[provider]'..PEM..' '{..JWK..}'
private_key[provider]'..PEM..' '{..JWK..}'
redirect_urigenerated'http://localhost:3000/connect/twitter/callback'
Grant  
namegeneratedname: 'twitter'
[provider]generatedtwitter: true
profile_url[profile.json]'https://api.twitter.com/1.1/users/show.json'

Configuration: Scopes

Grant relies on configuration gathered from 6 different places:

The first place Grant looks for configuration is the built-in oauth.json file located in the config folder.

The second place Grant looks for configuration is the defaults key, specified in the user's configuration. These defaults are applied for every provider in the user's configuration.

The third place for configuration is the provider itself. All providers in the user's configuration inherit every option defined for them in the oauth.json file, and all options defined inside the defaults key. Having oauth.json file and a defaults configuration is only a convenience. You can define all available options directly for a provider.

The fourth place for configuration are the provider's overrides. The static overrides inherit their parent provider, essentially creating new provider of the same type.

The fifth place for configuration is the dynamic state override. The request/response lifecycle state of your HTTP framework of choice can be used to dynamically override configuration.

The sixth place for configuration, that potentially can override all of the above, and make all of the above optional, is the dynamic HTTP override.


Connect

Connect: Origin

The origin is where your client server can be reached:

{
  "defaults": {
    "origin": "http://localhost:3000"
  }
}

You login by navigating to the /connect/:provider route where :provider is a key in your configuration, usually one of the officially supported ones, but you can define your own as well. Additionally you can login through a static override defined for that provider by navigating to the /connect/:provider/:override? route.

Connect: Prefix

By default Grant operates on the following two routes:

/connect/:provider/:override?
/connect/:provider/callback

However, the default /connect prefix can be configured:

{
  "defaults": {
    "origin": "http://localhost:3000",
    "prefix": "/oauth"
  }
}

Connect: Redirect URI

The redirect_uri of your OAuth app should follow this format:

[origin][prefix]/[provider]/callback

Where origin and prefix have to match the ones set in your configuration, and provider is a provider key found in your configuration.

For example: http://localhost:3000/connect/google/callback

This redirect URI is used internally by Grant. Depending on the transport being used you will receive the response data in the callback route or absolute URL configured for that provider.

Connect: Custom Parameters

Some providers may employ custom authorization parameters that you can configure using the custom_params key:

{
  "google": {
    "custom_params": {"access_type": "offline", "prompt": "consent"}
  },
  "reddit": {
    "custom_params": {"duration": "permanent"}
  },
  "trello": {
    "custom_params": {"name": "my app", "expiration": "never"}
  }
}

Connect: OpenID Connect

The openid scope is required, and generating a random nonce string is optional but recommended:

{
  "google": {
    "scope": ["openid"],
    "nonce": true
  }
}

Grant does not verify the signature of the returned id_token by default.

However, the following two claims of the id_token are being validated:

  1. aud - is the token intended for my OAuth app?
  2. nonce - does it tie to a request of my own?

Connect: PKCE

PKCE can be enabled for all providers or for a specific provider only:

{
  "google": {
    "pkce": true
  }
}

Providers that do not support PKCE will ignore the additional parameters being sent.

Connect: Static Overrides

Provider sub configurations can be configured using the overrides key:

{
  "github": {
    "key": "...", "secret": "...",
    "scope": ["public_repo"],
    "callback": "/hello",
    "overrides": {
      "notifications": {
        "key": "...", "secret": "...",
        "scope": ["notifications"]
      },
      "all": {
        "scope": ["repo", "gist", "user"],
        "callback": "/hey"
      }
    }
  }
}

Navigate to:

  • /connect/github to request the public_repo scope
  • /connect/github/notifications to request the notifications scope using another OAuth App (key and secret)
  • /connect/github/all to request a bunch of scopes and also receive the response data in another callback route

Callback

Callback: Data

By default the response data will be returned in your callback route or absolute URL encoded as querystring.

Depending on the transport being used the response data can be returned in the session or in the state object instead.

The amount of the returned data can be controlled using the response configuration.

OAuth 2.0

{
  id_token: '...',
  access_token: '...',
  refresh_token: '...',
  raw: {
    id_token: '...',
    access_token: '...',
    refresh_token: '...',
    some: 'other data'
  }
}

The refresh_token is optional. The id_token is returned only for OpenID Connect providers requesting the openid scope.

OAuth 1.0a

{
  access_token: '...',
  access_secret: '...',
  raw: {
    oauth_token: '...',
    oauth_token_secret: '...',
    some: 'other data'
  }
}

Error

{
  error: {
    some: 'error data'
  }
}

Callback: Transport

querystring

By default Grant will encode the OAuth response data as querystring in your callback route or absolute URL:

{
  "github": {
    "callback": "https://site.com/hello"
  }
}

This is useful when using Grant as OAuth Proxy. However this final https://site.com/hello?access_token=... redirect may potentially leak private data in your server logs, especially when sitting behind a reverse proxy.

session

For local callback routes the session transport is recommended:

{
  "defaults": {
    "transport": "session"
  },
  "github": {
    "callback": "/hello"
  }
}

This will make the OAuth response data available in the session object instead:

req.session.grant.response // Express
ctx.session.grant.response // Koa
req.yar.get('grant').response // Hapi
req.session.grant.response // Fastify
(await session.get()).grant.response // Serverless Function

state

The request/response lifecycle state can be used as well:

{
  "defaults": {
    "transport": "state"
  }
}

In this case a callback route is not needed, and it will be ignored if provided. The response data will be available in the request/response lifecycle state object instead:

res.locals.grant.response // Express
ctx.state.grant.response // Koa
req.plugins.grant.response // Hapi
res.grant.response // Fastify
var {response} = await grant(...) // Serverless Function

Callback: Response

By default Grant returns all of the available tokens and the raw response data returned by the Authorization server:

{
  id_token: '...',
  access_token: '...',
  refresh_token: '...',
  raw: {
    id_token: '...',
    access_token: '...',
    refresh_token: '...',
    some: 'other data'
  }
}

querystring

When using the querystring transport it might be a good idea to limit the response data:

{
  "defaults": {
    "response": ["tokens"]
  }
}

This will return only the tokens available, without the raw response data.

This is useful when using Grant as OAuth Proxy. Encoding potentially large amounts of data as querystring can lead to incompatibility issues with some servers and browsers, and generally is considered a bad practice.

session

Using the session transport is generally safer, but it also depends on the implementation of your session store.

In case your session store encodes the entire session in a cookie, not just the session ID, some servers may reject the HTTP request because of HTTP headers size being too big.

{
  "google": {
    "response": ["tokens"]
  }
}

This will return only the tokens available, without the raw response data.

jwt

Grant can also return even larger response data by including the decoded JWT for OpenID Connect providers that return id_token:

{
  "google": {
    "response": ["tokens", "raw", "jwt"]
  }
}

This will make the decoded JWT available in the response data:

{
  id_token: '...',
  access_token: '...',
  refresh_token: '...',
  raw: {
    id_token: '...',
    access_token: '...',
    refresh_token: '...',
    some: 'other data'
  },
  jwt: {id_token: {header: {}, payload: {}, signature: '...'}}
}

Make sure you include all of the response keys that you want to be returned when configuring the response data explicitly.

profile

Outside of the regular OAuth flow, Grant can also request the user profile:

{
  "google": {
    "response": ["tokens", "profile"]
  }
}

Additionaly a profile key will be available in the response data:

{
  access_token: '...',
  refresh_token: '...',
  profile: {some: 'user data'}
}

The profile key contains either the raw response data returned by the user profile endpoint or an error message.

Not all of the supported providers have their profile_url set, and some of them might require custom parameters. Usually the user profile endpoint is accessible only when certain scopes were requested.

Callback: Session

Grant uses session to persist state between HTTP redirects occurring during the OAuth flow. This session, however, was never meant to be used as persistent storage, even though that's totally possible.

Once you receive the response data in your callback route you are free to destroy that session.

However, there are a few session keys returned in your callback route, that you may find useful:

KeyAvailabilityDescription
providerAlwaysThe provider name used for this authorization
overrideDepends on URLThe static override name used for this authorization
dynamicDepends on request typeThe dynamic override configuration passed to this authorization
stateOAuth 2.0 onlyOAuth 2.0 state string that was generated
nonceOpenID Connect onlyOpenID Connect nonce string that was generated
code_verifierPKCE onlyThe code verifier that was generated for PKCE
requestOAuth 1.0a onlyData returned from the first request of the OAuth 1.0a flow
responseDepends on transport usedThe final response data

Dynamic Configuration

Dynamic: Instance

Every Grant instance have a config property attached to it:

var grant = Grant(require('./config'))
console.log(grant.config)

You can use the config property to alter the Grant's behavior during runtime without having to restart your server.

This property contains the generated configuration used internally by Grant, and changes made to that configuration affects the entire Grant instance!

Dynamic: State

The request/response lifecycle state can be used to alter configuration on every request:

var state = {dynamic: {subdomain: 'usershop'}}
res.locals.grant = state // Express
ctx.state.grant = state // Koa
req.plugins.grant = state // Hapi
req.grant = state // Fastify
await grant(..., state) // Serverless Function

This is useful in cases when you want to configure Grant dynamically with potentially sensitive data that you don't want to send over HTTP.

The request/response lifecycle state is not controlled by the dynamic configuration, meaning that you can override any configuration key.

Any allowed dynamic configuration key sent through HTTP GET/POST request will override the identical one set using a state override.

Dynamic: HTTP

The dynamic configuration allows certain configuration keys to be set dynamically over HTTP GET/POST request.

For example shopify requires your shop name to be embedded into the OAuth URLs, so it makes sense to allow the subdomain configuration key to be set dynamically:

{
  "shopify": {
    "dynamic": ["subdomain"]
  }
}

Then you can have a web form on your website allowing the user to specify the shop name:

<form action="/connect/shopify" method="POST" accept-charset="utf-8">
  <input type="text" name="subdomain" value="" />
  <button>Login</button>
</form>

Making POST request to the /connect/:provider/:override? route requires a form body parser middleware:

.use(require('body-parser').urlencoded({extended: true})) // Express
.use(require('koa-bodyparser')()) // Koa
.register(require('fastify-formbody')) // Fastify

Alternatively you can make a GET request to the /connect/:provider/:override? route:

https://awesome.com/connect/shopify?subdomain=usershop

Any dynamic configuration sent over HTTP GET/POST request overrides any other configuration.

Dynamic: OAuth Proxy

In case you really want to, you can allow dynamic configuration override of every configuration key for a provider:

{
  "github": {
    "dynamic": true
  }
}

And the most extreme case is allowing even non preconfigured providers to be used dynamically:

{
  "defaults": {
    "dynamic": true
  }
}

Essentially Grant is a completely transparent OAuth Proxy.


Misc

Misc: Redirect URI

The origin and the prefix configuration is used to generate the correct redirect_uri that Grant expects:

{
  "defaults": {
    "origin": "https://mysite.com"
  },
  "google": {},
  "twitter": {}
}

The above configuration is identical to:

{
  "google": {
    "redirect_uri": "https://mysite.com/connect/google/callback"
  },
  "twitter": {
    "redirect_uri": "https://mysite.com/connect/twitter/callback"
  }
}

Explicitly specifying the redirect_uri overrides the one generated by default.

Misc: Custom Providers

You can define your own provider by adding a key for it in your configuration. In this case all of the required configuration keys have to be specified:

{
  "defaults": {
    "origin": "http://localhost:3000"
  },
  "awesome": {
    "authorize_url": "https://awesome.com/authorize",
    "access_url": "https://awesome.com/token",
    "oauth": 2,
    "key": "...",
    "secret": "...",
    "scope": ["read", "write"]
  }
}

Take a look at the oauth.json file on how various providers are being configured.

Misc: Meta Configuration

You can document your configuration by adding custom keys to it:

{
  "google": {
    "meta": {
      "app": "My Awesome OAuth App",
      "owner": "my_email@gmail.com",
      "url": "https://url/to/manage/oauth/app"
    }
  }
}

Note that meta is arbitrary key, but it cannot be one of the reserved keys.

Misc: Handler Constructors

Grant supports different ways of instantiation:

// Express or any other handler
var grant = require('grant').express()(config)
var grant = require('grant').express()({config, ...})
var grant = require('grant').express(config)
var grant = require('grant').express({config, ...})
var grant = require('grant')({handler: 'express', config, ...})

Using the new keyword is optional:

var Grant = require('grant').express()
var grant = Grant(config)
var grant = new Grant(config)

Additionally Hapi accepts the configuration in two different ways:

server.register([{plugin: grant(config)}])
server.register([{plugin: grant(), options: config}])

Misc: Path Prefix

You can mount Grant under specific path prefix:

// Express
app.use('/oauth', grant(config))
// Koa - using koa-mount
app.use(mount('/oauth', grant(config)))
// Hapi
server.register([{routes: {prefix: '/oauth'}, plugin: grant(config)}])
// Fastify
server.register(grant(config), {prefix: '/oauth'})

In this case the prefix configuration should reflect that + any other path parts that you may have:

{
  "defaults": {
    "origin": "http://localhost:3000",
    "prefix": "/oauth/login"
  }
}

In this case you login by navigating to: http://localhost:3000/oauth/login/:provider

And the redirect_uri of your OAuth app should be http://localhost:3000/oauth/login/:provider/callback

Optionally you can prefix your callback routes as well:

{
  "github": {
    "callback": "/oauth/login/hello"
  }
}

Misc: Request

The underlying HTTP client can be configured using the request option:

var grant = require('grant').express({
  config,
  request: {agent, timeout: 5000}
})

Fancy request logs are available too:

npm i --save-dev request-logs
DEBUG=req,res,json node app.js

Misc: ES Modules and TypeScript

Import Grant in your .mjs files:

import express from 'express'
import session from 'express-session'
import grant from 'grant'
import config from './config.json'

express()
  .use(session({}))
  .use(grant.express(config))

Importing a .json file may require additional flag:

node --experimental-json-modules app.mjs

Grant ships with extensive type definitions for TypeScript. Additonal type definitions and examples can be found here.

Misc: OAuth Quirks

Subdomain URLs

Some providers have dynamic URLs containing bits of user information embedded into them. Inside the main oauth.json configuration file such URLs contain a [subdomain] token embedded in them.

The subdomain option can be used to specify your company name, server region etc:

"shopify": {
  "subdomain": "mycompany"
},
"battlenet": {
  "subdomain": "us"
}

Then Grant will generate the correct OAuth URLs:

"shopify": {
  "authorize_url": "https://mycompany.myshopify.com/admin/oauth/authorize",
  "access_url": "https://mycompany.myshopify.com/admin/oauth/access_token"
},
"battlenet": {
  "authorize_url": "https://us.battle.net/oauth/authorize",
  "access_url": "https://us.battle.net/oauth/token"
}

Alternatively you can override the entire authorize_url and access_url in your configuration.

Sandbox OAuth URLs

Some providers may have Sandbox URLs to use while developing your app. To use them just override the entire request_url, authorize_url and access_url in your configuration (notice the sandbox bits):

"paypal": {
  "authorize_url": "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
  "access_url": "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice"
},
"evernote": {
  "request_url": "https://sandbox.evernote.com/oauth",
  "authorize_url": "https://sandbox.evernote.com/OAuth.action",
  "access_url": "https://sandbox.evernote.com/oauth"
}

Sandbox Redirect URI

Very rarely you may need to override the redirect_uri that Grant generates for you.

For example Feedly supports only http://localhost as redirect URI of their Sandbox OAuth app, and it won't allow the correct http://localhost/connect/feedly/callback URL:

"feedly": {
  "redirect_uri": "http://localhost"
}

In this case you'll have to redirect the user to the [origin][prefix]/[provider]/callback route that Grant uses to execute the last step of the OAuth flow:

var qs = require('querystring')

app.get('/', (req, res) => {
  if (process.env.NODE_ENV === 'development' &&
      req.session.grant &&
      req.session.grant.provider === 'feedly' &&
      req.query.code
  ) {
    res.redirect(`/connect/${req.session.grant.provider}/callback?${qs.stringify(req.query)}`)
  }
})

As usual you will receive the response data in your final callback route.

Provider Quirks

Ebay

Set the Redirect URI of your OAuth app as usual [origin][prefix]/[provider]/callback. Then Ebay will generate a special string called RuName (eBay Redirect URL name) that you need to set as redirect_uri in Grant:

"ebay": {
  "redirect_uri": "RUNAME"
}

Flickr, Freelancer, Optimizely

Some providers are using custom authorization parameter to pass the requested scopes - Flickr perms, Freelancer advanced_scopes, Optimizely scopes, but you can use the regular scope option instead:

"flickr": {
  "scope": ["write"]
},
"freelancer": {
  "scope": ["1", "2"]
},
"optimizely": {
  "scope": ["all"]
}

Mastodon

Mastodon requires the entire domain of your server to be embedded in the OAuth URLs. However you should use the subdomain option:

"mastodon": {
  "subdomain": "mastodon.cloud"
}

SurveyMonkey

Set your Mashery user name as key and your application key as api_key:

"surveymonkey": {
  "key": "MASHERY_USER_NAME",
  "secret": "CLIENT_SECRET",
  "custom_params": {"api_key": "CLIENT_ID"}
}

Twitter

Twitter OAuth 1.0a custom scope parameter can be specified in two ways:

"twitter": {
  "custom_params": {"x_auth_access_type": "read"}
}
"twitter": {
  "scope": ["read"]
}

Twitter OAuth 2.0 applications have to use the twitter2 provider:

"twitter2": {
  "state": true,
  "pkce": true,
  "scope": [
    "users.read",
    "tweet.read"
  ]
}

VisualStudio

Set your Client Secret as secret not the App Secret:

"visualstudio": {
  "key": "APP_ID",
  "secret": "CLIENT_SECRET instead of APP_SECRET"
}

Download Details:

Author: Simov
Source Code: https://github.com/simov/grant 
License: MIT license

#serverless #nodejs #javascript #aws #middleware #oauth

Grant: OAuth Proxy

How to Set Up a GitHub OAuth Application

In this tutorial, you'll learn how to set up a GitHub OAuth application. GitHub is an incredibly useful OAuth provider, especially if you are building an application targeted toward developers.

Create Your Application

Begin by navigating to your GitHub settings (make sure you are logged in!). Scroll down to the bottom of the sidebar and click "Developer Settings".

This will take you to the application page:

GitHub OAuth Apps view, showing a hacktoberfest and mattermost application that have been previously authorised.

You may see some applications you've previously authorised.

Click the "New OAuth App" button to create a new application.

The new OAuth application page, showing form fields for Application name, homepage URL, application description, and authorisation callback URL.

Fill in the form and click "Register application". This will create your application and take you to the settings page.

The Application settings page, which shows the same form fields as the previous form, with additional options to transfer ownership, revoke user tokens, generate client secrets, and upload a logo.

For OAuth applications, you will need the Client ID. You will also need to generate a client secret. Click the "Generate a new client secret" to do so.

The new client secret (obfuscated for security in this image)

Make sure to save this secret in a secure location as you will not be able to view it again.

Using Your New Application

Now that you have a client ID and secret, you can use your OAuth application in your project.

Original article source at https://www.freecodecamp.org

#github #oauth #security

How to Set Up a GitHub OAuth Application
Gordon  Taylor

Gordon Taylor

1661982300

Oauth-1.0a: OAuth 1.0a Request Authorization for Node and Browser

OAuth 1.0a Request Authorization

OAuth 1.0a Request Authorization for Node and Browser

Send OAuth request with your favorite HTTP client (request, jQuery.ajax...)

No more headache about OAuth 1.0a's stuff or "oauth_consumer_key, oauth_nonce, oauth_signature...." parameters, just use your familiar HTTP client to send OAuth requests.

Tested on some popular OAuth 1.0a services:

  • Twitter
  • Flickr
  • Bitbucket
  • Openbankproject(HMAC-SHA256)

Quick Start

const crypto = require('crypto')
const OAuth = require('oauth-1.0a')

const oauth = OAuth({
    consumer: { key: '<your consumer key>', secret: '<your consumer secret>' },
    signature_method: 'HMAC-SHA1',
    hash_function(base_string, key) {
        return crypto
            .createHmac('sha1', key)
            .update(base_string)
            .digest('base64')
    },
})

Get OAuth request data that can be easily used with your http client:

oauth.authorize(request, token)

Or if you want to get as a header key-value pair:

oauth.toHeader(oauth_data)

Crypto

Starting with version 2.0.0, crypto/hash stuff is separated. oauth-1.0a will use your hash_function to sign.

Example

Node.js

const crypto = require('crypto')

function hash_function_sha1(base_string, key) {
    return crypto
        .createHmac('sha1', key)
        .update(base_string)
        .digest('base64')
}

const oauth = OAuth({
    consumer: { key: '<your consumer key>', secret: '<your consumer secret>' },
    signature_method: 'HMAC-SHA1',
    hash_function: hash_function_sha1,
})
  • sha1: crypto.createHmac('sha1', key).update(base_string).digest('base64');
  • sha256: crypto.createHmac('sha256', key).update(base_string).digest('base64');

Browser

Using Google's CryptoJS

  • sha1: CryptoJS.HmacSHA1(base_string, key).toString(CryptoJS.enc.Base64);
  • sha256: CryptoJS.HmacSHA256(base_string, key).toString(CryptoJS.enc.Base64);

Installation

Node.js

$ npm install oauth-1.0a --production
  • You can use the native crypto package for hash_function.
  • It is possible for Node.js to be built without including support for the crypto module. In such cases, calling require('crypto') will result in an error being thrown.
  • You can use your own hash function which has format as:
function(base_string, key) return <string>

Browser

Download oauth-1.0a.js here

And also your crypto lib. For example CryptoJS

<!-- sha1 -->
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha1.js"></script>
<!-- sha256 -->
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha256.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js"></script>
<script src="oauth-1.0a.js"></script>

Example

Work with the request library (Node.js):

// Dependencies
const request = require('request')
const OAuth = require('oauth-1.0a')
const crypto = require('crypto')

// Initialize
const oauth = OAuth({
    consumer: {
        key: 'xvz1evFS4wEEPTGEFPHBog',
        secret: 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw',
    },
    signature_method: 'HMAC-SHA1',
    hash_function(base_string, key) {
        return crypto
            .createHmac('sha1', key)
            .update(base_string)
            .digest('base64')
    },
})

const request_data = {
    url: 'https://api.twitter.com/1/statuses/update.json?include_entities=true',
    method: 'POST',
    data: { status: 'Hello Ladies + Gentlemen, a signed OAuth request!' },
}

// Note: The token is optional for some requests
const token = {
    key: '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb',
    secret: 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE',
}

request(
    {
        url: request_data.url,
        method: request_data.method,
        form: oauth.authorize(request_data, token),
    },
    function(error, response, body) {
        // Process your data here
    }
)

Or if you want to send OAuth data in request's header:

request(
    {
        url: request_data.url,
        method: request_data.method,
        form: request_data.data,
        headers: oauth.toHeader(oauth.authorize(request_data, token)),
    },
    function(error, response, body) {
        // Process your data here
    }
)

Work with jQuery.ajax (Browser):

Caution: Please make sure you understand what happens when using OAuth protocol on the client side here

// Initialize
const oauth = OAuth({
    consumer: {
        key: 'xvz1evFS4wEEPTGEFPHBog',
        secret: 'kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw',
    },
    signature_method: 'HMAC-SHA1',
    hash_function(base_string, key) {
        return CryptoJS.HmacSHA1(base_string, key).toString(CryptoJS.enc.Base64)
    },
})

const request_data = {
    url: 'https://api.twitter.com/1/statuses/update.json?include_entities=true',
    method: 'POST',
    data: { status: 'Hello Ladies + Gentlemen, a signed OAuth request!' },
}

// Note: The token is optional for some requests
const token = {
    key: '370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb',
    secret: 'LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE',
}

$.ajax({
    url: request_data.url,
    type: request_data.method,
    data: oauth.authorize(request_data, token),
}).done(function(data) {
    // Process your data here
})

Or if you want to send OAuth data in request's header:

$.ajax({
    url: request_data.url,
    type: request_data.method,
    data: request_data.data,
    headers: oauth.toHeader(oauth.authorize(request_data, token)),
}).done(function(data) {
    // Process your data here
})

.authorize(/_ options _/)

  • url: String
  • method: String default 'GET'
  • data: Object any custom data you want to send with, including extra oauth option oauth_* as oauth_callback, oauth_version...
  • includeBodyHash: Boolean default false set to true if you want oauth_body_hash signing (you will need to have define the body_hash_function in most cases - for HMAC-SHA1 Oauth signature method, the body_hash_function should return a SHA1 hash).
const request_data = {
    url: 'https://bitbucket.org/api/1.0/oauth/request_token',
    method: 'POST',
    data: { oauth_callback: 'http://www.ddo.me' },
}

.toHeader(/_ signed data _/)

Convert signed data into headers:

$.ajax({
    url: request_data.url,
    type: request_data.method,
    data: request_data.data,
    headers: oauth.toHeader(oauth.authorize(request_data, token)),
}).done(function(data) {
    // Process your data here
})

Init Options

const oauth = OAuth(/* options */)
  • consumer: Object Required your consumer keys
{
  key: <your consumer key>,
  secret: <your consumer secret>
}
  • signature_method: String default 'PLAINTEXT'
  • hash_function: Function if signature_method = 'PLAINTEXT' default return key
  • body_hash_function: Function default to hash_function
  • nonce_length: Int default 32
  • version: String default '1.0'
  • parameter_seperator: String for header only, default ', '. Note that there is a space after ,
  • realm: String
  • last_ampersand: Bool default true. For some services if there is no Token Secret then no need & at the end. Check oauth doc for more information

oauth_signature is set to the concatenated encoded values of the Consumer Secret and Token Secret, separated by a '&' character (ASCII code 38), even if either secret is empty

Notes

  • Some OAuth requests without token use .authorize(request_data) instead of .authorize(request_data, {})
  • Or just token key only .authorize(request_data, {key: 'xxxxx'})
  • Want easier? Take a look:

Client Side Usage Caution

OAuth is based around allowing tools and websites to talk to each other. However, JavaScript running in web browsers is hampered by security restrictions that prevent code running on one website from accessing data stored or served on another.

Before you start hacking, make sure you understand the limitations posed by cross-domain XMLHttpRequest.

On the bright side, some platforms use JavaScript as their language, but enable the programmer to access other web sites. Examples include:

  • Google/Firefox/Safari extensions
  • Google Gadgets
  • Microsoft Sidebar

For those platforms, this library should come in handy.

Changelog

Contributors

Download Details:

Author: ddo
Source Code: https://github.com/ddo/oauth-1.0a 
License: MIT license

#javascript #oauth #node #browser 

Oauth-1.0a: OAuth 1.0a Request Authorization for Node and Browser
Reid  Rohan

Reid Rohan

1661820000

Oauth2orize: OAuth 2.0 Authorization Server toolkit for Node.js

OAuth2orize

OAuth2orize is an authorization server toolkit for Node.js. It provides a suite of middleware that, combined with Passport authentication strategies and application-specific route handlers, can be used to assemble a server that implements the OAuth 2.0 protocol.


Install

$ npm install oauth2orize

Usage

OAuth 2.0 defines an authorization framework, allowing an extensible set of authorization grants to be exchanged for access tokens. Implementations are free to choose what grant types to support, by using bundled middleware to support common types or plugins to support extension types.

Create an OAuth Server

Call createServer() to create a new OAuth 2.0 server. This instance exposes middleware that will be mounted in routes, as well as configuration options.

var server = oauth2orize.createServer();

Register Grants

A client must obtain permission from a user before it is issued an access token. This permission is known as a grant, the most common type of which is an authorization code.

server.grant(oauth2orize.grant.code(function(client, redirectURI, user, ares, done) {
  var code = utils.uid(16);

  var ac = new AuthorizationCode(code, client.id, redirectURI, user.id, ares.scope);
  ac.save(function(err) {
    if (err) { return done(err); }
    return done(null, code);
  });
}));

OAuth2orize also bundles support for implicit token grants.

Register Exchanges

After a client has obtained an authorization grant from the user, that grant can be exchanged for an access token.

server.exchange(oauth2orize.exchange.code(function(client, code, redirectURI, done) {
  AuthorizationCode.findOne(code, function(err, code) {
    if (err) { return done(err); }
    if (client.id !== code.clientId) { return done(null, false); }
    if (redirectURI !== code.redirectUri) { return done(null, false); }

    var token = utils.uid(256);
    var at = new AccessToken(token, code.userId, code.clientId, code.scope);
    at.save(function(err) {
      if (err) { return done(err); }
      return done(null, token);
    });
  });
}));

OAuth2orize also bundles support for password and client credential grants. Additionally, bundled refresh token support allows expired access tokens to be renewed.

Implement Authorization Endpoint

When a client requests authorization, it will redirect the user to an authorization endpoint. The server must authenticate the user and obtain their permission.

app.get('/dialog/authorize',
  login.ensureLoggedIn(),
  server.authorize(function(clientID, redirectURI, done) {
    Clients.findOne(clientID, function(err, client) {
      if (err) { return done(err); }
      if (!client) { return done(null, false); }
      if (client.redirectUri != redirectURI) { return done(null, false); }
      return done(null, client, client.redirectURI);
    });
  }),
  function(req, res) {
    res.render('dialog', { transactionID: req.oauth2.transactionID,
                           user: req.user, client: req.oauth2.client });
  });

In this example, connect-ensure-login middleware is being used to make sure a user is authenticated before authorization proceeds. At that point, the application renders a dialog asking the user to grant access. The resulting form submission is processed using decision middleware.

app.post('/dialog/authorize/decision',
   login.ensureLoggedIn(),
   server.decision());

Based on the grant type requested by the client, the appropriate grant module registered above will be invoked to issue an authorization code.

Session Serialization

Obtaining the user's authorization involves multiple request/response pairs. During this time, an OAuth 2.0 transaction will be serialized to the session. Client serialization functions are registered to customize this process, which will typically be as simple as serializing the client ID, and finding the client by ID when deserializing.

server.serializeClient(function(client, done) {
  return done(null, client.id);
});

server.deserializeClient(function(id, done) {
  Clients.findOne(id, function(err, client) {
    if (err) { return done(err); }
    return done(null, client);
  });
});

Implement Token Endpoint

Once a user has approved access, the authorization grant can be exchanged by the client for an access token.

app.post('/token',
  passport.authenticate(['basic', 'oauth2-client-password'], { session: false }),
  server.token(),
  server.errorHandler());

Passport strategies are used to authenticate the client, in this case using either an HTTP Basic authentication header (as provided by passport-http) or client credentials in the request body (as provided by passport-oauth2-client-password).

Based on the grant type issued to the client, the appropriate exchange module registered above will be invoked to issue an access token. If an error occurs, errorHandler middleware will format an error response.

Implement API Endpoints

Once an access token has been issued, a client will use it to make API requests on behalf of the user.

app.get('/api/userinfo', 
  passport.authenticate('bearer', { session: false }),
  function(req, res) {
    res.json(req.user);
  });

In this example, bearer tokens are issued, which are then authenticated using an HTTP Bearer authentication header (as provided by passport-http-bearer)


Advertisement 
Node.js API Masterclass With Express & MongoDB
Create a real world backend for a bootcamp directory app


Examples

This example demonstrates how to implement an OAuth service provider, complete with protected API access.

Related Modules

Debugging

oauth2orize uses the debug module. You can enable debugging messages on the console by doing export DEBUG=oauth2orize before running your application.

Download Details:

Author: jaredhanson
Source Code: https://github.com/jaredhanson/oauth2orize 
License: MIT license

#javascript #oauth #node 

Oauth2orize: OAuth 2.0 Authorization Server toolkit for Node.js

A Complete Module Implement OAuth2 Server in Node.js

oauth2-server

Complete, compliant and well tested module for implementing an OAuth2 server in Node.js.

Note: After a period of hiatus, this project is now back under active maintenance. Dependencies have been updated and bug fixes will land in v3 (current master). v4 will be mostly backwards compatible with no code changes required for users using a supported node release. More details in #621.

Installation

npm install oauth2-server

The oauth2-server module is framework-agnostic but there are several officially supported wrappers available for popular HTTP server frameworks such as Express and Koa. If you're using one of those frameworks it is strongly recommended to use the respective wrapper module instead of rolling your own.

Features

  • Supports authorization_code, client_credentials, refresh_token and password grant, as well as extension grants, with scopes.
  • Can be used with promises, Node-style callbacks, ES6 generators and async/await (using Babel).
  • Fully RFC 6749 and RFC 6750 compliant.
  • Implicitly supports any form of storage, e.g. PostgreSQL, MySQL, MongoDB, Redis, etc.
  • Complete test suite.

Documentation

Documentation is hosted on Read the Docs.

Examples

Most users should refer to our Express or Koa examples.

More examples can be found here: https://github.com/14gasher/oauth-example

Upgrading from 2.x

This module has been rewritten using a promise-based approach, introducing changes to the API and model specification. v2.x is no longer supported.

Please refer to our 3.0 migration guide for more information.

Tests

To run the test suite, install dependencies, then run npm test:

npm install
npm test

package.json

{
  "name": "oauth2-server",
  "description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js",
  "version": "4.0.0-dev.3",
  "keywords": [
    "oauth",
    "oauth2"
  ],
  "contributors": [
    {
      "name": "Thom Seddon",
      "email": "thom@seddonmedia.co.uk"
    },
    {
      "name": "Lars F. Karlström",
      "email": "lars@lfk.io"
    },
    {
      "name": "Rui Marinho",
      "email": "ruipmarinho@gmail.com"
    },
    {
      "name": "Tiago Ribeiro",
      "email": "tiago.ribeiro@gmail.com"
    },
    {
      "name": "Michael Salinger",
      "email": "mjsalinger@gmail.com"
    },
    {
      "name": "Nuno Sousa"
    },
    {
      "name": "Max Truxa"
    }
  ],
  "main": "index.js",
  "dependencies": {
    "basic-auth": "2.0.1",
    "bluebird": "3.7.2",
    "lodash": "4.17.21",
    "promisify-any": "2.0.1",
    "statuses": "1.5.0",
    "type-is": "1.6.18"
  },
  "devDependencies": {
    "jshint": "2.13.0",
    "mocha": "5.2.0",
    "should": "13.2.3",
    "sinon": "7.5.0"
  },
  "license": "MIT",
  "engines": {
    "node": ">=4.0"
  },
  "scripts": {
    "pretest": "./node_modules/.bin/jshint --config ./.jshintrc lib test",
    "test": "NODE_ENV=test ./node_modules/.bin/mocha 'test/**/*_test.js'",
    "test-debug": "NODE_ENV=test ./node_modules/.bin/mocha  --inspect --debug-brk 'test/**/*_test.js'"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/oauthjs/node-oauth2-server.git"
  }
}

Download Details:
 

Author: oauthjs
Download Link: Download The Source Code
Official Website: https://github.com/oauthjs/node-oauth2-server 
License: MIT license

#nodejs #oauth #javascript 

A Complete Module Implement OAuth2 Server in Node.js

Next-auth-mui: Sign-in Dialog for NextAuth Built with MUI and React

next-auth-mui

Sign-in dialog for NextAuth built with MUI and React. Detects configured OAuth and Email providers and renders buttons or input fields for each respectively. Fully themeable, extensible and customizable to support custom credential flows.

Storybook - Examples

Getting started

Install

npm install next-auth-mui

yarn add next-auth-mui

❗Note: NextAuth needs to be configured and MUI has to be installed in your project.

Usage

Simply render the <NextAuthDialog /> component in your app. This component will automatically detect the configured providers by sending a request to the /api/auth/providers endpoint and render the appropriate sign-in buttons or input fields.

import React from 'react';
import { Button } from '@mui/material';
import NextAuthDialog from 'next-auth-mui';

const Example = () => {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <NextAuthDialog open={open} onClose={() => setOpen(false)} />

      <Button onClick={() => setOpen(true)}>
        Sign-in
      </Button>
    </>
  );
}

Customization

Components rendered within the next-auth-mui dialog are customizable through passing the standard props supported by the respective @mui/material components.

If you need to implement custom logic for fetching providers or want complete control over the sign-in dialog, you can also import the AuthDialog component.

import { AuthDialog } from 'next-auth-mui';

Custom email authentication

A common use-case is using a 3rd party email authentication service to send magic links alongside NextAuth's OAuth providers. In this case you can implement a custom email submit handler.

In the example below we're using magic.link to send emails and a custom credentials provider to authenticate the user.

Additionally, a custom email validator is included to only allow emails that end in @gmail.com.

import NextAuthDialog, { isValidEmail } from 'next-auth-mui';

<NextAuthDialog
  open
  {/* Render email field even if there is no email provider configured */}
  alwaysShowEmailField
  emailValidator={(email) => isValidEmail(email) && email.endsWith('@gmail.com')}
  {/* Custom email submit handler */}
  onSubmitEmail={async (email) => {
    // Send magic link
    const didToken = await magic.auth.loginWithMagicLink({ email });

    // sign in with NextAuth
    await signIn(`credentials`, {
      didToken,
      callbackUrl: router.query[`callbackUrl`]
    });
  }}
/>

All options

export type NextAuthDialogProps = AuthDialogProps & {
  /**
   * Disable sorting of providers by name when rendering their buttons.
   */
  disableSortByName?: boolean;
  /**
   * The endpoint of NextAuth server. Defaults to `/api/auth/providers`.
   */
  url?: string;
}

export type AuthDialogProps = PropsWithChildren<{
  /**
   * See @mui/material documentation
   */
  ButtonProps?: ButtonProps,
  /**
   * See @mui/material documentation
   */
  ButtonTypographyProps?: TypographyProps,
  /**
   * See @mui/material documentation
   */
  DialogContentProps?: DialogContentProps;
  /**
   * See @mui/material documentation
   */
  DialogContentTextProps?: DialogContentTextProps
  /**
   * See @mui/material documentation
   */
  DialogProps?: DialogProps;
  /**
   * See @mui/material documentation
   */
  DialogTitleProps?: DialogTitleProps;
  /**
   * Props passed to the email input field. See @mui/material documentation
   */
  EmailFieldProps?: TextFieldProps;
  /**
   * Props to pass to the default loading indicator. See @mui/material documentation
   */
  LinearProgressProps?: LinearProgressProps;
  /**
   * A custom loading indicator.
   */
  Progress?: React.ReactNode;
  /**
   * Always show the email field regardless if email provider has been configured.
   * This is useful for implementing email auth with a 3rd party api.
   */
  alwaysShowEmailField?: boolean;
  /**
   * Controls width of dialog.
   * When breakpoint >= viewport the dialog will be rendered in mobile mode.
   * Defaults to `xs`.
   */
  breakpoint?: Breakpoint,
  /**
   * Disable autofocus of email field.
   */
  disableEmailAutoFocus?: boolean;
  /**
   * Text to display between email field and oauth buttons. Defaults to "or".
   */
  dividerText?: string | React.ReactNode;
  /**
   * Render error text instead of providers
   */
  error?: string,
  /**
   * Hide the provider icons on their buttons.
   */
  hideProviderIcon?: boolean;
  /**
   * Hide the provider names on their buttons.
   */
  hideProviderName?: boolean;
  /**
   * Hide the dialog title. In mobile mode this will hide the close "x" icon.
   */
  hideTitle?: boolean;
  /**
   * Custom email validation function.
   */
  isValidEmail?: (email: string) => boolean | Promise<boolean>;
  /**
   * If true a loading indicator will be displayed in the dialog.
   */
  loading?: boolean;
  /**
   * Callback for closing the dialog.
   */
  onClose?: () => void;
  /**
   * Callback runs on a failed sign in.
   */
  onOAuthSignInError?: (error: Error) => void;
  /**
   * Callback runs on a successful sign in.
   */
  onOAuthSignInSuccess?: (response: SignInResponse | undefined) => void;
  /**
   * Override default email submission function.
   * This is useful for implementing authentication with a 3rd party API like MagicLink.
   */
  onSubmitEmail?: (email: string) => Promise<void>;
  /**
   * When true the dialog will be open.
   */
  open: boolean,
  /**
   * An object mapping of provider id to provider config.
   */
  providers?: Record<string, ProviderConfig>;
  /**
   * Additional sign in options to be passed when calling `signIn`.  See next-auth for documentation
   */
  signInOptions?: SignInOptions;
  /**
   * Text to display in the dialog title. Empty by default.
   */
  titleText?: string | React.ReactNode;
}>

export type OauthProviderConfig = {
  /**
   * Override props passed to provider's button. See @mui/material documentation.
   */
  ButtonProps?: ButtonProps
  /**
   * Override props passed to provider's button's typography. See @mui/material documentation.
   */
  ButtonTypographyProps?: TypographyProps
  /**
   * Override props passed to provider's icon. See @iconify/react documentation.
   */
  IconProps?: IconProps;
  /**
   * Hide the provider icon on button.
   */
  hideProviderIcon?: boolean;
  /**
   * Hide the provider names button.
   */
  hideProviderName?: boolean;
  /**
   * Override the provider's icon. Can be a @iconify/react icon name or a custom component.
   */
  icon?: string | React.ReactNode;
  /**
   * Override the provider's name when rendering the button.
   */
  label?: string;
}

export type EmailProviderConfig = {
  /**
   * Override props passed to the email's input field. See @mui/material documentation.
   */
  EmailFieldProps?: TextFieldProps,
  /**
   * Override end icon rendered in the email input field
   */
  endIcon?: React.ReactNode;
  /**
   * Override text rendered below the email input field.
   */
  helperText?: string | React.ReactNode;
  /**
   * Override the placeholder text rendered in the email input field.
   */
  placeholder?: string;
  /**
   * Override start icon rendered in the email input field
   */
  startIcon?: React.ReactNode;
}

export type ProviderConfig = OauthProviderConfig & EmailProviderConfig & {
  /**
   * ID of the provider.
   */
  id: string;
  /**
   * Name of the provider. Will be used as the button's label and used when sorting providers.
   */
  name: string;
  /**
   * Override sign in options to be passed when calling `signIn`.  See next-auth for documentation
   */
  signInOptions?: SignInOptions;
  /**
   * Type of the provider.
   * Only `email` and `oauth` are supported, all other types will be ignored when rendering fields.
   */
  type: 'oauth' | 'email' | string;
};

Download details:

Author: TimMikeladze
Source code: https://github.com/TimMikeladze/next-auth-mui
License: MIT license

#nextjs #react #reactjs #javascript #auth #oauth #typescript

Next-auth-mui: Sign-in Dialog for NextAuth Built with MUI and React
Joseph  Murray

Joseph Murray

1660844580

SvelteKit Auth: Authentication library for use with SvelteKit

SvelteKitAuth

Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!

Installation

SvelteKitAuth is available on NPM as sk-auth, it can be installed with NPM:

npm i sk-auth --save

Or Yarn:

yarn add sk-auth

Usage with Typescript

SvelteKitAuth also comes with first-class support for Typescript out of the box, so no need to add an additional @types/ dev dependency! 🎉

Getting Started

SvelteKitAuth is very easy to setup! All you need to do is instantiate the SvelteKitAuth class, and configure it with some default providers, as well as a JWT secret key used to verify the cookies:

Warning: env variables prefixed with VITE_ can be exposed and leaked into client-side bundles if they are referenced in any client-side code. Make sure this is not the case, or consider using an alternative method such as loading them via dotenv directly instead.

export const appAuth = new SvelteKitAuth({
  providers: [
    new GoogleOAuthProvider({
      clientId: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_ID,
      clientSecret: import.meta.env.VITE_GOOGLE_OAUTH_CLIENT_SECRET,
      profile(profile) {
        return { ...profile, provider: "google" };
      },
    }),
  ],
  jwtSecret: import.meta.env.JWT_SECRET_KEY,
});

If you want to override or augment the default SvelteKit session to get access to the user in the session store, you can use the getSession hook:

// overriding the default session
export const { getSession } = appAuth;

// augmenting it
export const getSession: GetSession = async (request) => {
  const { user } = await appAuth.getSession(request);

  return { user };
};

Callbacks

SvelteKitAuth provides some callbacks, similar to NextAuth.js. Their call signatures are:

interface AuthCallbacks {
  signIn?: () => boolean | Promise<boolean>;
  jwt?: (token: JWT, profile?: any) => JWT | Promise<JWT>;
  session?: (token: JWT, session: Session) => Session | Promise<Session>;
  redirect?: (url: string) => string | Promise<string>;
}

Adding more Providers

SvelteKitAuth uses a object-oriented approach towards creating providers. It is unopionated and allows you to implement any three-legged authentication flow such as OAuth, SAML SSO, and even regular credential logins by omitting the signin() route.

You can implement your own using the Provider base provider class, and by implementing the signin() and callback() methods:

export abstract class Provider<T extends ProviderConfig = ProviderConfig> {
  abstract signin<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
    request: ServerRequest<Locals, Body>,
  ): EndpointOutput | Promise<EndpointOutput>;

  abstract callback<Locals extends Record<string, any> = Record<string, any>, Body = unknown>(
    request: ServerRequest<Locals, Body>,
  ): CallbackResult | Promise<CallbackResult>;
}

signin() must return a generic endpoint output, this can be a redirect, or the path to the provider's sign-in page. When implementing a HTTP POST route, signin() can simply return an empty body and callback() should handle the user login flow.

callback() takes a ServerRequest and must return a CallbackResult which is a custom type exported by svelte-kit-auth:

export type Profile = any;
export type CallbackResult = [Profile, string | null];

The first item in the tuple is the user profile, which gets stored in the token, and is provided to the jwt() callback as the second argument. The second item is a redirect route, which may be tracked using the state query parameter for OAuth providers, or other implementations depending on the sign-in method.

OAuth2

SvelteKitAuth comes with a built-in OAuth2 provider that takes extensive configuration parameters to support almost any common OAuth2 provider which follows the OAuth2 spec. It can be imported from sk-auth/providers and configured with the following configuration object:

export interface OAuth2ProviderConfig<ProfileType = any, TokensType extends OAuth2Tokens = any>
  extends OAuth2BaseProviderConfig<ProfileType, TokensType> {
  accessTokenUrl?: string;
  authorizationUrl?: string;
  profileUrl?: string;
  clientId?: string;
  clientSecret?: string;
  scope: string | string[];
  headers?: any;
  authorizationParams?: any;
  params: any;
  grantType?: string;
  responseType?: string;
  contentType?: "application/json" | "application/x-www-form-urlencoded";
}

Some values have defaults which can be seen below:

const defaultConfig: Partial<OAuth2ProviderConfig> = {
  responseType: "code",
  grantType: "authorization_code",
  contentType: "application/json",
};

The OAuth2Provider class can then be instantiated with the configuration to support the OAuth2 flow, including authorization redirect, token retrieval and profile fetching. It will also automatically handle the state and nonce params for you.

Motivation

SvelteKitAuth is inspired by the NextAuth.js package built for the Next.js SSR framework for React. Unlike NextAuth.js it is completely unopinionated and only provides implementations for default flows, while still empowering users to add their own providers.

As it leverages classes and Typescript, the implementation of such providers is very straightforward, and in the future it will even be possible to register multiple SvelteKitAuth handlers in the same project, should the need arise, by leveraging a class-based client and server setup.

Examples

Looking for help? Check out the example app in the repository source. Make something cool you want to show off? Share it with others in the discussion section.

Contributing

🚧 Work in Progress!

Download details:

Author: Dan6erbond
Source code: https://github.com/Dan6erbond/sk-auth
License: MIT license

#svetle #javascript #auth #oauth 

SvelteKit Auth: Authentication library for use with SvelteKit
Hunter  Krajcik

Hunter Krajcik

1659705540

Oauth_webauth: A Plugin Provides an Alternative to AppAuth Use WebView

OAuth WebAuth

This plugin offers a WebView implementation approach for OAuth authorization/authentication with identity providers.

Compatibility

PlatformCompatibility
Android
iOS
WebWork in progress

Preamble

Other plugins like Flutter AppAuth uses native implementation of AppAuth which in turn uses SFAuthenticationSession and CustomTabs for iOS and Android respectively. When using SFAuthenticationSession and CustomTabs your app will/could present some problems like:

  • UI: users will notice a breaking UI difference when system browser opens to handle the identity provider authentication process.
  • In iOS an annoying system dialog shows up every time the user tries to authenticate, indicating that the current app and browser could share their information.
  • Your system browser cache is shared with your app which is good and bad, bad because any cache problem due to your every day navigation use could affect your app authentication and the only way to clean cache it's by cleaning system browser cache at operating system level.
  • Authentication page will use the locale from System Browser which in fact uses the Operating System locale, this means if your app uses different language than the Operating System then authentication page will show different internationalization than your app.

Features

With this plugin you will get:

  • Full control over the UI, WebView will run inside your app so Theme and Color Scheme will be yours to choose, in fact you can add AppBar or FloatingActionButton or whatever you thinks it's necessary to your UI.
  • No system dialog will be shown when users tries to authenticate.
  • Users will not be affected by any system browser problem cache and also will be able to clean app browser cache from the authentication screen itself.
  • Authentication page locale can be set from app using the contentLocale property to ensure the same locale. By default Operating System locale will be used if no contentLocale is specified.

Note:

  • contentLocale will apply only if the authentication page supports the specified Locale('...') and accepts the header: 'Accept-Language': 'es-ES'

Migration from ^1.0.0 to ^2.0.0

  • Static constants key for tooltips, message and hero tags were moved from OAuthWebView to BaseWebView
  • OAuthWebView renamed onSuccess function to onSuccessAuth.

Getting started

As stated before this plugin uses WebView implementation specifically the plugin flutter_inappwebview. For any WebView related problem please check the documentation of that plugin at docs.

Android setup

Just add the internet permission to your AndroidManifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.example">  
    <uses-permission android:name="android.permission.INTERNET"/>  
 <application>  
    ...
 </application>

iOS setup

Just add this to your Info.plist

<plist version="1.0">  
<dict>
    ....
    ....
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
    ....
    ....
</dict>  
</plist>

Usage

  • This plugin offers a widget OAuthWebView which handles all the authorization/authentication and navigation logic; this widget can be used in any widget tree of your current app or as an individual authentication screen. For individual authentication screen it offers the widget OAuthWebScreen which can be started as a new route and also handles the Android back button to navigate backward when applies.
  • In addition, this plugin offers a simple widget BaseWebView which may be useful for cases in which you need to handle a link to your Auth server, let's say for email confirmation, or password reset, etc. This widget will handle the web UI and automatically get back to you when loaded any of the specified redirectUrls. The BaseWebView widget works very similar to OAuthWebView, it can be used in any widget tree of your current app or as an individual screen. For individual screen it offers the widget BaseWebScreen which can be started as a new route and also handles the Android back button to navigate backward when applies.

NOTE: all widgets can be extended to change/improve its features.

OAuthWebView

An authorization/authentication process can get 3 outputs.

  1. User successfully authenticates, which returns an Oauth2 Credentials object with the access-token and refresh-token.
  2. An error occurred during authorization/authentication, or maybe certificate validation failed.
  3. User canceled authentication.

This plugin offers two variants to handle these outputs.

Variant 1

Awaiting response from navigator route Future.

void loginV1() async {
  final result = await OAuthWebScreen.start(
      context: context,
      authorizationEndpointUrl: authorizationEndpointUrl,
      tokenEndpointUrl: tokenEndpointUrl,
      clientSecret: clientSecret,
      clientId: clientId,
      redirectUrl: redirectUrl,
      baseUrl: baseUrl, //Optional, if you want to get back to the application when the web view gets redirected to baseUrl. This will be like an onCancel callback
      scopes: scopes,
      promptValues: const ['login'],
      loginHint: 'johndoe@mail.com',
      onCertificateValidate: (certificate) {
        ///This is recommended
        /// Do certificate validations here
        /// If false is returned then a CertificateException() will be thrown
        return true;
      },
      contentLocale: Locale('es'),
      refreshBtnVisible: false,
      clearCacheBtnVisible: false,
      textLocales: {
        ///Optionally texts can be localized
        OAuthWebView.backButtonTooltipKey: 'Ir atrás',
        OAuthWebView.forwardButtonTooltipKey: 'Ir adelante',
        OAuthWebView.reloadButtonTooltipKey: 'Recargar',
        OAuthWebView.clearCacheButtonTooltipKey: 'Limpiar caché',
        OAuthWebView.closeButtonTooltipKey: 'Cerrar',
        OAuthWebView.clearCacheWarningMessageKey:
        '¿Está seguro que desea limpiar la caché?',
      });
  if (result != null) {
    if (result is Credentials) {
      authResponse = getPrettyCredentialsJson(result);
    } else {
      authResponse = result.toString();
    }
  } else {
    authResponse = 'User cancelled authentication';
  }
  setState(() {});
}

Variant2

Using callbacks

void loginV2() {
 OAuthWebScreen.start(
     context: context,
     authorizationEndpointUrl: authorizationEndpointUrl,
     tokenEndpointUrl: tokenEndpointUrl,
     clientSecret: clientSecret,
     clientId: clientId,
     redirectUrl: redirectUrl,
     baseUrl: baseUrl, //Optional, if you want to get back to the application when the web view gets redirected to baseUrl. This will be like an onCancel callback
     scopes: scopes,
     promptValues: const ['login'],
     loginHint: 'johndoe@mail.com',
     onCertificateValidate: (certificate) {
       ///This is recommended
       /// Do certificate validations here
       /// If false is returned then a CertificateException() will be thrown
       return true;
     },
     contentLocale: Locale('es'),
     refreshBtnVisible: false,
     clearCacheBtnVisible: false,
     textLocales: {
       ///Optionally text can be localized
       OAuthWebView.backButtonTooltipKey: 'Ir atrás',
       OAuthWebView.forwardButtonTooltipKey: 'Ir adelante',
       OAuthWebView.reloadButtonTooltipKey: 'Recargar',
       OAuthWebView.clearCacheButtonTooltipKey: 'Limpiar caché',
       OAuthWebView.closeButtonTooltipKey: 'Cerrar',
       OAuthWebView.clearCacheWarningMessageKey:
       '¿Está seguro que desea limpiar la caché?',
     },
     onSuccess: (credentials) {
       setState(() {
         authResponse = getPrettyCredentialsJson(credentials);
       });
     },
     onError: (error) {
       setState(() {
         authResponse = error.toString();
       });
     },
     onCancel: () {
       setState(() {
         authResponse = 'User cancelled authentication';
       });
     });
}

BaseWebView

3 possible outputs.

  1. User is successfully redirected, which returns the full redirect url.
  2. An error occurred during navigation.
  3. User canceled web view.

This plugin offers two variants to handle these outputs.

Variant 1

Awaiting response from navigator route Future.

void baseRedirectV1() async {
  final result = await BaseWebScreen.start(
    context: context,
    initialUrl: initialUrl,
    redirectUrls: [redirectUrl, baseUrl],
    onCertificateValidate: (certificate) {
      ///This is recommended
      /// Do certificate validations here
      /// If false is returned then a CertificateException() will be thrown
      return true;
    },
    textLocales: {
      ///Optionally texts can be localized
      BaseWebView.backButtonTooltipKey: 'Ir atrás',
      BaseWebView.forwardButtonTooltipKey: 'Ir adelante',
      BaseWebView.reloadButtonTooltipKey: 'Recargar',
      BaseWebView.clearCacheButtonTooltipKey: 'Limpiar caché',
      BaseWebView.closeButtonTooltipKey: 'Cerrar',
      BaseWebView.clearCacheWarningMessageKey:
      '¿Está seguro que desea limpiar la caché?',
    },
    contentLocale: contentLocale,
    refreshBtnVisible: false,
    clearCacheBtnVisible: false,
  );
  if (result != null) {
    if (result is String) {
      /// If result is String it means redirected successful
      response = 'User redirected to: $result';
    } else {
      /// If result is not String then some error occurred
      response = result.toString();
    }
  } else {
    /// If no result means user cancelled
    response = 'User cancelled';
  }
  setState(() {});
}

Variant2

Using callbacks

void baseRedirectV2() {
 BaseWebScreen.start(
     context: context,
     initialUrl: initialUrl,
     redirectUrls: [redirectUrl, baseUrl],
     onCertificateValidate: (certificate) {
       ///This is recommended
       /// Do certificate validations here
       /// If false is returned then a CertificateException() will be thrown
       return true;
     },
     textLocales: {
       ///Optionally text can be localized
       BaseWebView.backButtonTooltipKey: 'Ir atrás',
       BaseWebView.forwardButtonTooltipKey: 'Ir adelante',
       BaseWebView.reloadButtonTooltipKey: 'Recargar',
       BaseWebView.clearCacheButtonTooltipKey: 'Limpiar caché',
       BaseWebView.closeButtonTooltipKey: 'Cerrar',
       BaseWebView.clearCacheWarningMessageKey:
       '¿Está seguro que desea limpiar la caché?',
     },
     contentLocale: contentLocale,
     refreshBtnVisible: false,
     clearCacheBtnVisible: false,
     onSuccess: () {
       setState(() {
         response = 'User redirected';
       });
     },
     onError: (error) {
       setState(() {
         response = error.toString();
       });
     },
     onCancel: () {
       setState(() {
         response = 'User cancelled';
       });
     });
}

Important notes

  • goBackBtnVisible, goForwardBtnVisible, refreshBtnVisible, clearCacheBtnVisible, closeBtnVisible allows you to show/hide buttons from toolbar, if you want to completely hide toolbar, set all buttons to false.
  • Use urlStream when you need to asynchronously navigate to a specific url, like when user registered using OauthWebAuth and the web view waits for user email verification; in this case when the user opens the email verification link you can navigate to this link by emitting the new url to the stream you previously set in the urlStream instead of creating a new OautHWebAuth or BaseWebView.
  • For more details on how to use check the sample project of this plugin.

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add oauth_webauth

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  oauth_webauth: ^2.2.0+9

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:oauth_webauth/oauth_webauth.dart';

example/lib/main.dart

import 'package:example/src/auth_sample_screen.dart';
import 'package:example/src/base_redirect_sample_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Oauth WebAuth samples'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        systemOverlayStyle: SystemUiOverlayStyle.dark,
      ),
      body: Center(
        child: SingleChildScrollView(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => const AuthSampleScreen()));
                },
                child: const Text('Oauth login samples'),
                style: ButtonStyle(
                    backgroundColor: MaterialStateProperty.all(Colors.green)),
              ),
              const SizedBox(height: 4),
              ElevatedButton(
                onPressed: () {
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) =>
                              const BaseRedirectSampleScreen()));
                },
                child: const Text('Base redirect samples'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Author: luis901101
Source Code: https://github.com/luis901101/oauth_webauth 
License: BSD-3-Clause license

#flutter #dart #oauth #web 

Oauth_webauth: A Plugin Provides an Alternative to AppAuth Use WebView
Thierry  Perret

Thierry Perret

1659032700

Ajoutez Auth à N'importe Quelle Application Avec Le Proxy OAuth2

La mise à jour d'une application pour utiliser OAuth 2.0 n'a pas besoin d'être compliquée. La plupart du temps, votre langage ou framework aura déjà une bibliothèque OAuth. Parfois, ce n'est pas le cas et vous devez trouver une alternative. Dans cet article, je vais vous expliquer comment configurer et utiliser le proxy OAuth2 pour sécuriser votre application sans aucune modification de code !

OAuth2 Proxy est un proxy inverse qui se trouve devant votre application et gère les complexités d'OpenID Connect / OAuth 2.0 pour vous ; les demandes qui parviennent à votre candidature ont déjà été autorisées !

Schéma d'utilisation de base du proxy OAuth2

Conditions préalables

Une application sans sécurité

Tout d'abord, nous avons besoin d'une application. Vous pouvez utiliser n'importe quelle application Web, mais pour cet article, je m'en tiendrai à une application Java Spring Boot qui fera écho aux détails de la requête HTTP entrante. Faire écho aux informations de la demande aidera à visualiser les en-têtes HTTP supplémentaires ajoutés par le proxy OAuth2, tels que l'adresse e-mail de l'utilisateur.

Vous pouvez récupérer le projet depuis GitHub :

git clone https://github.com/oktadev/okta-oauth2-proxy-example.git -b start
cd okta-oauth2-proxy-example
 Si vous voulez voir le projet terminé, consultez mainplutôt la branche.

Si vous êtes intéressé par les détails de cette application Java, jetez un œil à la EchoApplicationclasse. Il contient un point de terminaison unique qui gérera toutes les requêtes et "videra" le contenu de la requête en tant qu'objet JSON :

@RestController
static class EchoRestController {
    @RequestMapping("/**")
    Map<String, Object> echo(HttpServletRequest request,
                             @RequestHeader HttpHeaders headers,
                             @RequestBody(required = false) Map<String, Object> body) {

        Instant now = Instant.now();
        Cookie[] cookies = request.getCookies();

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("clientIpAddress", request.getRemoteAddr());
        result.put("cookies", cookies == null ? emptyList()
                                              : Arrays.stream(cookies).toList());
        result.put("headers", headers.toSingleValueMap()); // simplify json response
        result.put("httpVersion", request.getProtocol());
        result.put("method", request.getMethod());
        result.put("body", body);
        result.put("queryString", request.getQueryString());
        result.put("startedDateTime", now);
        result.put("url", request.getRequestURL());

        return result;
    }
}

Si vous êtes un fan de Java, vous pouvez démarrer l'application en exécutant ./mvnw spring-boot:run. Tout le monde peut exécuter les commandes Docker suivantes :

docker build --tag echo-app .
docker run -p 8080:8080 echo-app

Une fois l'application lancée, ouvrez votre navigateur ou accédez à l'application web dans votre terminal avec HTTPie :

http localhost:8080/echo
 Lorsque vous utilisez HTTPie, vous pouvez omettre "localhost" et simplement taper http :8080/echo! 😎

Vous verrez une réponse qui ressemble à ceci :

{
    "body": null,
    "clientIpAddress": "172.17.0.1",
    "cookies": [],
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate",
        "connection": "keep-alive",
        "host": "localhost:8080",
        "user-agent": "HTTPie/3.2.1"
    },
    "httpVersion": "HTTP/1.1",
    "method": "GET",
    "queryString": null,
    "startedDateTime": "2022-06-24T18:52:07.853663255Z",
    "url": "http://localhost:8080/echo"
}

Maintenant que nous avons une application fonctionnelle, sécurisons-la !

Avant de passer à la section suivante, arrêtez l'application avec Ctrl+C, ne vous inquiétez pas ; nous allons le redémarrer dans une minute.

Configurer Okta

Pour sécuriser notre application avec OAuth 2.0, nous allons avoir besoin d'un fournisseur d'identité OAuth (IdP). À peu près n'importe quel serveur compatible OpenID Connect (OIDC) devrait fonctionner, comme Auth0 ou Keycloak , mais ceci est un blog Okta, alors utilisons Okta.

Si vous n'en avez pas déjà un, vous aurez besoin d'un compte développeur Okta gratuit. Installez la CLI Okta et depuis le répertoire du projet, exécutez okta startpour vous inscrire à un nouveau compte et configurer cette application !

 Si vous avez déjà un compte Okta, lancez-le d' okta loginabord.

Que fait la CLI d'Okta ?

L'Okta CLI créera une application Web OIDC dans votre Okta Org. Il ajoutera les URI de redirection requis et accordera l'accès au groupe Tout le monde. Vous verrez une sortie comme celle-ci lorsqu'elle sera terminée :

Okta application configuration has been written to: .env

Exécutez cat .env(ou type .envsous Windows) pour voir l'émetteur et les informations d'identification de votre application.

ISSUER=https://dev-133337.okta.com/oauth2/default
CLIENT_ID=0oab8eb55Kb9jdMIr5d6
CLIENT_SECRET=NEVER-SHOW-SECRETS

Configurer le proxy OAuth2

Il existe deux façons d'utiliser le proxy OAuth2 : acheminez votre trafic directement ou utilisez-le avec la auth_requestdirective Nginx. Je recommanderais d'acheminer le trafic via Nginx si possible, mais je vais parcourir les deux options et expliquer ma recommandation ci-dessous.

Pour réduire le nombre de pièces mobiles, je laisserai Nginx de côté dans ce premier exemple ; tout notre trafic Web passera par le proxy OAuth2. Les utilisateurs qui accèdent à l'application Web echo seront redirigés vers Okta pour se connecter. Une fois qu'ils se seront connectés, le proxy OAuth2 définira un cookie de session. Le proxy OAuth2 validera la session avant de transmettre la requête à l'application Web echo dans les requêtes futures.

 En termes OAuth, OAuth2 Proxy agit en tant que "client", gérant les détails du protocole OAuth (dans ce cas, un code d'autorisation ).
TL; DR - Redirige l'utilisateur vers la page de connexion de l'IdP OAuth et gère une route de "rappel" pour le renvoyer à l'application.

Diagramme de séquence montrant la redirection d'authentification et la demande valide

Cet exemple va devenir trop grand manuellement à l'aide de docker runcommandes ; passez à using docker composepour démarrer l'application Web echo et oauth2-proxy.

Commençons simplement et augmentons la complexité au fur et à mesure. Créez un docker-compose.ymlavec oauth2-proxy et l'application Web ci-dessus :

version: "3.7"
services:

  web-app: 
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    command:
      - --http-address
      - 0.0.0.0:4180 
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/ 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER}
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost:4180/oauth2/callback 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

    ports:
      - 4180:4180 
 Construisez et exécutez Dockerfile dans le répertoire actuel.
 Écoutez sur le port 4180.
 Requêtes authentifiées par proxy vers le conteneur d'application Web Java.
 Informations client OIDC (émetteur, ID client et secret client), ces valeurs sont définies dans le .envfichier.
 En option, transmettez l'accès à l'application Web.
 Autorisez tous les domaines de messagerie sauf si vous utilisez un fournisseur d'authentification sociale, vous voudrez gérer cela dans votre fournisseur d'identité et NON dans votre application.
 Définissez l'URL de redirection sur une httpURL, celle-ci par défaut est https.
 Ouvrez le .envfichier et définissez cette variable sur une chaîne base64 aléatoire de 32 octets openssl rand -base64 32 | tr — '+/' '-_'.
 Exposez le port 4180.

Démarrez tout en exécutant :

docker compose up

Ouvrez maintenant votre navigateur sur http://localhost:4180/echo, et vous serez redirigé vers une page avec un bouton de connexion. Cliquez sur le bouton, et vous serez redirigé vers l'application "echo", et vous devriez voir des informations sur la requête nouvellement authentifiée !

Capture d'écran de la page de connexion par défaut oauth2-proxy

 Si vous êtes déjà connecté à votre compte Okta, ouvrez un navigateur incognito/privé pour voir le flux de connexion complet.

Super, l'application est maintenant sécurisée, mais nous avons encore quelques petites choses à nettoyer :

Tout l'état de la session est stocké dans un cookie.

La page de connexion initiale à double redirection doit disparaître.

Nous n'avons pas encore parlé de l'accès à l'API.

Ces deux premiers problèmes peuvent être résolus avec quelques mises à jour de la configuration du proxy OAuth2. Modifiez le docker-compose.ymlfichier :

       OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
+      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
+      OAUTH2_PROXY_COOKIE_NAME: SESSION 
+      OAUTH2_PROXY_COOKIE_SAMESITE: lax 
+      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
+      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis
    ports:
      - 4180:4180
+    depends_on:
+      - redis
+
+  redis:
+    image: redis:7.0.2-alpine3.16
+    volumes:
+      - cache:/data 
+
+volumes:
+  cache:
+    driver: local
 Ignorez la page de connexion par défaut et redirigez directement vers l'IdP.
 Par défaut, le nom du cookie est _oauth2_proxy; changez-le en SESSION.
 Définissez la politique du même site du cookie surlax ; la redirection de l'IdP OAuth aura besoin du cookie de session.
 Utilisez Redis pour stocker les informations de session.
 Démarrez un conteneur Redis.
 Conserver les données Redis entre les redémarrages.

Arrêtez le processus docker-compose (Ctrl+C) et redémarrez-le :

docker compose up

Encore une fois, ouvrez votre navigateur http://localhost:4180/echoet ouvrez votre onglet réseau, vous verrez le SESSIONcookie renommé et maintenant plus petit.

Vous pourriez vous arrêter ici, mais vous ne devriez pas. Nous avons encore quelques problèmes : les clients API ne sont pas pris en charge et nous n'avons pas parlé de la déconnexion.

 Pour la section suivante, vous aurez besoin d'un jeton d'accès. Vous pouvez utiliser le jeton d'accès de l' en- x-access-tokentête de votre dernière requête. Ouvrez votre terminal et définissez une variable d'environnement : export TOKEN={your-token-value}.

Client API REST

Pour les besoins de cet article, je considérerai tout client qui définit un Authorizationen-tête HTTP comme un client API. Par exemple : Authorization: Bearer {access_token_here}.

Le client API ne peut probablement pas gérer une réponse de redirection mais s'attend à ce qu'un 40xcode d'état soit renvoyé.

Diagramme de séquence montrant une requête API via oauth2-proxy

Prenons du recul et configurons OAuth2 Proxy en tant que serveur de ressources OAuth , qui accepte les jetons d'accès JWT. C'est peut-être tout ce dont vous avez besoin pour certaines applications, mais si vous devez prendre en charge à la fois les clients de navigateur et d'API, continuez à lire, nous y arriverons dans la section suivante.

 C'est courant, mais il n'est pas nécessaire que les jetons d'accès OAuth 2.0 soient un JWT. Si vous utilisez un autre IdP OAuth, vérifiez s'il prend en charge les JWT avant de continuer.

Dans le docker-compose.yml, réduisez les variables d'environnement au strict minimum nécessaire pour une API REST :

...
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: api://default 
      OAUTH2_PROXY_SET_XAUTHREQUEST: true
      OAUTH2_PROXY_CLIENT_SECRET: this_value_is_required_but_not_used 
      OAUTH2_PROXY_COOKIE_SECRET: NOT_USED_BUT_REQUIRED_VALUE_32b_ 
...
 Nous n'utilisons actuellement aucun des flux OIDC, mais cela reste nécessaire.
 Peut-être une variable mal nommée, cela indique oauth2-proxyde valider le jeton d'accès JWT et de "sauter" la recherche d'une session OAuth 2.0.
 Lisez l'e-mail de l'utilisateur à partir de la subrevendication dans le jeton d'accès.
 La même URL d'émetteur est utilisée, le point de terminaison JWKS sera recherché automatiquement via les métadonnées de découverte OIDC.
 Le "client-id" est en fait la demande d'audience audet non l'ID d'un client spécifique (plusieurs "clients" d'API peuvent accéder à la même API REST).
 Il n'y a pas de "secret client", mais c'est un champ obligatoire…​
 Idem avec le cookie secret, ces flux n'utilisent pas de cookies, mais le champ est obligatoire.

Redémarrez les services. (Arrêtez-vous puis docker compose uprecommencez.)

À l'aide de la variable d'environnement du jeton d'accès que vous avez définie dans la section précédente, exécutez ceci :

http :4180/echo "Authorization: Bearer ${TOKEN}"

Impressionnant! Maintenant votre application est sécurisée pour les clients REST !

Pas si vite; nos clients de navigateur ne fonctionnent plus correctement ! L'ID client et le secret ne sont pas corrects, ce qui signifie que les utilisateurs ne pourront pas se connecter. Nous pouvons résoudre ces deux problèmes avec Nginx.

Ajouter Nginx pour acheminer le trafic

Ajouter un autre proxy inverse dans le mix peut sembler excessif ; pour qu'une requête accède à l'application, elle doit d'abord passer par Nginx et OAuth2 Proxy. Cependant, vous pouvez déjà utiliser Nginx pour l'équilibrage de charge, la terminaison TLS ou d'autres problèmes d'entrée.

Diagramme de séquence montrant un flux à travers les deux requêtes avec Nginx et oauth2-proxy

Bien que nous puissions acheminer notre trafic via les deux proxys comme illustré ci-dessus, j'utiliserai à la auth_requestplace la directive Nginx. Nginx fera une demande REST au point de terminaison du proxy OAuth2 en /oauth2/authutilisant les en-têtes de demande d'origine (y compris les cookies et les Authorizationen-têtes). Le proxy OAuth2 répondra avec un 202code d'état si la demande est valide ou 401non.

Diagramme de séquence montrant une requête avec Nginx et oauth2-proxy

Cette configuration utilise le même nombre de requêtes que le diagramme précédent, mais offre une flexibilité supplémentaire sur la manière dont les requêtes sont acheminées vers l'application Web en amont.

Configurer Nginx

Revenez dans le docker-compose.ymlet ajoutez un nouveau servicepour Nginx :

...
  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

Ensuite, créez un nginx-default.conf.templatefichier. Ce bloc de code est un peu compliqué, assurez-vous de lire les annotations :

server {
    listen 80;
    server_name _;

    location = /oauth2/auth {
        internal; 
        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Scheme         $scheme;
        # nginx auth_request includes headers but not body
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
    }

    location / {
        auth_request /oauth2/auth; 

        auth_request_set $email  $upstream_http_x_auth_request_email; 
        proxy_set_header X-Email $email;
        auth_request_set $user  $upstream_http_x_auth_request_user;
        proxy_set_header X-User  $user;
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host:80;
        proxy_set_header X-Forwarded-Port 80;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Proto http;

        proxy_http_version 1.1; 
        proxy_pass http://web-app:8080/; 
    }
}
 N'exposez pas cette route à des clients externes.
 Nginx fait une demande à l' oauth2-proxyAPI REST, pour vérifier l'authentification pour cette demande.
 Ajoutez les en-têtes renvoyés par la demande d'authentification.
 HTTP 1.0 est la valeur par défaut si elle n'est pas définie.
 Envoyez des demandes authentifiées à l'application Web.

Redémarrez les conteneurs Docker et vérifiez que tout fonctionne avec HTTPie (assurez-vous que vous utilisez le port 80maintenant).

http localhost/echo "Authorization: Bearer ${TOKEN}"

Si vous supprimez ou modifiez l' Authorizationen-tête, un 401sera renvoyé. Les requêtes du navigateur renverront désormais également un 401!

Presque fini! Nous devons encore faire en sorte que tout fonctionne pour les clients API et les navigateurs, et gérer les demandes de déconnexion.

Acheminer tout le trafic via Nginx

L'envoi de tout le trafic via Nginx a l'avantage supplémentaire de vous donner le contrôle de la façon dont les points de terminaison du proxy OAuth2 sont exposés. Par exemple, la section précédente marquait la /oauth2/authroute comme "interne", donc seule la auth_requstdirective peut l'utiliser.

Dans le nginx-default.conf.template, ajoutez quelques nouvelles locationsections pour exposer les autres points de /oauth2terminaison. Le premier locationtraitera les requêtes liées à OAuth 2.0 comme le rappel de redirection. La seconde configurera le point de terminaison de déconnexion pour accepter uniquement les requêtes POST. (Cela empêche une requête GET malveillante de mettre fin à la session de l'utilisateur.)

    location /oauth2/ {
        proxy_pass       http://oauth2-proxy:4180; 
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }

    location = /oauth2/sign_out { 
        # Sign-out mutates the session, only allow POST requests
        if ($request_method != POST) {
            return 405;
        }

        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }
...
 Envoyez les demandes de rappel et de déconnexion OAuth à oauth2-proxy.
 Autorisez uniquement les requêtes POST au point de terminaison de déconnexion.
 Le point de terminaison de déconnexion n'utilise pas de jeton CSRF. TODO : lien vers le post d'Alisa sur ce sujet.

Un dernier changement, mettez à jour la location /section pour rediriger vers la page de connexion pour tous les clients non API :

location / {
        auth_request /oauth2/auth;

        # if the authorization header was set (i.e. `Authorization: Bearer {token}`)
        # assume API client and do NOT redirect to login page
        if ($http_authorization = "") {
            error_page 401 = /oauth2/start;
        }
...

Configurer le proxy OAuth2 pour prendre en charge les clients d'API et de navigateur

Parfois, une application doit gérer les requêtes des navigateurs et d'autres clients API. Dans ce cas, l'application agit à la fois comme client OAuth et serveur de ressources. Le proxy OAuth2 peut être configuré pour prendre en charge les deux types d'applications. Cependant, vous avez peut-être remarqué que certaines valeurs de configuration du proxy OAuth2 sont surchargées ; par exemple, "l'ID client" est utilisé à la fois comme ID du client OAuth et comme valeur JWT pour l'audience. Heureusement, il existe une solution de contournement ! Voici la finale annotée docker-compose.yml:

version: "3.7"

services:

  web-app:
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    depends_on:
      - redis
    command:
      - --http-address
      - 0.0.0.0:4180
    environment:
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost/oauth2/callback 

      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}

      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EXTRA_AUDIENCES: api://default 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 

      OAUTH2_PROXY_SET_XAUTHREQUEST: true 
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 

      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis

      OAUTH2_PROXY_COOKIE_REFRESH: 30m 
      OAUTH2_PROXY_COOKIE_NAME: SESSION 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

  redis:
    image: redis:7.0.2-alpine3.16
    volumes:
      - cache:/data

volumes:
  cache:
    driver: local
 Autoriser toutes les adresses e-mail ; l'IdP gérera les utilisateurs qui y ont accès.
 Pour les cas d'utilisation d'IdP uniques, ignorez la page de connexion intermédiaire.
 oauth2-proxy par défaut est https, cet exemple utilise httpsur localhost.
 L'émetteur, l'ID client et le secret seront chargés à partir du .envfichier.
 Autoriser le traitement des jetons porteurs JWT pour les clients API.
 Configurez une audience "autorisée" supplémentaire, en plus de "l'ID client".
 Utilisez la subrevendication des jetons d'accès JWT comme adresse e-mail.
 Ajoutez des en-têtes d'informations utilisateur à la demande d'application Web proxy.
 Facultatif, transmettez le jeton d'accès à la demande d'application Web proxy.
 Utilisez Redis pour la gestion des sessions.
 Actualiser les cookies toutes les 30 minutes.
 Définissez le nom du cookie de session sur SESSION.
 Configurez la clé de chiffrement (chargée à partir du .envfichier).

Redémarrez les services et accédez à l'application via votre navigateur : http://localhost/echo. Réessayez avec HTTPie :

http localhost/echo "Authorization: Bearer ${TOKEN}"

Les deux requêtes doivent afficher des informations similaires.

Sans aucun changement de code, l'application web "echo" est désormais sécurisée avec OIDC / OAuth 2.0 !

Le proxy OAuth2 est-il adapté à votre application ?

Cet article vous a appris à sécuriser une application existante avec OAuth 2.0 sans aucun changement de code ! Le proxy OAuth2 n'est pas réservé aux applications héritées ; il est également couramment utilisé dans un modèle side-car avec Kubernetes, ce qui vous permet de séparer les problèmes d'autorisation de votre application.

Ce n'est pas parfait, cependant; il y a quelques lacunes :

La déconnexion SSO ( OIDC RP-Initated Logout ) n'est pas encore prise en charge.

Nécessite de contourner une route de déconnexion qui accepte GETles requêtes et ne nécessite pas de jeton CSRF.

Les cas d'utilisation de l'API prennent en charge les jetons d'accès JWT uniquement ; si votre application a besoin de valider à distance des jetons , vous auriez besoin d'une autre solution. Il peut être plus facile de configurer le module JWT de Nginx à la place.

La configuration est un peu maladroite ; certaines valeurs sont obligatoires mais non utilisées.

Où brille-t-il ?

Si vous avez besoin d'ajouter une authentification à une application qui ne prend pas en charge OAuth, ou si vous devez prendre en charge différents types d'applications de manière standard, OAuth2 Proxy peut être un excellent choix ! En prime, il a une excellente documentation .

Lien : https://developer.okta.com/blog/2022/07/14/add-auth-to-any-app-with-oauth2-proxy

#auth #oauth

Ajoutez Auth à N'importe Quelle Application Avec Le Proxy OAuth2
Hong  Nhung

Hong Nhung

1659025389

Cách Thêm Auth Vào Bất Kỳ ứng Dụng Nào Với OAuth2 Proxy

Việc cập nhật ứng dụng để sử dụng OAuth 2.0 không cần quá phức tạp. Hầu hết thời gian, ngôn ngữ hoặc khuôn khổ của bạn sẽ có thư viện OAuth. Đôi khi, đây không phải là trường hợp, và bạn cần phải tìm một giải pháp thay thế. Trong bài đăng này, tôi sẽ hướng dẫn cách thiết lập và sử dụng OAuth2 Proxy để bảo mật ứng dụng của bạn mà không có bất kỳ thay đổi mã nào!

OAuth2 Proxy là một proxy ngược nằm trước ứng dụng của bạn và xử lý sự phức tạp của OpenID Connect / OAuth 2.0 cho bạn; các yêu cầu gửi đến ứng dụng của bạn đã được cho phép!

Sơ đồ sử dụng cơ bản của OAuth2 Proxy

Điều kiện tiên quyết

Một ứng dụng không có bảo mật

Điều đầu tiên, chúng tôi cần một ứng dụng. Bạn có thể sử dụng bất kỳ ứng dụng web nào, nhưng đối với bài đăng này, tôi sẽ sử dụng một ứng dụng Java Spring Boot sẽ lặp lại các chi tiết của yêu cầu HTTP gửi đến. Đưa ra thông tin yêu cầu sẽ giúp trực quan hóa các tiêu đề HTTP bổ sung do OAuth2 Proxy thêm vào, chẳng hạn như địa chỉ email của người dùng.

Bạn có thể lấy dự án từ GitHub :

git clone https://github.com/oktadev/okta-oauth2-proxy-example.git -b start
cd okta-oauth2-proxy-example
 Nếu bạn muốn xem dự án đã hoàn thành, hãy xem mainchi nhánh.

Nếu bạn quan tâm đến thông tin chi tiết về ứng dụng Java này, hãy xem EchoApplicationlớp học. Nó chứa một điểm cuối duy nhất sẽ xử lý tất cả các yêu cầu và "kết xuất" nội dung của yêu cầu dưới dạng đối tượng JSON:

@RestController
static class EchoRestController {
    @RequestMapping("/**")
    Map<String, Object> echo(HttpServletRequest request,
                             @RequestHeader HttpHeaders headers,
                             @RequestBody(required = false) Map<String, Object> body) {

        Instant now = Instant.now();
        Cookie[] cookies = request.getCookies();

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("clientIpAddress", request.getRemoteAddr());
        result.put("cookies", cookies == null ? emptyList()
                                              : Arrays.stream(cookies).toList());
        result.put("headers", headers.toSingleValueMap()); // simplify json response
        result.put("httpVersion", request.getProtocol());
        result.put("method", request.getMethod());
        result.put("body", body);
        result.put("queryString", request.getQueryString());
        result.put("startedDateTime", now);
        result.put("url", request.getRequestURL());

        return result;
    }
}

Nếu bạn là một người hâm mộ Java, bạn có thể khởi động ứng dụng bằng cách chạy ./mvnw spring-boot:run. Mọi người khác có thể chạy các lệnh Docker sau:

docker build --tag echo-app .
docker run -p 8080:8080 echo-app

Khi ứng dụng đang chạy, hãy mở trình duyệt của bạn hoặc truy cập ứng dụng web trong thiết bị đầu cuối của bạn bằng HTTPie:

http localhost:8080/echo
 Khi sử dụng HTTPie, bạn có thể bỏ qua "localhost" và chỉ cần nhập http :8080/echo! 😎

Bạn sẽ thấy một phản hồi trông giống như sau:

{
    "body": null,
    "clientIpAddress": "172.17.0.1",
    "cookies": [],
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate",
        "connection": "keep-alive",
        "host": "localhost:8080",
        "user-agent": "HTTPie/3.2.1"
    },
    "httpVersion": "HTTP/1.1",
    "method": "GET",
    "queryString": null,
    "startedDateTime": "2022-06-24T18:52:07.853663255Z",
    "url": "http://localhost:8080/echo"
}

Bây giờ chúng ta có một ứng dụng đang hoạt động, hãy bảo mật nó!

Trước khi chuyển sang phần tiếp theo, hãy dừng ứng dụng bằng Ctrl + C, đừng lo lắng; chúng tôi sẽ khởi động lại nó sau một phút.

Thiết lập Okta

Để bảo mật ứng dụng của chúng tôi với OAuth 2.0, chúng tôi sẽ cần Nhà cung cấp nhận dạng OAuth (IdP). Chỉ cần bất kỳ máy chủ có khả năng OpenID Connect (OIDC) nào sẽ hoạt động, chẳng hạn như Auth0 hoặc Keycloak , nhưng đây là blog Okta, vì vậy hãy sử dụng Okta.

Nếu chưa có, bạn sẽ cần có tài khoản nhà phát triển Okta miễn phí. Cài đặt Okta CLI và từ thư mục dự án, chạy okta startđể đăng ký tài khoản mới và cấu hình ứng dụng này!

 Nếu bạn đã có tài khoản Okta, hãy chạy okta logintrước.

Okta CLI làm gì?

Okta CLI sẽ tạo Ứng dụng web OIDC trong Tổ chức Okta của bạn. Nó sẽ thêm các URI chuyển hướng được yêu cầu và cấp quyền truy cập vào nhóm Mọi người. Bạn sẽ thấy kết quả như sau khi hoàn tất:

Okta application configuration has been written to: .env

Chạy cat .env(hoặc type .envtrên Windows) để xem nhà phát hành và thông tin đăng nhập cho ứng dụng của bạn.

ISSUER=https://dev-133337.okta.com/oauth2/default
CLIENT_ID=0oab8eb55Kb9jdMIr5d6
CLIENT_SECRET=NEVER-SHOW-SECRETS

Thiết lập OAuth2 Proxy

Có hai cách để sử dụng OAuth2 Proxy: định tuyến lưu lượng truy cập của bạn qua nó trực tiếp hoặc sử dụng nó với lệnh Nginx auth_request. Tôi khuyên bạn nên định tuyến lưu lượng truy cập qua Nginx nếu có thể, nhưng tôi sẽ xem xét cả hai tùy chọn và giải thích đề xuất của tôi bên dưới.

Để giảm số lượng các bộ phận chuyển động, tôi sẽ loại bỏ Nginx trong ví dụ đầu tiên này; tất cả lưu lượng truy cập web của chúng tôi sẽ chuyển qua OAuth2 Proxy. Người dùng truy cập ứng dụng web echo sẽ được chuyển hướng đến Okta để đăng nhập. Sau khi họ đã đăng nhập, OAuth2 Proxy sẽ đặt cookie phiên. OAuth2 Proxy sẽ xác thực phiên trước khi chuyển yêu cầu đến ứng dụng web echo trong các yêu cầu trong tương lai.

 Theo điều khoản OAuth, OAuth2 Proxy đóng vai trò là "máy khách", xử lý các chi tiết giao thức OAuth, (trong trường hợp này là Cấp mã ủy quyền ).
TL; DR - Chuyển hướng người dùng đến trang đăng nhập của OAuth IdP và xử lý tuyến "gọi lại" để đưa họ trở lại ứng dụng.

Sơ đồ trình tự hiển thị chuyển hướng xác thực và yêu cầu hợp lệ

Ví dụ này sẽ phát triển thủ công bằng cách sử dụng docker runcác lệnh; chuyển sang sử dụng docker composeđể khởi động ứng dụng web echo và oauth2-proxy.

Hãy bắt đầu đơn giản và tăng độ phức tạp khi chúng ta tiếp tục. Tạo một docker-compose.ymlvới oauth2-proxy và ứng dụng web từ phía trên:

version: "3.7"
services:

  web-app: 
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    command:
      - --http-address
      - 0.0.0.0:4180 
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/ 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER}
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost:4180/oauth2/callback 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

    ports:
      - 4180:4180 
 Xây dựng và chạy Dockerfile trong thư mục hiện tại.
 Nghe trên cổng 4180.
 Yêu cầu được xác thực proxy tới vùng chứa ứng dụng web Java.
 Thông tin khách hàng OIDC (nhà phát hành, ID khách hàng và bí mật khách hàng), các giá trị này được xác định trong .envtệp.
 Theo tùy chọn, chuyển quyền truy cập vào ứng dụng web.
 Cho phép tất cả các miền email trừ khi bạn sử dụng nhà cung cấp dịch vụ xác thực xã hội, bạn sẽ muốn quản lý điều này trong IdP chứ KHÔNG phải trong ứng dụng của mình.
 Đặt URL chuyển hướng thành một httpURL, điều này mặc định là https.
 Mở .envtệp và đặt biến này thành một chuỗi base64 32 byte ngẫu nhiên openssl rand -base64 32 | tr — '+/' '-_'.
 Cổng tiếp xúc 4180.

Bắt đầu mọi thứ bằng cách chạy:

docker compose up

Bây giờ, hãy mở trình duyệt của bạn đến http://localhost:4180/echovà bạn sẽ được chuyển hướng đến một trang có nút đăng nhập. Nhấp vào nút và bạn sẽ được chuyển hướng trở lại ứng dụng "echo" và bạn sẽ thấy thông tin về yêu cầu mới được xác thực!

Ảnh chụp màn hình của trang đăng nhập mặc định oauth2-proxy

 Nếu bạn đã đăng nhập vào tài khoản Okta của mình, hãy mở trình duyệt ẩn danh / riêng tư để xem toàn bộ quy trình đăng nhập.

Tuyệt vời, ứng dụng hiện đã được bảo mật, nhưng chúng tôi vẫn còn một số thứ cần làm sạch:

Tất cả trạng thái phiên được lưu trữ trong một cookie.

Trang đăng nhập chuyển hướng kép ban đầu phải đi.

Chúng tôi chưa nói về quyền truy cập API.

Hai sự cố đầu tiên này có thể được khắc phục bằng một vài bản cập nhật cho cấu hình OAuth2 Proxy. Chỉnh sửa docker-compose.ymltệp:

       OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
+      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
+      OAUTH2_PROXY_COOKIE_NAME: SESSION 
+      OAUTH2_PROXY_COOKIE_SAMESITE: lax 
+      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
+      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis
    ports:
      - 4180:4180
+    depends_on:
+      - redis
+
+  redis:
+    image: redis:7.0.2-alpine3.16
+    volumes:
+      - cache:/data 
+
+volumes:
+  cache:
+    driver: local
 Bỏ qua trang đăng nhập mặc định và chuyển hướng trực tiếp đến IdP.
 Theo mặc định, tên cookie là _oauth2_proxy; thay đổi nó thành SESSION.
 Đặt chính sách trang web tương tự của cookie thành lax; chuyển hướng từ OAuth IdP sẽ cần cookie phiên.
 Sử dụng Redis để lưu trữ thông tin phiên.
 Khởi động vùng chứa Redis.
 Duy trì dữ liệu Redis giữa các lần khởi động lại.

Dừng quá trình soạn thư (Ctrl + C) và khởi động lại:

docker compose up

Một lần nữa, mở trình duyệt của bạn http://localhost:4180/echovà mở tab mạng của bạn, bạn sẽ thấy SESSIONcookie đã được đổi tên và bây giờ nhỏ hơn.

Bạn có thể dừng lại ở đây, nhưng bạn không nên. Chúng tôi vẫn gặp một số vấn đề: ứng dụng khách API không được hỗ trợ và chúng tôi chưa nói về việc đăng xuất.

 Đối với phần tiếp theo, bạn sẽ cần một mã thông báo truy cập. Bạn có thể sử dụng mã thông báo truy cập từ tiêu đề yêu cầu cuối cùng của mình x-access-token. Mở thiết bị đầu cuối của bạn và đặt một biến môi trường export TOKEN={your-token-value}:.

Máy khách API REST

Vì lợi ích của bài đăng này, tôi sẽ xem xét bất kỳ ứng dụng khách nào đặt Authorizationtiêu đề HTTP là ứng dụng khách API. Ví dụ Authorization: Bearer {access_token_here}:.

Ứng dụng khách API có thể không thể xử lý phản hồi chuyển hướng nhưng mong đợi 40xmã trạng thái được trả lại.

Sơ đồ trình tự hiển thị một yêu cầu API thông qua oauth2-proxy

Hãy lùi lại một bước và định cấu hình OAuth2 Proxy làm máy chủ tài nguyên OAuth , máy chủ này chấp nhận mã thông báo truy cập JWT. Đây có thể là tất cả những gì bạn cần đối với một số ứng dụng, nhưng nếu bạn cần hỗ trợ cả trình duyệt và ứng dụng khách API, hãy tiếp tục đọc, chúng ta sẽ đến đó trong phần sau.

 Điều này là phổ biến, nhưng không bắt buộc mã thông báo truy cập OAuth 2.0 phải là JWT. Nếu bạn đang sử dụng IdP OAuth khác, hãy kiểm tra kỹ xem chúng có hỗ trợ JWT không trước khi tiếp tục.

Trong phần docker-compose.yml, cắt giảm các biến môi trường xuống mức tối thiểu cần thiết cho API REST:

...
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: api://default 
      OAUTH2_PROXY_SET_XAUTHREQUEST: true
      OAUTH2_PROXY_CLIENT_SECRET: this_value_is_required_but_not_used 
      OAUTH2_PROXY_COOKIE_SECRET: NOT_USED_BUT_REQUIRED_VALUE_32b_ 
...
 Chúng tôi không thực sự sử dụng bất kỳ luồng OIDC nào, nhưng điều này vẫn được yêu cầu.
 Perhaps a poorly named variable, this tells oauth2-proxy to validate the JWT access token and to "skip" looking for an OAuth 2.0 session.
 Read the user’s email from the sub claim in the access token.
 The same issuer URL is used, the JWKS endpoint will be looked up automatically via the OIDC discovery metadata.
 The "client-id" is actually the audience aud claim and not the ID of a specific client (multiple API "clients" could be accessing the same REST API).
 There is no "client-secret," but it’s a required field…​
 Same with the cookie secret, these flows do not use cookies, but the field is required.

Restart the services. (Stop and then run docker compose up again.)

Using the access token environment variable you set in the previous section, run this:

http :4180/echo "Authorization: Bearer ${TOKEN}"

Đáng kinh ngạc! Bây giờ ứng dụng của bạn đã được bảo mật cho các khách hàng REST!

Không quá nhanh; bây giờ các ứng dụng khách trình duyệt của chúng tôi không hoạt động chính xác! ID khách hàng và bí mật không chính xác, có nghĩa là người dùng sẽ không thể đăng nhập. Chúng tôi có thể khắc phục cả hai sự cố này với Nginx.

Thêm Nginx vào tuyến đường giao thông

Thêm một proxy ngược khác vào hỗn hợp có vẻ quá mức; đối với yêu cầu truy cập ứng dụng, trước tiên nó cần phải chuyển qua cả Nginx và OAuth2 Proxy. Tuy nhiên, bạn có thể đã sử dụng Nginx để cân bằng tải, kết thúc TLS hoặc các mối quan tâm về xâm nhập khác.

Sơ đồ trình tự hiển thị cả hai yêu cầu với Nginx và oauth2-proxy

Mặc dù chúng tôi có thể định tuyến lưu lượng truy cập của mình thông qua cả hai proxy như hình trên, nhưng tôi sẽ sử dụng lệnh Nginx auth_requestđể thay thế. Nginx sẽ thực hiện một yêu cầu REST tới /oauth2/authđiểm cuối của OAuth2 Proxy bằng cách sử dụng các tiêu đề yêu cầu ban đầu (bao gồm bất kỳ cookie và Authorizationtiêu đề nào). OAuth2 Proxy sẽ phản hồi bằng 202mã trạng thái nếu yêu cầu hợp lệ hoặc theo 401cách khác.

Sơ đồ trình tự hiển thị một yêu cầu với Nginx và oauth2-proxy

Thiết lập này sử dụng cùng một số lượng yêu cầu như sơ đồ trước đó nhưng cung cấp thêm tính linh hoạt về cách các yêu cầu được chuyển đến ứng dụng web ngược dòng.

Định cấu hình Nginx

Quay lại docker-compose.ymlvà thêm một cái mới servicecho Nginx:

...
  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

Tiếp theo, tạo một nginx-default.conf.templatetệp. Khối mã này có một chút liên quan, hãy nhớ đọc các chú thích:

server {
    listen 80;
    server_name _;

    location = /oauth2/auth {
        internal; 
        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Scheme         $scheme;
        # nginx auth_request includes headers but not body
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
    }

    location / {
        auth_request /oauth2/auth; 

        auth_request_set $email  $upstream_http_x_auth_request_email; 
        proxy_set_header X-Email $email;
        auth_request_set $user  $upstream_http_x_auth_request_user;
        proxy_set_header X-User  $user;
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host:80;
        proxy_set_header X-Forwarded-Port 80;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Proto http;

        proxy_http_version 1.1; 
        proxy_pass http://web-app:8080/; 
    }
}
 Không để lộ tuyến đường này cho các khách hàng bên ngoài.
 Nginx đưa ra yêu cầu tới oauth2-proxyREST API, để xác minh xác thực cho yêu cầu này.
 Thêm tiêu đề được trả về từ yêu cầu xác thực.
 HTTP 1.0 là mặc định nếu không được đặt.
 Gửi yêu cầu đã xác thực đến ứng dụng web.

Khởi động lại các bộ chứa docker và xác minh mọi thứ đang hoạt động với HTTPie (đảm bảo rằng bạn đang sử dụng cổng 80ngay bây giờ).

http localhost/echo "Authorization: Bearer ${TOKEN}"

Nếu bạn xóa hoặc thay đổi Authorizationtiêu đề, a 401sẽ được trả lại. Yêu cầu trình duyệt bây giờ cũng sẽ trả về a 401!

Sắp xong! Chúng tôi vẫn cần làm cho mọi thứ hoạt động cho cả trình duyệt và ứng dụng khách API, đồng thời xử lý các yêu cầu đăng xuất.

Định tuyến tất cả giao thông qua Nginx

Gửi tất cả lưu lượng truy cập thông qua Nginx có thêm lợi ích là cho phép bạn kiểm soát cách các điểm cuối Proxy OAuth2 được hiển thị. Ví dụ: phần trước đã đánh dấu /oauth2/authtuyến đường là "nội bộ", vì vậy chỉ có chỉ auth_requstthị mới có thể sử dụng nó.

Trong nginx-default.conf.template, hãy thêm một vài locationphần mới để hiển thị các /oauth2điểm cuối khác. Đầu tiên locationsẽ xử lý các yêu cầu liên quan đến OAuth 2.0 như gọi lại chuyển hướng. Thứ hai sẽ định cấu hình điểm cuối đăng xuất để chỉ chấp nhận các yêu cầu ĐĂNG. (Điều này ngăn một yêu cầu GET giả mạo kết thúc phiên của người dùng.)

    location /oauth2/ {
        proxy_pass       http://oauth2-proxy:4180; 
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }

    location = /oauth2/sign_out { 
        # Sign-out mutates the session, only allow POST requests
        if ($request_method != POST) {
            return 405;
        }

        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }
...
 Gửi yêu cầu gọi lại và đăng xuất OAuth tới oauth2-proxy.
 Chỉ cho phép các yêu cầu ĐĂNG tới điểm cuối đăng xuất.
 Điểm cuối đăng xuất không sử dụng mã thông báo CSRF. VIỆC CẦN LÀM: liên kết đến bài đăng của Alisa về chủ đề này.

Một thay đổi cuối cùng, hãy cập nhật location /phần để chuyển hướng đến trang đăng nhập cho tất cả các ứng dụng khách không phải API:

location / {
        auth_request /oauth2/auth;

        # if the authorization header was set (i.e. `Authorization: Bearer {token}`)
        # assume API client and do NOT redirect to login page
        if ($http_authorization = "") {
            error_page 401 = /oauth2/start;
        }
...

Định cấu hình OAuth2 Proxy để hỗ trợ các ứng dụng khách API và trình duyệt

Đôi khi, một ứng dụng cần xử lý các yêu cầu từ trình duyệt và các ứng dụng khách API khác. Trong trường hợp này, ứng dụng hoạt động như một máy khách OAuth và máy chủ tài nguyên. OAuth2 Proxy có thể được định cấu hình để hỗ trợ cả hai loại ứng dụng. Tuy nhiên, bạn có thể nhận thấy rằng một vài giá trị cấu hình OAuth2 Proxy bị quá tải; ví dụ: "ID khách hàng" được sử dụng làm ID của ứng dụng khách OAuth và giá trị JWT cho đối tượng. May mắn thay, có một cách giải quyết! Đây là chú thích cuối cùng docker-compose.yml:

version: "3.7"

services:

  web-app:
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    depends_on:
      - redis
    command:
      - --http-address
      - 0.0.0.0:4180
    environment:
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost/oauth2/callback 

      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}

      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EXTRA_AUDIENCES: api://default 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 

      OAUTH2_PROXY_SET_XAUTHREQUEST: true 
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 

      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis

      OAUTH2_PROXY_COOKIE_REFRESH: 30m 
      OAUTH2_PROXY_COOKIE_NAME: SESSION 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

  redis:
    image: redis:7.0.2-alpine3.16
    volumes:
      - cache:/data

volumes:
  cache:
    driver: local
 Cho phép tất cả các địa chỉ email; IdP sẽ quản lý người dùng nào có quyền truy cập.
 Đối với các trường hợp sử dụng IdP đơn lẻ, hãy bỏ qua trang đăng nhập trung gian.
 oauth2-proxy mặc định thành https, ví dụ này sử dụng httptrên localhost.
 Công ty phát hành, ID khách hàng và bí mật sẽ được tải từ .envtệp.
 Cho phép xử lý mã thông báo mang JWT cho các ứng dụng khách API.
 Định cấu hình thêm một đối tượng "được phép", ngoài "ID khách hàng".
 Sử dụng subxác nhận quyền sở hữu từ mã thông báo truy cập JWT làm địa chỉ email.
 Thêm tiêu đề thông tin người dùng vào yêu cầu ứng dụng web được ủy quyền.
 Tùy chọn, chuyển mã thông báo truy cập vào yêu cầu ứng dụng web được ủy quyền.
 Sử dụng Redis để quản lý phiên.
 Làm mới cookie sau mỗi 30 phút.
 Đặt tên cookie phiên thành SESSION.
 Định cấu hình khóa mã hóa (được tải từ .envtệp).

Khởi động lại các dịch vụ và truy cập ứng dụng thông qua trình duyệt của bạn http://localhost/echo:. Hãy thử lại bằng HTTPie:

http localhost/echo "Authorization: Bearer ${TOKEN}"

Cả hai yêu cầu phải hiển thị thông tin tương tự.

Nếu không có bất kỳ thay đổi mã nào, ứng dụng web "echo" hiện được bảo mật bằng OIDC / OAuth 2.0!

OAuth2 Proxy có phù hợp với ứng dụng của bạn không?

Bài đăng này đã dạy bạn cách bảo mật ứng dụng hiện có bằng OAuth 2.0 mà không có bất kỳ thay đổi mã nào! OAuth2 Proxy không chỉ dành cho các ứng dụng kế thừa; nó cũng thường được sử dụng trong một mẫu sidecar với Kubernetes, cho phép bạn tách biệt các mối quan tâm về ủy quyền khỏi ứng dụng của mình.

Tuy nhiên, nó không hoàn hảo; có một số thiếu sót:

Đăng xuất SSO ( OIDC RP-Initated Logout ) chưa được hỗ trợ.

Yêu cầu làm việc xung quanh một tuyến đường đăng xuất chấp nhận GETyêu cầu và không yêu cầu mã thông báo CSRF.

Các trường hợp sử dụng API hỗ trợ mã thông báo truy cập chỉ JWT; nếu ứng dụng của bạn cần xác thực mã thông báo từ xa , bạn sẽ cần một giải pháp khác. Thay vào đó, việc định cấu hình mô-đun JWT của Nginx có thể dễ dàng hơn .

Cấu hình là một chút lộn xộn; một số giá trị được yêu cầu nhưng không được sử dụng.

Nó tỏa sáng ở đâu?

Nếu bạn cần thêm xác thực vào một ứng dụng không có hỗ trợ OAuth hoặc bạn cần hỗ trợ nhiều loại ứng dụng khác nhau theo cách tiêu chuẩn, OAuth2 Proxy có thể là một lựa chọn tuyệt vời! Như một phần thưởng, nó có tài liệu tuyệt vời .

Liên kết: https://developer.okta.com/blog/2022/07/14/add-auth-to-any-app-with-oauth2-proxy

#auth #oauth

Cách Thêm Auth Vào Bất Kỳ ứng Dụng Nào Với OAuth2 Proxy

Добавьте аутентификацию в любое приложение с помощью OAuth2 Proxy

Обновление приложения для использования OAuth 2.0 не должно быть сложным. В большинстве случаев в вашем языке или фреймворке уже есть библиотека OAuth. Иногда это не так, и вам нужно найти альтернативу. В этом посте я расскажу о настройке и использовании прокси-сервера OAuth2 для защиты вашего приложения без каких-либо изменений кода!

OAuth2 Proxy — это обратный прокси-сервер, который находится перед вашим приложением и решает за вас сложности OpenID Connect / OAuth 2.0; запросы, которые попадают в ваше приложение, уже авторизованы!

Базовая схема использования OAuth2 Proxy

Предпосылки

Приложение без безопасности

Прежде всего, нам нужно приложение. Вы можете использовать любое веб-приложение, но в этом посте я буду использовать приложение Java Spring Boot, которое будет отображать детали входящего HTTP-запроса. Повторение информации запроса поможет визуализировать дополнительные заголовки HTTP, добавленные прокси-сервером OAuth2, например адрес электронной почты пользователя.

Вы можете взять проект с GitHub :

git clone https://github.com/oktadev/okta-oauth2-proxy-example.git -b start
cd okta-oauth2-proxy-example
 Если вы хотите увидеть завершенный проект, вместо этого проверьте mainветку.

Если вас интересуют подробности этого Java-приложения, взгляните на EchoApplicationкласс. Он содержит одну конечную точку, которая будет обрабатывать все запросы и «сбрасывать» содержимое запроса в виде объекта JSON:

@RestController
static class EchoRestController {
    @RequestMapping("/**")
    Map<String, Object> echo(HttpServletRequest request,
                             @RequestHeader HttpHeaders headers,
                             @RequestBody(required = false) Map<String, Object> body) {

        Instant now = Instant.now();
        Cookie[] cookies = request.getCookies();

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("clientIpAddress", request.getRemoteAddr());
        result.put("cookies", cookies == null ? emptyList()
                                              : Arrays.stream(cookies).toList());
        result.put("headers", headers.toSingleValueMap()); // simplify json response
        result.put("httpVersion", request.getProtocol());
        result.put("method", request.getMethod());
        result.put("body", body);
        result.put("queryString", request.getQueryString());
        result.put("startedDateTime", now);
        result.put("url", request.getRequestURL());

        return result;
    }
}

Если вы являетесь поклонником Java, вы можете запустить приложение, запустив ./mvnw spring-boot:run. Все остальные могут запускать следующие команды Docker:

docker build --tag echo-app .
docker run -p 8080:8080 echo-app

После запуска приложения откройте браузер или войдите в веб-приложение в своем терминале с помощью HTTPie:

http localhost:8080/echo
 При использовании HTTPie вы можете опустить «localhost» и просто ввести http :8080/echo! 😎

Вы увидите ответ, который выглядит примерно так:

{
    "body": null,
    "clientIpAddress": "172.17.0.1",
    "cookies": [],
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate",
        "connection": "keep-alive",
        "host": "localhost:8080",
        "user-agent": "HTTPie/3.2.1"
    },
    "httpVersion": "HTTP/1.1",
    "method": "GET",
    "queryString": null,
    "startedDateTime": "2022-06-24T18:52:07.853663255Z",
    "url": "http://localhost:8080/echo"
}

Теперь, когда у нас есть работающее приложение, давайте защитим его!

Прежде чем перейти к следующему разделу, остановите приложение с помощью Ctrl+C, не беспокойтесь; мы запустим его обратно через минуту.

Настроить Окту

Чтобы защитить наше приложение с помощью OAuth 2.0, нам понадобится поставщик удостоверений OAuth (IdP). Почти любой сервер с поддержкой OpenID Connect (OIDC) должен работать, например, Auth0 или Keycloak , но это блог Okta, поэтому давайте использовать Okta.

Если у вас ее еще нет, вам понадобится бесплатная учетная запись разработчика Okta. Установите Okta CLI и из каталога проекта запустите, okta startчтобы зарегистрировать новую учетную запись и настроить это приложение!

 Если у вас уже есть учетная запись Okta, okta loginсначала запустите.

Что делает интерфейс командной строки Okta?

Интерфейс командной строки Okta создаст веб-приложение OIDC в ​​вашей организации Okta. Он добавит необходимые URI перенаправления и предоставит доступ группе «Все». Вы увидите вывод, подобный следующему, когда он будет завершен:

Okta application configuration has been written to: .env

Запустите cat .env(или type .envв Windows), чтобы увидеть издателя и учетные данные для вашего приложения.

ISSUER=https://dev-133337.okta.com/oauth2/default
CLIENT_ID=0oab8eb55Kb9jdMIr5d6
CLIENT_SECRET=NEVER-SHOW-SECRETS

Настроить прокси OAuth2

Существует два способа использования прокси-сервера OAuth2: направлять трафик напрямую через него или использовать его с auth_requestдирективой Nginx. Я бы рекомендовал направлять трафик через Nginx, если это возможно, но я рассмотрю оба варианта и объясню свою рекомендацию ниже.

Чтобы уменьшить количество движущихся частей, я не буду использовать Nginx в этом первом примере; весь наш веб-трафик будет проходить через прокси-сервер OAuth2. Пользователи, которые получают доступ к эхо-веб-приложению, будут перенаправлены в Okta для входа. После того, как они вошли в систему, прокси-сервер OAuth2 установит файл cookie сеанса. Прокси-сервер OAuth2 проверит сеанс перед передачей запроса эхо-веб-приложению в будущих запросах.

 В терминах OAuth прокси-сервер OAuth2 действует как «клиент», обрабатывая детали протокола OAuth (в данном случае — предоставление кода авторизации ).
TL;DR — перенаправляет пользователя на страницу входа OAuth IdP и обрабатывает маршрут «обратного вызова», чтобы вернуть его в приложение.

Диаграмма последовательности, показывающая перенаправление авторизации и действительный запрос

Этот пример будет перерастать вручную с помощью docker runкоманд; переключитесь на использование docker composeдля запуска эхо-веб-приложения и oauth2-proxy.

Начнем с простого и постепенно усложняем. Создайте docker-compose.ymlс oauth2-proxy и веб-приложением выше:

version: "3.7"
services:

  web-app: 
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    command:
      - --http-address
      - 0.0.0.0:4180 
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/ 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER}
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost:4180/oauth2/callback 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

    ports:
      - 4180:4180 
 Создайте и запустите Dockerfile в текущем каталоге.
 Слушайте в порту 4180.
 Прокси-аутентифицированные запросы к контейнеру веб-приложения Java.
 Информация о клиенте OIDC (эмитент, идентификатор клиента и секрет клиента), эти значения определены в .envфайле.
 При желании передайте доступ к веб-приложению.
 Разрешите все домены электронной почты, если вы не используете поставщика социальной аутентификации, вы захотите управлять этим в своем IdP, а НЕ в своем приложении.
 Установите URL-адрес перенаправления на URL- httpадрес, по умолчанию это https.
 Откройте .envфайл и задайте для этой переменной случайную 32-байтовую строку base64 openssl rand -base64 32 | tr — '+/' '-_'.
 Выставить порт 4180.

Запустите все, запустив:

docker compose up

Теперь откройте браузер http://localhost:4180/echo, и вы будете перенаправлены на страницу с кнопкой входа. Нажмите кнопку, и вы будете перенаправлены обратно в приложение «эхо», и вы должны увидеть информацию о новом аутентифицированном запросе!

Скриншот страницы входа по умолчанию oauth2-proxy

 Если вы уже вошли в свою учетную запись Okta, откройте инкогнито/приватный браузер, чтобы увидеть полный процесс входа.

Отлично, теперь приложение защищено, но нам еще нужно кое-что почистить:

Все состояние сеанса сохраняется в файле cookie.

Начальная страница входа с двойным перенаправлением должна быть удалена.

Мы еще не говорили о доступе к API.

Эти первые две проблемы можно исправить с помощью нескольких обновлений конфигурации прокси-сервера OAuth2. Отредактируйте docker-compose.ymlфайл:

       OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
+      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
+      OAUTH2_PROXY_COOKIE_NAME: SESSION 
+      OAUTH2_PROXY_COOKIE_SAMESITE: lax 
+      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
+      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis
    ports:
      - 4180:4180
+    depends_on:
+      - redis
+
+  redis:
+    image: redis:7.0.2-alpine3.16
+    volumes:
+      - cache:/data 
+
+volumes:
+  cache:
+    driver: local
 Пропустите страницу входа по умолчанию и перенаправьте напрямую на IdP.
 По умолчанию имя файла cookie _oauth2_proxy; изменить его на SESSION.
 Установите для той же политики сайта cookie значение lax; для перенаправления от OAuth IdP потребуется файл cookie сеанса.
 Используйте Redis для хранения информации о сеансе.
 Запустите контейнер Redis.
 Сохраняйте данные Redis между перезапусками.

Остановите процесс создания докера (Ctrl+C) и запустите его снова:

docker compose up

Еще раз откройте браузер http://localhost:4180/echoи откройте вкладку сети, вы увидите переименованный и теперь меньший SESSIONфайл cookie.

Вы могли бы остановиться здесь, но вы не должны. У нас все еще есть несколько проблем: клиенты API не поддерживаются, и мы не говорили о выходе из системы.

 Для следующего раздела вам понадобится токен доступа. Вы можете использовать токен доступа из x-access-tokenзаголовка вашего последнего запроса. Откройте терминал и установите переменную среды: export TOKEN={your-token-value}.

Клиенты REST API

Ради этого поста я буду считать любого клиента, который устанавливает Authorizationзаголовок HTTP, клиентом API. Например: Authorization: Bearer {access_token_here}.

Клиент API, вероятно, не может обработать ответ о перенаправлении, но ожидает 40xвозврата кода состояния.

Диаграмма последовательности, показывающая запрос API через oauth2-proxy

Сделаем шаг назад и настроим OAuth2 Proxy в качестве сервера ресурсов OAuth , который принимает токены доступа JWT. Это может быть все, что вам нужно для некоторых приложений, но если вам нужна поддержка как браузера, так и клиентов API, продолжайте читать, мы перейдем к этому в следующем разделе.

 Это распространено, но для маркеров доступа OAuth 2.0 не требуется , чтобы они были JWT. Если вы используете другого поставщика удостоверений OAuth, дважды проверьте, поддерживают ли они JWT, прежде чем продолжить.

В docker-compose.ymlфайле сократите переменные среды до минимума, необходимого для REST API:

...
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: api://default 
      OAUTH2_PROXY_SET_XAUTHREQUEST: true
      OAUTH2_PROXY_CLIENT_SECRET: this_value_is_required_but_not_used 
      OAUTH2_PROXY_COOKIE_SECRET: NOT_USED_BUT_REQUIRED_VALUE_32b_ 
...
 На самом деле мы не используем ни один из потоков OIDC, но это все равно необходимо.
 Возможно, это переменная с неправильным названием, которая указывает oauth2-proxyна необходимость проверки токена доступа JWT и «пропуска» поиска сеанса OAuth 2.0.
 Прочитайте электронную почту пользователя из subутверждения в маркере доступа.
 Используется тот же URL-адрес издателя, конечная точка JWKS будет автоматически искаться с помощью метаданных обнаружения OIDC.
 «Идентификатор клиента» на самом деле является audутверждением аудитории, а не идентификатором конкретного клиента (несколько «клиентов» API могут обращаться к одному и тому же REST API).
 «Клиентского секрета» нет, но это обязательное поле…​
 То же самое с секретом куки, эти потоки не используют куки, но поле обязательно.

Перезапустите службы. (Остановитесь и снова бегите docker compose up.)

Используя переменную среды маркера доступа, которую вы установили в предыдущем разделе, выполните следующее:

http :4180/echo "Authorization: Bearer ${TOKEN}"

Потрясающий! Теперь ваше приложение защищено для клиентов REST!

Не так быстро; теперь наши браузерные клиенты работают некорректно! Идентификатор клиента и секрет неверны, что означает, что пользователи не смогут войти в систему. Мы можем решить обе эти проблемы с помощью Nginx.

Добавьте Nginx для маршрутизации трафика

Добавление еще одного обратного прокси-сервера может показаться излишним; чтобы запрос попал в приложение, ему сначала нужно пройти через прокси-сервер Nginx и OAuth2. Однако вы уже можете использовать Nginx для балансировки нагрузки, завершения TLS или других проблем с входом.

Диаграмма последовательности, показывающая прохождение через оба запроса с помощью Nginx и oauth2-proxy

Хотя мы могли бы направлять наш трафик через оба прокси, как показано на рисунке выше, вместо этого я буду использовать auth_requestдирективу Nginx. Nginx отправит запрос REST к конечной точке прокси-сервера OAuth2, /oauth2/authиспользуя исходные заголовки запроса (включая любые файлы cookie и Authorizationзаголовки). Прокси-сервер OAuth2 ответит 202кодом состояния, если запрос действителен или 401нет.

Диаграмма последовательности, показывающая запрос с Nginx и oauth2-proxy

Эта настройка использует то же количество запросов, что и предыдущая схема, но обеспечивает дополнительную гибкость в отношении маршрутизации запросов в вышестоящее веб-приложение.

Настроить Nginx

Вернитесь docker-compose.ymlи добавьте новый serviceдля Nginx:

...
  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

Далее создайте nginx-default.conf.templateфайл. Этот блок кода немного сложен, обязательно прочитайте аннотации:

server {
    listen 80;
    server_name _;

    location = /oauth2/auth {
        internal; 
        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Scheme         $scheme;
        # nginx auth_request includes headers but not body
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
    }

    location / {
        auth_request /oauth2/auth; 

        auth_request_set $email  $upstream_http_x_auth_request_email; 
        proxy_set_header X-Email $email;
        auth_request_set $user  $upstream_http_x_auth_request_user;
        proxy_set_header X-User  $user;
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host:80;
        proxy_set_header X-Forwarded-Port 80;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Proto http;

        proxy_http_version 1.1; 
        proxy_pass http://web-app:8080/; 
    }
}
 Не раскрывайте этот маршрут внешним клиентам.
 Nginx делает запрос к oauth2-proxyREST API, чтобы проверить авторизацию для этого запроса.
 Добавьте заголовки, возвращенные из запроса аутентификации.
 HTTP 1.0 используется по умолчанию, если он не установлен.
 Отправлять аутентифицированные запросы в веб-приложение.

Перезапустите контейнеры докеров и убедитесь, что все работает с HTTPie (убедитесь, что вы сейчас используете порт 80).

http localhost/echo "Authorization: Bearer ${TOKEN}"

Если вы удалите или измените Authorizationзаголовок, 401будет возвращен a. Запросы браузера теперь также будут возвращать 401!

Почти сделано! Нам все еще нужно заставить все работать как для клиентов API, так и для браузеров, а также обрабатывать запросы на выход.

Маршрутизировать весь трафик через Nginx

Отправка всего трафика через Nginx имеет дополнительное преимущество, поскольку дает вам контроль над тем, как отображаются конечные точки прокси-сервера OAuth2. Например, в предыдущем разделе /oauth2/authмаршрут был помечен как «внутренний», поэтому его auth_requstможет использовать только директива.

В nginx-default.conf.templateфайле добавьте пару новых locationразделов, чтобы открыть доступ к другим /oauth2конечным точкам. Первый locationбудет обрабатывать запросы, связанные с OAuth 2.0, такие как обратный вызов перенаправления. Второй настроит конечную точку выхода для приема только запросов POST. (Это предотвращает завершение пользовательского сеанса мошенническим запросом GET.)

    location /oauth2/ {
        proxy_pass       http://oauth2-proxy:4180; 
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }

    location = /oauth2/sign_out { 
        # Sign-out mutates the session, only allow POST requests
        if ($request_method != POST) {
            return 405;
        }

        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }
...
 Отправьте запросы обратного вызова OAuth и выхода из системы на oauth2-proxy.
 Разрешить запросы POST только к конечной точке выхода.
 Конечная точка выхода не использует токен CSRF. TODO: ссылка на пост Алисы в этой теме.

Последнее изменение: обновите location /раздел, чтобы перенаправить на страницу входа для всех клиентов, не использующих API:

location / {
        auth_request /oauth2/auth;

        # if the authorization header was set (i.e. `Authorization: Bearer {token}`)
        # assume API client and do NOT redirect to login page
        if ($http_authorization = "") {
            error_page 401 = /oauth2/start;
        }
...

Настройте прокси-сервер OAuth2 для поддержки клиентов API и браузера.

Иногда приложению необходимо обрабатывать запросы от браузеров и других клиентов API. В этом случае приложение действует как клиент OAuth и сервер ресурсов. Прокси-сервер OAuth2 можно настроить для поддержки обоих типов приложений. Однако вы могли заметить, что некоторые значения конфигурации прокси-сервера OAuth2 перегружены; например, «идентификатор клиента» используется как идентификатор клиента OAuth, так и значение JWT для аудитории. К счастью, есть обходной путь! Вот финальная аннотация docker-compose.yml:

version: "3.7"

services:

  web-app:
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    depends_on:
      - redis
    command:
      - --http-address
      - 0.0.0.0:4180
    environment:
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost/oauth2/callback 

      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}

      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EXTRA_AUDIENCES: api://default 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 

      OAUTH2_PROXY_SET_XAUTHREQUEST: true 
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 

      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis

      OAUTH2_PROXY_COOKIE_REFRESH: 30m 
      OAUTH2_PROXY_COOKIE_NAME: SESSION 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

  redis:
    image: redis:7.0.2-alpine3.16
    volumes:
      - cache:/data

volumes:
  cache:
    driver: local
 Разрешить все адреса электронной почты; IdP будет управлять тем, какие пользователи имеют доступ.
 В случаях использования одного поставщика удостоверений пропустите промежуточную страницу входа.
 oauth2-proxy по умолчанию имеет значение https, этот пример используется httpна локальном хосте.
 .envИз файла будут загружены эмитент, идентификатор клиента и секрет .
 Разрешить обработку токенов носителя JWT для клиентов API.
 Настройте дополнительную «разрешенную» аудиторию в дополнение к «идентификатору клиента».
 Используйте subутверждение из токенов доступа JWT в качестве адреса электронной почты.
 Добавьте заголовки с информацией о пользователе в запрос проксируемого веб-приложения.
 Необязательно, передайте токен доступа в запрос проксируемого веб-приложения.
 Используйте Redis для управления сессиями.
 Обновлять куки каждые 30 минут.
 Установите имя файла cookie сеанса на SESSION.
 Настройте ключ шифрования (загружается из .envфайла).

Перезапустите службы и войдите в приложение через браузер: http://localhost/echo. Попробуйте еще раз, используя HTTPie:

http localhost/echo "Authorization: Bearer ${TOKEN}"

Оба запроса должны показывать одинаковую информацию.

Без каких-либо изменений кода веб-приложение «эхо» теперь защищено с помощью OIDC / OAuth 2.0!

Подходит ли прокси-сервер OAuth2 для вашего приложения?

В этом посте вы узнали, как защитить существующее приложение с помощью OAuth 2.0 без каких-либо изменений кода! Прокси-сервер OAuth2 предназначен не только для устаревших приложений; он также часто используется в шаблоне sidecar с Kubernetes, позволяя вам отделить проблемы авторизации от вашего приложения.

Однако это не идеально; есть несколько недостатков:

Выход из системы единого входа ( OIDC RP-Initated Logout ) пока не поддерживается.

Требуется обойти маршрут выхода из системы, который принимает GETзапросы и не требует токена CSRF.

Сценарии использования API поддерживают токены доступа только для JWT; если вашему приложению необходимо удаленно проверять токены , вам потребуется другое решение. Вместо этого может быть проще настроить модуль JWT Nginx .

Конфигурация немного неуклюжая; некоторые значения требуются, но не используются.

Где светит?

Если вам нужно добавить аутентификацию в приложение, которое не поддерживает OAuth, или вам нужно стандартно поддерживать различные типы приложений, прокси-сервер OAuth2 может стать отличным выбором! В качестве бонуса у него отличная документация .

Ссылка: https://developer.okta.com/blog/2022/07/14/add-auth-to-any-app-with-oauth2-proxy

#auth #oauth

Добавьте аутентификацию в любое приложение с помощью OAuth2 Proxy
高橋  陽子

高橋 陽子

1658992736

如何使用 OAuth2 代理向任何應用程序添加身份驗證

更新應用程序以使用 OAuth 2.0 並不需要很複雜。大多數時候,您的語言或框架已經有一個 OAuth 庫。有時,情況並非如此,您需要找到替代方案。在這篇文章中,我將介紹如何設置和使用 OAuth2 代理來保護您的應用程序,而無需更改任何代碼!

OAuth2 代理是一個反向代理,位於您的應用程序前面,為您處理 OpenID Connect / OAuth 2.0 的複雜性;進入您的應用程序的請求已被授權!

OAuth2 Proxy基本使用圖

先決條件

沒有安全性的應用程序

首先,我們需要一個應用程序。您可以使用任何 Web 應用程序,但在本文中,我將堅持使用 Java Spring Boot 應用程序,該應用程序將回顯入站 HTTP 請求的詳細信息。回顯請求信息將有助於可視化 OAuth2 代理添加的附加 HTTP 標頭,例如用戶的電子郵件地址。

您可以從GitHub獲取項目:

git clone https://github.com/oktadev/okta-oauth2-proxy-example.git -b start
cd okta-oauth2-proxy-example
 如果您想查看已完成的項目,請查看main分支。

如果您對此 Java 應用程序的詳細信息感興趣,請查看EchoApplication該類。它包含一個端點,它將處理所有請求並將請求的內容“轉儲”為 JSON 對象:

@RestController
static class EchoRestController {
    @RequestMapping("/**")
    Map<String, Object> echo(HttpServletRequest request,
                             @RequestHeader HttpHeaders headers,
                             @RequestBody(required = false) Map<String, Object> body) {

        Instant now = Instant.now();
        Cookie[] cookies = request.getCookies();

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("clientIpAddress", request.getRemoteAddr());
        result.put("cookies", cookies == null ? emptyList()
                                              : Arrays.stream(cookies).toList());
        result.put("headers", headers.toSingleValueMap()); // simplify json response
        result.put("httpVersion", request.getProtocol());
        result.put("method", request.getMethod());
        result.put("body", body);
        result.put("queryString", request.getQueryString());
        result.put("startedDateTime", now);
        result.put("url", request.getRequestURL());

        return result;
    }
}

如果您是 Java 愛好者,您可以通過運行./mvnw spring-boot:run. 其他人都可以運行以下 Docker 命令:

docker build --tag echo-app .
docker run -p 8080:8080 echo-app

應用程序運行後,打開瀏覽器或使用 HTTPie 在終端中訪問 Web 應用程序:

http localhost:8080/echo
 使用 HTTPie 時,您可以省略“localhost”,只需鍵入http :8080/echo! 😎

您將看到如下所示的響應:

{
    "body": null,
    "clientIpAddress": "172.17.0.1",
    "cookies": [],
    "headers": {
        "accept": "*/*",
        "accept-encoding": "gzip, deflate",
        "connection": "keep-alive",
        "host": "localhost:8080",
        "user-agent": "HTTPie/3.2.1"
    },
    "httpVersion": "HTTP/1.1",
    "method": "GET",
    "queryString": null,
    "startedDateTime": "2022-06-24T18:52:07.853663255Z",
    "url": "http://localhost:8080/echo"
}

現在我們有了一個工作應用程序,讓我們保護它!

在繼續下一部分之前,使用 Ctrl+C 停止應用程序,別擔心;我們將在一分鐘內啟動它。

設置 Okta

為了使用 OAuth 2.0 保護我們的應用程序,我們需要一個 OAuth 身份提供者 (IdP)。幾乎任何支持 OpenID Connect (OIDC) 的服務器都應該可以工作,例如Auth0Keycloak,但這是一個 Okta 博客,所以讓我們使用 Okta。

如果您還沒有,則需要一個免費的 Okta 開發者帳戶。安裝Okta CLI並從項目目錄運行okta start以註冊新帳戶並配置此應用程序!

 如果您已經有 Okta 帳戶,okta login請先運行。

Okta CLI 有什麼作用?

Okta CLI 將在您的 Okta 組織中創建一個 OIDC Web 應用程序。它將添加所需的重定向 URI 並授予對 Everyone 組的訪問權限。完成後,您將看到如下輸出:

Okta application configuration has been written to: .env

運行cat .env(或type .env在 Windows 上)以查看您的應用的頒發者和憑據。

ISSUER=https://dev-133337.okta.com/oauth2/default
CLIENT_ID=0oab8eb55Kb9jdMIr5d6
CLIENT_SECRET=NEVER-SHOW-SECRETS

設置 OAuth2 代理

有兩種使用 OAuth2 代理的方法:直接通過它路由您的流量或將其與 Nginxauth_request指令一起使用。如果可能,我建議通過 Nginx 路由流量,但我將介紹這兩個選項並在下面解釋我的建議。

為了減少移動部件的數量,我將在第一個示例中省略 Nginx;我們所有的網絡流量都將通過 OAuth2 代理。訪問 echo Web 應用程序的用戶將被重定向到 Okta 進行登錄。一旦他們登錄,OAuth2 代理將設置會話 cookie。OAuth2 代理將在將來的請求中將請求傳遞給 echo Web 應用程序之前驗證會話。

 在 OAuth 術語中,OAuth2 代理充當“客戶端”,處理 OAuth 協議詳細信息(在本例中為Authorization Code Grant)。
TL;DR - 將用戶重定向到 OAuth IdP 的登錄頁面並處理“回調”路由以將其返回到應用程序。

顯示身份驗證重定向和有效請求的序列圖

此示例將使用docker run命令手動擴展;切換到使用docker compose來啟動 echo web-app 和 oauth2-proxy。

讓我們從簡單開始,隨著我們的進展增加複雜性。docker-compose.yml使用 oauth2-proxy 和上面的 Web 應用程序創建一個:

version: "3.7"
services:

  web-app: 
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    command:
      - --http-address
      - 0.0.0.0:4180 
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/ 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER}
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost:4180/oauth2/callback 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

    ports:
      - 4180:4180 
 在當前目錄中構建並運行 Dockerfile。
 監聽端口4180
 將經過身份驗證的請求代理到 Java web-app 容器。
 OIDC 客戶端信息(頒發者、客戶端 ID 和客戶端密碼),這些值在.env文件中定義。
 (可選)將訪問權限傳遞給網絡應用程序。
 除非您使用社交身份驗證提供商,否則允許所有電子郵件域,您需要在您的 IdP 中而不是在您的應用程序中管理它。
 將重定向 URL 設置為httpURL,默認為https.
 打開.env文件並將此變量設置為隨機的 32 字節 base64 字符串openssl rand -base64 32 | tr — '+/' '-_'
 暴露端口4180

通過運行啟動一切:

docker compose up

現在打開瀏覽器到http://localhost:4180/echo,您將被重定向到帶有登錄按鈕的頁面。單擊按鈕,您將被重定向回“echo”應用程序,您應該會看到有關新驗證請求的信息!

oauth2-proxy 默認登錄頁面截圖

 如果您已登錄 Okta 帳戶,請打開隱身/私人瀏覽器以查看完整的登錄流程。

太好了,應用程序現在是安全的,但我們還有一些事情需要清理:

所有會話狀態都存儲在 cookie 中。

初始的雙重重定向登錄頁面必須去。

我們還沒有談到 API 訪問。

前兩個問題可以通過對 OAuth2 代理配置進行一些更新來解決。編輯docker-compose.yml文件:

       OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
+      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
+      OAUTH2_PROXY_COOKIE_NAME: SESSION 
+      OAUTH2_PROXY_COOKIE_SAMESITE: lax 
+      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
+      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis
    ports:
      - 4180:4180
+    depends_on:
+      - redis
+
+  redis:
+    image: redis:7.0.2-alpine3.16
+    volumes:
+      - cache:/data 
+
+volumes:
+  cache:
+    driver: local
 跳過默認登錄頁面並直接重定向到 IdP。
 默認情況下,cookie 名稱是_oauth2_proxy; 將其更改為SESSION.
 將 cookie 的相同站點策略設置為lax; 來自 OAuth IdP 的重定向將需要會話 cookie。
 使用 Redis 存儲會話信息。
 啟動一個 Redis 容器。
 在重新啟動之間保留 Redis 數據。

停止 docker-compose 進程 (Ctrl+C) 並再次啟動它:

docker compose up

再次打開您的瀏覽器http://localhost:4180/echo並打開您的網絡選項卡,您將看到重命名且現在更小的SESSIONcookie。

可以在這裡停下來,但你不應該。我們還有幾個問題:不支持 API 客戶端,我們還沒有談到註銷。

 對於下一部分,您將需要一個訪問令牌。您可以使用上一個請求x-access-token標頭中的訪問令牌。打開終端並設置環境變量:export TOKEN={your-token-value}.

REST API 客戶端

為了這篇文章,我將把任何設置AuthorizationHTTP 標頭的客戶端視為 API 客戶端。例如:Authorization: Bearer {access_token_here}

API 客戶端可能無法處理重定向響應,但希望40x返回狀態代碼。

顯示通過 oauth2-proxy 的 API 請求的序列圖

讓我們退後一步,將 OAuth2 代理配置為OAuth 資源服務器,它接受 JWT 訪問令牌。這可能是您對某些應用程序所需要的全部內容,但如果您需要同時支持瀏覽器和 API 客戶端,請繼續閱讀,我們將在下一節中介紹。

 這很常見,但OAuth 2.0 訪問令牌不需要是JWT。如果您使用不同的 OAuth IdP,請在繼續之前仔細檢查它們是否支持 JWT。

在 中docker-compose.yml,將環境變量減少到 REST API 所需的最低限度:

...
    environment:
      OAUTH2_PROXY_UPSTREAMS: http://web-app:8080/
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: api://default 
      OAUTH2_PROXY_SET_XAUTHREQUEST: true
      OAUTH2_PROXY_CLIENT_SECRET: this_value_is_required_but_not_used 
      OAUTH2_PROXY_COOKIE_SECRET: NOT_USED_BUT_REQUIRED_VALUE_32b_ 
...
 我們實際上並沒有使用任何 OIDC 流,但這仍然是必需的。
 也許是一個命名不當的變量,它告訴oauth2-proxy驗證 JWT 訪問令牌並“跳過”尋找 OAuth 2.0 會話。
 sub從訪問令牌中的聲明中讀取用戶的電子郵件。
 使用相同的頒發者 URL,將通過 OIDC 發現元數據自動查找 JWKS 端點。
 “client-id”實際上是受眾aud聲明,而不是特定客戶端的 ID(多個 API“客戶端”可能正在訪問同一個 REST API)。
 沒有“客戶機密”,但它是必填字段……​
 與 cookie 機密相同,這些流不使用 cookie,但該字段是必需的。

重新啟動服務。(停止然後docker compose up再次運行。)

使用您在上一節中設置的訪問令牌環境變量,運行以下命令:

http :4180/echo "Authorization: Bearer ${TOKEN}"

驚人的!現在您的應用程序對於 REST 客戶端是安全的!

沒那麼快;現在我們的瀏覽器客戶端無法正常工作!客戶端 ID 和密碼不正確,這意味著用戶將無法登錄。我們可以使用 Nginx 解決這兩個問題。

添加 Nginx 來路由流量

在組合中添加另一個反向代理似乎有些過分。對於到達應用程序的請求,它需要首先通過 Nginx 和 OAuth2 代理。但是,您可能已經將 Nginx 用於負載平衡、TLS 終止或其他入口問題。

序列圖顯示了使用 Nginx 和 oauth2-proxy 的請求

雖然我們可以通過兩個代理路由我們的流量,如上圖所示,但我將使用 Nginxauth_request指令。Nginx 將/oauth2/auth使用原始請求標頭(包括任何 cookie 和Authorization標頭)向 OAuth2 代理的端點發出 REST 請求。202如果請求有效或無效,OAuth2 代理將使用狀態碼進行響應401

顯示使用 Nginx 和 oauth2-proxy 的請求的序列圖

此設置使用與上圖相同數量的請求,但在如何將請求路由到上游 Web 應用程序方面提供了額外的靈活性。

配置 Nginx

跳回並為 Nginxdocker-compose.yml添加一個新的:service

...
  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

接下來,創建一個nginx-default.conf.template文件。這個代碼塊有點牽扯,請務必閱讀註釋:

server {
    listen 80;
    server_name _;

    location = /oauth2/auth {
        internal; 
        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Scheme         $scheme;
        # nginx auth_request includes headers but not body
        proxy_set_header Content-Length   "";
        proxy_pass_request_body           off;
    }

    location / {
        auth_request /oauth2/auth; 

        auth_request_set $email  $upstream_http_x_auth_request_email; 
        proxy_set_header X-Email $email;
        auth_request_set $user  $upstream_http_x_auth_request_user;
        proxy_set_header X-User  $user;
        auth_request_set $token  $upstream_http_x_auth_request_access_token;
        proxy_set_header X-Access-Token $token;
        auth_request_set $auth_cookie $upstream_http_set_cookie;
        add_header Set-Cookie $auth_cookie;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host:80;
        proxy_set_header X-Forwarded-Port 80;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-Proto http;

        proxy_http_version 1.1; 
        proxy_pass http://web-app:8080/; 
    }
}
 不要將此路由公開給外部客戶端。
 Nginx 向 REST API 發出請求oauth2-proxy,以驗證此請求的身份驗證。
 添加從身份驗證請求返回的標頭。
 如果未設置,HTTP 1.0 是默認值。
 向 Web 應用程序發送經過身份驗證的請求。

重新啟動 docker 容器並驗證一切都與 HTTPie 一起工作(確保您80現在正在使用端口)。

http localhost/echo "Authorization: Bearer ${TOKEN}"

如果您刪除或更改Authorization標題,401將返回 a。瀏覽器請求現在也將返回一個401!

快完成了!我們仍然需要讓 API 客戶端和瀏覽器都能正常工作,並處理退出請求。

通過 Nginx 路由所有流量

通過 Nginx 發送所有流量還有一個額外的好處,就是讓您可以控制 OAuth2 代理端點的公開方式。例如,上一節將/oauth2/auth路由標記為“內部”,因此只有auth_requst指令可以使用它。

在 中nginx-default.conf.template,添加幾個新location部分以公開其他/oauth2端點。第一個location將處理與 OAuth 2.0 相關的請求,例如重定向回調。第二個將配置註銷端點以僅接受 POST 請求。(這可以防止惡意 GET 請求結束用戶的會話。)

    location /oauth2/ {
        proxy_pass       http://oauth2-proxy:4180; 
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }

    location = /oauth2/sign_out { 
        # Sign-out mutates the session, only allow POST requests
        if ($request_method != POST) {
            return 405;
        }

        proxy_pass       http://oauth2-proxy:4180;
        proxy_set_header Host                    $host;
        proxy_set_header X-Real-IP               $remote_addr;
        proxy_set_header X-Scheme                $scheme;
    }
...
 將 OAuth 回調和註銷請求發送到 oauth2-proxy。
 僅允許向註銷端點發出 POST 請求。
 註銷端點不使用 CSRF 令牌。TODO:鏈接到 Alisa 關於此主題的帖子。

最後一項更改,更新該location /部分以重定向到所有非 API 客戶端的登錄頁面:

location / {
        auth_request /oauth2/auth;

        # if the authorization header was set (i.e. `Authorization: Bearer {token}`)
        # assume API client and do NOT redirect to login page
        if ($http_authorization = "") {
            error_page 401 = /oauth2/start;
        }
...

配置 OAuth2 代理以支持 API 和瀏覽器客戶端

有時,應用程序需要處理來自瀏覽器和其他 API 客戶端的請求。在這種情況下,應用程序既充當 OAuth 客戶端又充當資源服務器。OAuth2 代理可以配置為支持這兩種類型的應用程序。但是,您可能已經註意到一些 OAuth2 代理配置值被重載;例如,“客戶端 ID”既用作 OAuth 客戶端的 ID,也用作受眾的 JWT 值。幸運的是,有一個解決方法!這是最後的註釋docker-compose.yml

version: "3.7"

services:

  web-app:
    build: .

  oauth2-proxy:
    image: bitnami/oauth2-proxy:7.3.0
    depends_on:
      - redis
    command:
      - --http-address
      - 0.0.0.0:4180
    environment:
      OAUTH2_PROXY_EMAIL_DOMAINS: '*' 
      OAUTH2_PROXY_PROVIDER: oidc 
      OAUTH2_PROXY_PROVIDER_DISPLAY_NAME: Okta
      OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: true 
      OAUTH2_PROXY_REDIRECT_URL: http://localhost/oauth2/callback 

      OAUTH2_PROXY_OIDC_ISSUER_URL: ${ISSUER} 
      OAUTH2_PROXY_CLIENT_ID: ${CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${CLIENT_SECRET}

      OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS: true 
      OAUTH2_PROXY_OIDC_EXTRA_AUDIENCES: api://default 
      OAUTH2_PROXY_OIDC_EMAIL_CLAIM: sub 

      OAUTH2_PROXY_SET_XAUTHREQUEST: true 
      OAUTH2_PROXY_PASS_ACCESS_TOKEN: true 

      OAUTH2_PROXY_SESSION_STORE_TYPE: redis 
      OAUTH2_PROXY_REDIS_CONNECTION_URL: redis://redis

      OAUTH2_PROXY_COOKIE_REFRESH: 30m 
      OAUTH2_PROXY_COOKIE_NAME: SESSION 
      OAUTH2_PROXY_COOKIE_SECRET: ${OAUTH2_PROXY_COOKIE_SECRET} 

  nginx:
    image: nginx:1.21.6-alpine
    depends_on:
      - oauth2-proxy
      - web-app
    volumes:
      - ./nginx-default.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80

  redis:
    image: redis:7.0.2-alpine3.16
    volumes:
      - cache:/data

volumes:
  cache:
    driver: local
 允許所有電子郵件地址;IdP 將管理哪些用戶可以訪問。
 對於單個 IdP 用例,請跳過中間登錄頁面。
 oauth2-proxy 默認為https,本示例http在 localhost 上使用。
 將從.env文件中加載頒發者、客戶端 ID 和密鑰。
 允許為 API 客戶端處理 JWT 不記名令牌。
 除了“客戶端 ID”之外,還配置一個額外的“允許”受眾。
 使用sub來自 JWT 訪問令牌的聲明作為電子郵件地址。
 將用戶信息標頭添加到代理的 Web 應用請求。
 可選,將訪問令牌傳遞給代理的 Web 應用請求。
 使用 Redis 進行會話管理。
 每 30 分鐘刷新一次 cookie。
 將會話 cookie 名稱設置為SESSION.
 配置加密密鑰(從.env文件加載)。

重新啟動服務並通過瀏覽器訪問應用程序:http://localhost/echo. 使用 HTTPie 再試一次:

http localhost/echo "Authorization: Bearer ${TOKEN}"

兩個請求都應該顯示相似的信息。

在沒有任何代碼更改的情況下,“echo”網絡應用程序現在使用 OIDC / OAuth 2.0 進行保護!

OAuth2 代理是否適合您的應用程序?

這篇文章教您如何在不更改任何代碼的情況下使用 OAuth 2.0 保護現有應用程序!OAuth2 代理不僅適用於遺留應用程序;它也常用於 Kubernetes 的 sidecar 模式,允許您將授權問題與應用程序分開。

不過,它並不完美。有幾個缺點:

尚不支持SSO 註銷(OIDC RP-Initated Logout)。

需要解決接受GET請求且不需要 CSRF 令牌的註銷路由。

API 用例支持僅 JWT 訪問令牌;如果您的應用程序需要遠程驗證令牌,您將需要另一個解決方案。相反,配置Nginx 的 JWT 模塊可能更容易。

配置有點笨拙;某些值是必需的,但未使用。

它在哪裡發光?

如果您需要為不支持 OAuth 的應用程序添加身份驗證,或者您需要以標準方式支持各種類型的應用程序,OAuth2 Proxy 可能是一個不錯的選擇!作為獎勵,它具有出色的文檔

鏈接:https ://developer.okta.com/blog/2022/07/14/add-auth-to-any-app-with-oauth2-proxy

#auth #oauth

如何使用 OAuth2 代理向任何應用程序添加身份驗證
Hunter  Krajcik

Hunter Krajcik

1658703960

Flutter_dauth: A Flutter Package for Authentication with DAuth

A flutter package for authentication with DAuth(an OAuth2 based SSO (Single Sign On) for NITT students) authorisations service on behalf of the resource-owner/user. DAuth lets the application developers securely get access to users’ data without users having to share their passwords.

DAuth allows a Client-App (the program using this library) to access and manipulate a resource that's owned by a resource owner (the end user) and lives on a remote server. The Client-App directs the resource owner to dauth authorization server, where the resource owner tells the authorization server to give the Client-App an access token. This token serves as proof that the client has permission to access resources on behalf of the resource owner.

Note: OAuth2 provides several different methods for the client to obtain authorization.But, currently This package only supports Authorisation Code Grant

Features

AuthorisationCodeGrant

  • This Package Allows user to get the authorized token by calling fetchToken(authorizationRequest), which automates the following workflow:
    • Generates authorizationUrl using the provided authorizationRequest in the parameter.
    • Opens up a webView with the generated authorizationUrl and Listens to the NavigationRequests.
    • Allows user to enable permissions to Client-App to access the resource of the user from Dauth-Resource-Provider.
    • After Authentication server redirects to the registered redirect_uri and code is fetched by listening to the NavigationRequest.
    • Using the code as body parameter a post-request is automated to retrive the token.
  • Once the tokenResponse is fetched the user can send a post request using fetchResources(token) and get the protectedResources based on the Scope mentioned.

DataTypes

DataTypesParametersDescription
ResourceResponseString? tokenType, String? accessToken, String? state, int? expiresIn,String? idToken,String? status,ErrorResponse? errorResponseResponse-body returned from fetchResources() request
TokenResponseString? email,String? id,String? name,String? phoneNumber,String? gender,DateTime? createdAt,DateTime? updatedAt,Response-body returned from fetchToken() request
Scopebool isOpenId, bool isEmail, bool isProfile, bool isUserConsists of 4 boolean parameters to enable SCOPE of Resource Access
TokenRequestString? clientId,String? codeVerifier,String codeChallengeMethod,String? redirectUri,String? responseType,String? grantType,String? state,String? scope,String? nonceRequest-Parameter for fetchToken()

Methods

MethodsParameters
TokenResponse fetchToken()TokenRequest request
ResourceResponse fetchResource()String access_token
Widget DauthButton()Function OnPressed: (TokenResponse res){}

Getting started

To use this package:

  • Run the following command in terminal
flutter pub get flutter_dauth
  • OR
  • Add the following in pubspec.yaml file
dependencies:
    flutter:
        sdk: flutter
    flutter_dauth:   

Usage

Following is an example of Authorization Grant Code using this package.

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomePage(),
      );
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  State<StatefulWidget> createState() => HomeState();
}

class HomeState extends State<HomePage> {
  //A string object used in Text() widget as data.
  String _exampleText = 'Flutter Application';

  //Create a TokenRequest Object
  final dauth.TokenRequest _request = TokenRequest(
     //Your Client-Id provided by Dauth Server at the time of registration.
      clientId: 'YOUR CLIENT ID',
      //redirectUri provided by You to Dauth Server at the time of registration.
      redirectUri: 'YOUR REDIRECT URI',
      //A String which will retured with access_token for token verification in client side.
      state: 'STATE',
      //setting isUser to true to retrive UserDetails in ResourceResponse from Dauth server.
      scope: const dauth.Scope(isUser: true),
      //codeChallengeMethod Should be specified as `plain` or `S256` based on thier requirement.
      codeChallengeMethod: 'S256');

  @override
  Widget build(BuildContext context) => SafeArea(
          child: Scaffold(
              body: Container(
        color: Colors.blueGrey,
        child: Stack(
          children: [
            Center(
                child: Text(
              _exampleText,
              style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
            )),
            Positioned(
                left: 50,
                right: 50,
                bottom: 10,
                //DAuth button returns TokenResponse and ResponseMessage when pressed.
                child: dauth.DauthButton(
                    request: _request,
                    onPressed:
                        (dauth.TokenResponse res) {
                      //changes the exampleText as Token_TYPE: <YOUR_TOKEN> from the previous string if the response is success'
                        setState(() {
                          _exampleText = 'Token_TYPE: ' +
                              (res)
                                  .tokenType
                                  .toString();
                        });
                    }))
          ],
        ),
      )));
}

Installing

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_dauth

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_dauth: ^0.0.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_dauth/flutter_dauth.dart';

example/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dauth/src/model/requests/token_request.dart';
import 'package:flutter_dauth/flutter_dauth.dart' as dauth;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomePage(),
      );
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  State<StatefulWidget> createState() => HomeState();
}

class HomeState extends State<HomePage> {
  //A string object used in Text() widget as data.
  String _exampleText = 'Flutter Application';

  //Create a TokenRequest Object
  final dauth.TokenRequest _request = TokenRequest(
      //Your Client-Id provided by Dauth Server at the time of registration.
      clientId: 'YOUR CLIENT ID',
      //redirectUri provided by You to Dauth Server at the time of registration.
      redirectUri: 'YOUR REDIRECT URI',
      //A String which will retured with access_token for token verification in client side.
      state: 'STATE',
      //setting isUser to true to retrive UserDetails in ResourceResponse from Dauth server.
      scope: const dauth.Scope(isUser: true),
      //codeChallengeMethod Should be specified as `plain` or `S256` based on thier requirement.
      codeChallengeMethod: 'S256');

  @override
  Widget build(BuildContext context) => SafeArea(
          child: Scaffold(
              body: Container(
        color: Colors.blue,
        child: Stack(
          children: [
            Center(
                child: Text(
              _exampleText,
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            )),
            Positioned(
                left: 50,
                right: 50,
                bottom: 10,
                //DAuth button returns TokenResponse and ResponseMessage when pressed.
                child: dauth.DauthButton(
                    request: _request,
                    onPressed: (dauth.TokenResponse res) {
                      //changes the exampleText as Token_TYPE: <YOUR_TOKEN> from the previous string if the response is success'
                      setState(() {
                        _exampleText = 'Token: ' + res.tokenType.toString();
                      });
                    }))
          ],
        ),
      )));
}

Updates

  • To Ensure Security issues related to Interception attacks PKCE is added with Authorisation Code Grant.

Issues/Upcoming Changes

  • DAuth only supports Authorisation Grant Flow at the time of writing supports, in future more methods will be added and flutter_dauth will also be updated accordingly.

Credits

This package wouldn't be possible without the following:

  • webviewx : for opening AuthorizationUrl in WebView and Listening to NavigationRequest
  • https : for HTTP requests to the Dauth-Server.
  • crypto : for SHA256 encryption.

Author: Muhesh7
Source Code: https://github.com/Muhesh7/flutter_dauth 
License: MIT license

#flutter #dart #oauth 

Flutter_dauth: A Flutter Package for Authentication with DAuth
Royce  Reinger

Royce Reinger

1658545620

Chatterbot: A Straightforward Ruby-based Twitter Bot Framework

Chatterbot

Hey! This is Chatterbot 2.0. There have been some breaking changes from older versions of the library, and it doesn't support MySQL anymore. If you are looking for the old version, you can try the 1.0 branch

Chatterbot is a Ruby library for making bots on Twitter. It's great for rapid development of bot ideas. It handles all of the basic Twitter API features -- searches, replies, tweets, retweets, etc. and has a simple blacklist/whitelist system to help minimize spam and unwanted data.

Features

  • Handles search queries and replies to your bot
  • Use a simple scripting language, or extend a Bot class if you need it
  • Wraps the Twitter gem so you have access to the entire Twitter API
  • Simple blocklist system to limit your annoyance of users
  • Avoid your bot making a fool of itself by ignoring tweets with certain bad words

Using Chatterbot

Chatterbot has a documentation website. It's a work-in-progress. You can also read the gem documentation.

Make a Twitter account

First thing you'll need to do is create an account for your bot on Twitter. That's the easy part.

Run the generator

Chatterbot comes with a script named chatterbot-register which will handle two tasks -- it will authorize your bot with Twitter and it will generate a skeleton script, which you use to implement your actual bot.

Write your bot

A bot using chatterbot can be as simple as this:

exclude "http://"
blocklist "mean_user, private_user"

puts "checking my timeline"
home_timeline do |tweet|
    # i like to favorite things
    favorite tweet
end

puts "checking for replies to my tweets and mentions of me"
replies do |tweet|
  text = tweet.text
  puts "message received: #{text}"
  src = text.gsub(/@echoes_bot/, "#USER#")  

  # send it back!
  reply src, tweet
end

Or you can write a bot using more traditional ruby classes, extend it if needed, and use it like so:

  class MyBot < Chatterbot::Bot
     def do_stuff
       home_timeline do |tweet|
         puts "I like to favorite things"
         favorite tweet
       end
    end
  end

Chatterbot can actually generate a template bot file for you, and will walk you through process of getting a bot authorized with Twitter.

That's it!

Chatterbot uses the the Twitter gem (https://github.com/sferik/twitter) to handle the underlying API calls. Any calls to the search/reply methods will return Twitter::Status objects, which are basically extended hashes. If you find yourself pushing the limits of Chatterbot, it's very possible that you should just be using the Twitter gem directly.

Streaming

Chatterbot used to have some basic support for the Streaming API, but I've removed it because Twitter is removing and/or restricting access to those APIs. If you need the Streaming API, I highly recommend using the the Twitter gem. Chatterbot is built on the Twitter gem, it works great, and has support for streaming.

What Can I Do?

Here's a list of the important methods in the Chatterbot DSL:

search -- You can use this to perform a search on Twitter:

search("pizza") do |tweet|
  tweet "I just read another tweet about pizza"
end

replies -- Use this to check for replies and mentions:

replies do |tweet|
  reply "#USER# Thanks for contacting me!", tweet
end

Note that the string #USER# will be replaced with the username of the person who sent the original tweet.

home_timeline -- This call will return tweets from the bot's home timeline -- this will include tweets from accounts the bot follows, as well as the bot's own tweets:

home_timeline do |tweet|
  puts tweet.text # this is the actual tweeted text
  favorite tweet # i like to fave tweets
end

direct_messages -- This will return any DMs for the bot:

direct_messages do |dm|
  puts dm.text

  # send a DM back to the user
  direct_message "Hey, I got your message at #{Time.now.to_s}", dm.sender
end

tweet -- send a Tweet out for this bot:

tweet "I AM A BOT!!!"

reply -- reply to another tweet:

reply "THIS IS A REPLY TO #USER#!", original_tweet

retweet -- Chatterbot can retweet tweets as well:

  search "xyzzy" do |tweet|    retweet(tweet.id)  end

direct_message -- send a DM to a user:

direct_message "I am a bot sending you a direct message", user

(NOTE: you'll need to make sure your bot has permission to send DMs)

blocklist -- you can use this to specify a list of users you don't want to interact with. If you put the following line at the top of your bot:

blocklist "user1, user2, user3"

None of those users will trigger your bot if they come up in a search. However, if a user replies to one of your tweets or mentions your bot in a tweet, you will receive that tweet when calling the reply method.

exclude -- similarly, you can specify a list of words/phrases which shouldn't trigger your bot. If you use the following:

exclude "spam"

Any tweets or mentions with the word 'spam' in them will be ignored by the bot. If you wanted to ignore any tweets with links in them (a wise precaution if you want to avoid spreading spam), you could call:

exclude "http://"

followers -- get a list of your followers. This is an experimental feature but should work for most purposes.

For more details, check out dsl.rb in the source code.

Direct Client Access

If you want to do something not provided by the DSL, you have access to an instance of Twitter::Client provided by the client method. In theory, you can do something like this in your bot to unfollow users who DM you:

client.direct_messages_received(:since_id => since_id).each do |m|
    client.unfollow(m.user.name)
end

Authorization

Before you setup a bot for the first time, you will need to register an application with Twitter. Twitter requires all API communication to be via an app which is registered on Twitter. I would set one up and make it part of Chatterbot, but unfortunately Twitter doesn't allow developers to publicly post the OAuth consumer key/secret that you would need to use. If you're planning to run more than one bot, you only need to do this step once -- you can use the same app setup for other bots too.

The helper script chatterbot-register will walk you through most of this without too much hand-wringing. And, if you write a bot without chatterbot-register, you'll still be sent through the authorization process the first time you run your script. But if you prefer, here's sthe instructions if you want to do it yourself:

Setup your own app on Twitter.

Put in whatever name, description, and website you want.

Take the consumer key/consumer secret values, and either run your bot, and enter them in when prompted, or store them in a config file for your bot. (See below for details on this). It should look like this:

  ---
  :consumer_secret: CONSUMER SECRET GOES HERE
  :consumer_key: CONSUMER KEY GOES HERE

When you do this via the helper script, chatterbot will point you at the URL in Step #1, then ask you to paste the same values as in Step #4.

Once this is done, you will need to setup authorization for the actual bot with Twitter. At the first run, you'll get a message asking you to go to a Twitter URL, where you can authorize the bot to post messages for your account or not. If you accept, you'll get a PIN number back. You need to copy this and paste it back into the prompt for the bot. Hit return, and you should be all set.

This is obviously a bunch of effort, but once you're done, you're ready to go!

Configuration

Chatterbot offers a couple different methods of storing the config for your bot:

  1. Your credentials can be stored as variables in the script itself. chatterbot-register will do this for you. If your bot is using replies or searches, that data will be written to a YAML file. NOTE this is a bad practice for scripts that are stored in a source control system such as git, or are publicly available on a site like github.
  2. In a YAML file with the same name as the bot, so if you have botname.rb or a Botname class, store your config in botname.yaml
  3. In a global config file at /etc/chatterbot.yml -- values stored here will apply to any bots you run.
  4. In another global config file specified in the environment variable 'chatterbot_config'.
  5. In a global.yml file in the same directory as your bot. This gives you the ability to have a global configuration file, but keep it with your bots if desired.

Running Your Bot

There's a couple ways of running your bot:

Run it on the command-line whenever you like. Whee!

Run it via cron. Here's an example of running a bot every two minutes

*/2 * * * * . ~/.bash_profile; cd /path/to/bot/;  ./bot.rb

Run it as a background process. Just put the guts of your bot in a loop like this:

loop do
  search "twitter" do |tweet|
    # here you could do something with a tweet
    # if you want to retweet
    retweet(tweet.id)
  end

  replies do |tweet|
    # do stuff
  end

  sleep 60
end

Blocklists

Not everyone wants to hear from your bot. To keep annoyances to a minimum, Chatterbot has a simple blocklist tool. Using it is as simple as:

blocklist "mean_user, private_user"

You should really respect the wishes of users who don't want to hear from your bot, and add them to your blocklist whenever requested.

There's also an 'exclude' method which can be used to add words/phrases you might want to ignore -- for example, if you wanted to ignore tweets with links, you could do something like this:

exclude "http://"

Contributing to Chatterbot

Pull requests for bug fixes and new features are eagerly accepted. Since this code is based off of actual Twitter bots, please try and maintain compatability with the existing codebase. If you are comfortable writing a spec for any changed code, please do so. If not, I can work with you on that.

Author: Muffinista
Source Code: https://github.com/muffinista/chatterbot 
License: MIT license

#ruby #bot #oauth 

Chatterbot: A Straightforward Ruby-based Twitter Bot Framework
Rupert  Beatty

Rupert Beatty

1658073060

Socialite: Laravel Wrapper Around OAuth 1 & OAuth 2 Libraries

Introduction

Laravel Socialite provides an expressive, fluent interface to OAuth authentication with Facebook, Twitter, Google, LinkedIn, GitHub, GitLab and Bitbucket. It handles almost all of the boilerplate social authentication code you are dreading writing.

We are not accepting new adapters.

Adapters for other platforms are listed at the community driven Socialite Providers website.

Official Documentation

Documentation for Socialite can be found on the Laravel website.

Contributing

Thank you for considering contributing to Socialite! The contribution guide can be found in the Laravel documentation.

Code of Conduct

In order to ensure that the Laravel community is welcoming to all, please review and abide by the Code of Conduct.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Author: Laravel
Source Code: https://github.com/laravel/socialite 
License: MIT license

#laravel #oauth 

Socialite: Laravel Wrapper Around OAuth 1 & OAuth 2 Libraries