asaranblin

Miembro Regular
Nuevo
Se incorporó
15 Agosto 2011
Mensajes
44
Hola,

Necesito implementar un servidor (Provider) para OpenID Connect, pero tengo la siguiente duda:

¿Es necesario usar alguna librería, o se puede simular el flujo entre el cliente y el servidor con solicitudes POST y GET?

Saludos cordiales
 

Gen1us

VCP
Se incorporó
16 Octubre 2012
Mensajes
1.358
Es necesario implementar ciertas dependencia. Bajo que framework o sistema piensas integrar OpenID?

Enviado desde mi A0001 mediante Tapatalk
 
Upvote 0

asaranblin

Miembro Regular
Nuevo
Se incorporó
15 Agosto 2011
Mensajes
44
Quiero ver la posibilidad de hacerlo en Python con el Frameworks Flask.

Pero mi duda, si utilizando Python-Requests (O Curl) podría construir el protocolo OpenID Connect?.

Si alguien tiene algún ejemplo o tutorial lo agradecería.

Saudos
 
Upvote 0

Gen1us

VCP
Se incorporó
16 Octubre 2012
Mensajes
1.358
Con Flask

Flask-OpenID is an extension to Flask that allows you to add OpenID based authentication to your website in a matter of minutes. It depends on Flask andpython-openid 2.x. You can install the requirements from PyPI with easy_install orpip or download them by hand.

Features
  • support for OpenID 2.x
  • friendly API
  • perfect integration into Flask
  • basic support for AX and SReg extensions to OpenID that make it possible to fetch basic profile information from a user’s OpenID provider.
Installation
Install the extension with one of the following commands:

$ easy_install Flask-OpenID
or alternatively if you have pip installed:

$ pip install Flask-OpenID
How to Use
To integrate Flask-OpenID into your application you need to create an instance of theOpenID object first:

from flask.ext.openid import OpenID
oid = OpenID(app, '/path/to/store', safe_roots=[])

By default it will use the filesystem as store for information needed by OpenID for the authentication process. You can alternatively implement your own store that uses the database or a no-sql server. For more information about that, consult the python-openid documentation.

The path to the store can also be specified with the OPENID_FS_STORE_PATHconfiguration variable.

Alternatively the object can be instantiated without the application in which case it can later be registered for an application with the init_app() method.

The list of URL roots that are safe to redirect the user to are passed via safe_roots. Whenever the url root of the 'next' request argument is not in this list, the user will get redirected to the app root. All urls that are local to the current app are always regared as trusted. This security mechanism can be disabled by leaving safe_rootsout, but this is not suggested.

The current logged in user has to memorized somewhere, we will use the 'openid'key in the session. This can be implemented in a before_request function:

from flask import g, session

@app.before_request
def lookup_current_user():
g.user = None
if 'openid' in session:
openid = session['openid']
g.user = User.query.filter_by(openid=openid).first()

This assumes the openid used for a user is stored in the user table itself. As you can see from the example above, we’re using SQLAlchemy here, but feel free to use a different storage backend. It’s just important that you can somehow map from openid URL to user.

Next you need to define a login handling function. This function is a standard view function that is additionally decorated as loginhandler():

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
if g.user is not None:
return redirect(oid.get_next_url())
if request.method == 'POST':
openid = request.form.get('openid')
if openid:
return oid.try_login(openid, ask_for=['email', 'nickname'],
ask_for_optional=['fullname'])
return render_template('login.html', next=oid.get_next_url(),
error=oid.fetch_error())

What’s happening inside the login handler is that first we try to figure out if the user is already logged in. In that case we return to where we just came from (get_next_url() can do that for us). When the data is submitted we get the openid the user entered and try to login with that information. Additionally we ask the openid provider for email, nickname and the user’s full name, where we declare full name as optional. If that information is available, we can use it to simplify the account creation process in our application.

The template also needs the URL we want to return to, because it has to forward that information in the form. If an error happened, fetch_error() will return that error message for us.

This is what a login template typically looks like:

{% extends "layout.html" %}
{% block title %}Sign in{% endblock %}
{% block body %}
<h2>Sign in</h2>
<form action="" method=post>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}</p>{% endif %}
<p>
OpenID:
<input type=text name=openid size=30>
<input type=submit value="Sign in">
<input type=hidden name=next value="{{ next }}">
</form>
{% endblock %}

See how error and next are used. The name of the form field next is required, so don’t change it.

Responding to Successful Logins
Next we have to define a function that is called after the login was successful. The responsibility of that function is to remember the user that just logged in and to figure out if it’s a new user to the system or one with an existing profile (if you want to use profiles).

Such a function is decorated with after_login() and must remember the user in the session and redirect to the proper page:

from flask import flash

@oid.after_login
def create_or_login(resp):
session['openid'] = resp.identity_url
user = User.query.filter_by(openid=resp.identity_url).first()
if user is not None:
flash(u'Successfully signed in')
g.user = user
return redirect(oid.get_next_url())
return redirect(url_for('create_profile', next=oid.get_next_url(),
name=resp.fullname or resp.nickname,
email=resp.email))

The resp object passed is a OpenIDResponse object with all the information you might desire. As you can see, we memorize the user’s openid and try to get the user with that OpenID from the database. If that fails we redirect the user to a page to create a new profile and also forward the name (or nickname if no name is provided) and the email address. Please keep in mind that an openid provider does not have to support these profile information and not every value you ask for will be there. If it’s missing it will be None. Again make sure to not lose the information about the next URL.

Creating a Profile
A typical page to create such a profile might look like this:

@app.route('/create-profile', methods=['GET', 'POST'])
def create_profile():
if g.user is not None or 'openid' not in session:
return redirect(url_for('index'))
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
if not name:
flash(u'Error: you have to provide a name')
elif '@' not in email:
flash(u'Error: you have to enter a valid email address')
else:
flash(u'Profile successfully created')
db_session.add(User(name, email, session['openid']))
db_session.commit()
return redirect(oid.get_next_url())
return render_template('create_profile.html', next=oid.get_next_url())

If you’re using the same names for the URL parameters in the step before and in this form, you have nice looking and simple templates:

{% extends "layout.html" %}
{% block title %}Create Profile{% endblock %}
{% block body %}
<h2>Create Profile</h2>
<p>
Hey! This is the first time you signed in on this website. In
order to proceed we need a couple of more information from you:
<form action="" method=post>
<dl>
<dt>Name:
<dd><input type=text name=name size=30 value="{{ request.values.name }}">
<dt>E-Mail:
<dd><input type=text name=email size=30 value="{{ request.values.email }}">
</dl>
<p>
<input type=submit value="Create profile">
<input type=hidden name=next value="{{ next }}">
</form>
<p>
If you don't want to proceed, you can <a href="{{ url_for('logout')
}}">sign out</a> again.
{% endblock %}

Logging Out
The logout function is very simple, it just has to unset the openid from the session and redirect back to where the user was before:

@app.route('/logout')
def logout():
session.pop('openid', None)
flash(u'You were signed out')
return redirect(oid.get_next_url())

Advanced usage
Flask-OpenID can also work with any python-openid extension. To use this, pass a list of instantiated request openid.extension.Extension objects in the extensions field of try_login(). The responses of these extensions are available during theafter_login() function, as entries in resp.extensions.

Full Example
To see the full code of that example, you can download the code from github.

Changes
1.2
  • The safe_roots argument and URL security system was added.
  • The OpenID extensions system was added.
1.0
  • the OpenID object is not registered to an application which allows configuration values to be used and is also consistent with other Flask extensions.
API References
The full API reference:

class flask_openid.OpenID(app=None, fs_store_path=None, store_factory=None,fallback_endpoint=None, extension_responses=None, safe_roots=None)
Simple helper class for OpenID auth. Has to be created in advance like a Flaskobject.

There are two usage modes which work very similar. One is binding the instance to a very specific Flask application:

app = Flask(__name__)
db = OpenID(app)

The second possibility is to create the object once and configure the application later to support it:

oid = OpenID()

def create_app():
app = Flask(__name__)
oid.init_app(app)
return app

Parameters:
  • app – the application to register this openid controller with.
  • fs_store_path – if given this is the name of a folder where the OpenID auth process can store temporary information. If neither is provided a temporary folder is assumed. This is overridden by the OPENID_FS_STORE_PATH configuration key.
  • store_factory – alternatively a function that creates a python-openid store object.
  • fallback_endpoint – optionally a string with the name of an URL endpoint the user should be redirected to if the HTTP referrer is unreliable. By default the user is redirected back to the application’s index in that case.
  • extension_responses – a list of OpenID Extensions Response class.
after_login(f)
This function will be called after login. It must redirect to a different place and remember the user somewhere. The session is not modified by SimpleOpenID. The decorated function is passed a OpenIDResponse object.

attach_reg_info(auth_request, keys, optional_keys)
Attaches sreg and ax requests to the auth request.

Internal :
errorhandler(f)
Called if an error occurs with the message. By default 'openid_error' is added to the session so that fetch_error() can fetch that error from the session. Alternatively it makes sense to directly flash the error for example:

@oid.errorhandler
def on_error(message):
flash(u'Error: ' + message)

fetch_error()
Fetches the error from the session. This removes it from the session and returns that error. This method is probably useless if errorhandler() is used.

get_current_url()
the current URL + next.

get_next_url()
Returns the URL where we want to redirect to. This will always return a valid URL.

get_success_url()
Return the internal success URL.

Internal :
init_app(app)
This callback can be used to initialize an application for the use with this openid controller.

New in version 1.0.

loginhandler(f)
Marks a function as login handler. This decorator injects some more OpenID required logic. Always decorate your login function with this decorator.

signal_error(msg)
Signals an error. It does this by storing the message in the session. Useerrorhandler() to this method.

try_login(identity_url, ask_for=None, ask_for_optional=None,extensions=None)
This tries to login with the given identity URL. This function must be called from the login_handler. The ask_for and ask_for_optional`parameter can be a set of values to be asked from the openid provider, where keys in `ask_for are marked as required, and keys in ask_for_optional are marked as optional.

The following strings can be used in the ask_for and ask_for_optionalparameters: aim, blog, country, dob (date of birth), email, fullname, gender, icq,image, jabber, language, msn, nickname, phone, postcode, skype, timezone,website, yahoo

extensions can be a list of instances of OpenID extension requests that should be passed on with the request. If you use this, please make sure to pass the Response classes of these extensions when initializing OpenID.

class flask_openid.OpenIDResponse(resp, extensions)
Passed to the after_login function. Provides all the information sent from the OpenID provider. The profile information has to be requested from the server by passing a list of fields as ask_for to the try_login() function.

aim = None
AIM messenger address as string

blog = None
URL of blog as string

country = None
the country of the user as specified by ISO3166

date_of_birth = None
date of birth as datetime object.

email = None
the email address of the user

extensions = None
Hash of the response object from the OpenID Extension by the

fullname = None
the full name of the user

gender = None
the gender of the user (f for femail and m for male)

icq = None
icq messenger number as string

identity_url = None
the openid the user used for sign in

image = None
URL to profile image as string

jabber = None
jabber address as string

language = None
the user’s preferred language as specified by ISO639

month_of_birth = None
the month of birth of the user as integer (1 based)

msn = None
msn name as string

nickname = None
desired nickname of the user

phone = None
phone number of the user as string

postcode = None
free text that should conform to the user’s country’s postal system

skype = None
skype name as string

timezone = None
timezone string from the TimeZone database

website = None
URL of website as string

yahoo = None
yahoo messenger address as string

year_of_birth = None
the year of birth of the user as integer

flask_openid.COMMON_PROVIDERS
a dictionary of common provider name -> login URL mappings. This can be used to implement “click button to login” functionality.

Currently contains general purpose entrypoints for the following providers:google, yahoo, aol, and steam.






In the example below I will go through all the steps and I will use the basic Client class because it will provide interfaces to all of them. So lets start with instantiating a client:

from oic.oic import Client

c = Client()

The first choices is really not yours it’s the OpenID Connect Provider (OP) that has to decide on whether it supports dynamic provider information gathering and/or dynamic client registration.

If the OP doesn’t support client registration then you have to static register your client with the provider. Typically this is accomplished using a web page provided by the organization that runs the OP. Can’t help you with this since each provider does it differently. What you eventually must get from the service provide is a client id and a client secret.

If the service provider does not support dynamic OP information lookup, then the necessary information will probably appear on some web page somewhere. Again look to the service provider. Going through the dynamic process below you will learn what information to look for.

Issuer discovery
OIDC uses webfinger (http://tools.ietf.org/html/rfc7033)to do the OP discovery. In very general terms this means that the user that accesses the RP provides an identifier. There are a number of different syntaxes that this identifier can adhere to. The most common probably the e-mail address syntax. It’s something the looks like an e-mail address (local@domain) but not necessarily is one.

At this point in time let us assume that you will instantiated a OIDC RP. .. Note::Oh, by the way I will probably alternate between talking about the RP

and the client, don’t get caught up on that, they are the same thing.
As stated above depending on depending on the OP and the return_type you will use some of these steps may be left out or replaced with an out-of-band process.

Using pyoidc this is how you would do it:

uid = "[email protected]"
issuer = client.discover(uid)

The discover method will use webfinger to find the OIDC OP given the user identifier provided. If the user identifier follows another syntax/scheme the same method can still be used, you just have to preface the ‘uid’ value with the scheme used. The returned issuer must according to the standard be a https url, but some implementers have decided differently on this, so you may get a http url.

Provider Info discovery
When you have the provider info URL you want to get information about the OP, so you query for that:

provider_info = client.provider_config(issuer)

A description of the whole set of metadata can be found here: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata

The resulting provider_info is a dictionary, hence you can easily find the necessary information:

>> provider_info["issuer"]
'https://example.com/op'
>> provider_info["authorization_endpoint"]
'https://example.com/op/authz_endp'

The provider info is also automatically stored in the client instance. Since a RP can potentially talk to more than one OP during it’s life time the provider information is store using the issuer name as the key:

>> client.provider_info.keys()
['https://example.com/op']
>> client.provider_info["https://example.com/op"]["scopes_supported"]
['openid', 'profile', 'email']

Now, you know all about the OP. The next step would be to register the client with the OP.

Client registration
To do that you need to know the ‘registration_endpoint’. And you have to decide on a couple of things about the RP.

Things like:

  • redirect_uris
    REQUIRED. Array of Redirection URI values used by the Client.

  • response_types
    OPTIONAL. JSON array containing a list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If omitted, the default is that the Client will use only the code Response Type.

  • contacts
    OPTIONAL. Array of e-mail addresses of people responsible for this Client.
The whole list of possible parameters can be found here: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata

The only absolutely required information is the redirect_uris

So, registering a client could then be accomplished doing:

client.redirect_uris = ['https://example.com/rp/authz_cb']
registration_response = client.register(provider_info["registration_endpoint"])

You have two choices here, you can either assign the parameters with value to the client instance as in the example above or you can provide them as an argument to the method:

args = {
"redirect_uris": ['https://example.com/rp/authz_cb'],
"contacts": ["[email protected]"]
}

registration_response = client.register(
provider_info["registration_endpoint"], **args)

or a combination of the two.

Provided the registration went flawlessly you will get the registration response (an instance of a RegistrationResponse) as a result. But at the same time automatically the response will be stored in the client instance (client_info parameter).

Note

The basic Client class is expected to only talk to one OP. If your service needs to talk to several OPs that are a couple of patterns you could use. One is to instantiate one RP per OP another to keep the OP specific information like provider information and client registration information outside the RP and then setup the RP every time you want to talk to a new OP.

Now back to the static variant. If you can not do the Provider discovery dynamically you have to get the information out-of-band and then configure the RP accordingly. And this is how you would do that:

from oic.oic.message import ProviderConfigurationResponse

op_info = ProviderConfigurationResponse(
version="1.0", issuer="https://example.org/OP/1",
authorization_endpoint="https://example.org/OP/1/authz",
token_endpoint="https://example.org/OP/1/token",
... and so on )

# or
# op_info = ProviderConfigurationResponse(**info)
# if you have the provider info in the form of a dictionary

client.provider_info = op_info

Likewise if the client registration has been done out-of-band:

from oic.oic.message import RegistrationResponse

info = {"client_id": "1234567890", "client_secret": "abcdefghijklmnop"}
client_reg = RegistrationResponse(**info)

client.client_info = client_reg

Authorization query
Once the client knows about the OP and the OP knows about the client we can start doing business, that is get information about users.

The request you then want to make is the authentication request.

Note

This might be slightly confusing. In OAuth2 (RFC 6749) the initial request is called authorization request and you do it at the authorization endpoint. In OIDC the request is renamed to authentication request. For historical reasons I’ve kept the name authorization request for the method that handles that request.

Before doing the request you have to decided on a couple of things:

  • which response type you want to use.
    You can read up on response types in the OAuth2 RFC.

  • the scope. The list of scopes must contain ‘openid’. There is a list of
    extra scopes that OIDC defines which can be found in the specification.

  • whether to use HTTP ‘GET’ or ‘POST’. Either one is allowed. ‘GET’ is default.
Authorization Code Flow
From the list redirect_uris you have to pick one to use for this request. Given you have all that, you now can send the request:

import hashlib
import hmac
from oic.oauth2 import rndstr

session["state"] = rndstr()
session["nonce"] = rndstr()
args = {
"client_id": client.client_id,
"response_type": "code",
"scope": ["openid"],
"nonce": session["nonce"],
"redirect_uri": client.redirect_uris[0]
}

result = client.do_authorization_request(state=session["state"],
request_args=args)

The arguments state are use to keep track on responses to outstanding requests (state).

nonce is a string value used to associate a Client session with an ID Token, and to mitigate replay attacks.

Since you will need both these arguments later in the process you probably want to store them in a session object (assumed to look like a dictionary).

Most probable the response to this request will be a redirect to some other URL where the authentication is performed.

Eventually a response is sent to the URL given as the redirect_uri.

You can parse this response by doing:

from oic.oic.message import AuthorizationResponse

# If you're in a WSGI environment
response = environ["QUERY_STRING"]

aresp = client.parse_response(AuthorizationResponse, info=response,
sformat="urlencoded")

code = aresp["code"]
assert aresp["state"] == session["state"]

aresp is an instance of an AuthorizationResponse or an ErrorResponse. The later if an error was return from the OP. Among other things you should get back in the authorization response is the same state value as you used when sending the request. If you used the response_type=’code’ then you should also receive a grant code which you then can use to get the access token:

args = {
"code": aresp["code"],
"redirect_uri": client.redirect_uris[0],
"client_id": client.client_id,
"client_secret": client.client_secret
}

resp = client.do_access_token_request(scope="openid",
state=aresp["state"],
request_args=args,
authn_method="client_secret_post"
)

‘scope’ has to be the same as in the authorization request.

If you don’t specify a specific client authentication method, then client_secret_basic is used.

You have to provide client_id and client_secret as arguments, how they are used depends on the authentication method used.

The resp you get back is an instance of an AccessTokenResponse or again possibly an ErrorResponse instance.

If it’s an AccessTokenResponse the information in the response will be stored in the client instance with state as the key for future use. One if the items in the response will be the ID Token which contains information about the authentication. One parameter (or claim as its also called) is the nonce you provider with the authroization request.

And then the final request, the user info request:

userinfo = client.do_user_info_request(state=aresp["state"])

Using the state the client library will find the appropriate access token and based on the token type chose the authentication method.

userinfo in an instance of OpenIDSchema or ErrorResponse. Given that you have used openid as the scope, userinfo will not contain a lot of information. actually only the sub parameter.

Implicit Flow
When using the Implicit Flow, all tokens are returned from the Authorization Endpoint; the Token Endpoint is not used.

So:

from oic.oauth2 import rndstr

seession["state"] = rndstr()
session["nonce"] = rndstr()
args = {
"client_id": client.client_id,
"response_type": ["id_token", "token"],
"scope": ["openid"],
"nonce": session["nonce"],
"redirect_uri": client.redirect_uris[0]
}

result = client.do_authorization_request(state=session["state"],
request_args=args)

As for the Authorization Code Flow the authentication part will begin with a redirect to a login page and end with a redirect back to the registered redirect_uri.

Since the response will be return as a fragment you need some special code to catch that information. How you do that depends on your setup.

Again the response can be parse by doing:

from oic.oic.message import AuthorizationResponse

aresp = client.parse_response(AuthorizationResponse, info=response,
sformat="urlencoded")

assert aresp["state"] == client.state

Now aresp will not contain any code reference but instead an access token and an ID token. The access token can be used as described above to fetch user information.

Using Implicit Flow instead of Authorization Code Flow will save you a round trip but at the same time you will get an access token and no refresh_token. So in order to get a new access token you have to perform another authorization request.

Fork

Making an OpenID Connect request
In order for the client to make an OpenID Connect request, it needs to have the following information about the server:
  • client identifier - An unique identifier issued to the client (RP) to identify itself to the authorization server. (e.g. 3214244)
  • client secret - A shared secret established between the authorization server and client used for signing requests.
  • end-user authorization endpoint - The authorization server’s HTTP endpoint capable of authenticating the end-user and obtaining authorization. (e.g., https://server.example.com/authorize )
  • token endpoint - The authorization server’s HTTP endpoint capable of issuing access tokens.
In the simplest cases, this information is obtained by the client developer, having read the server’s documentation and pre-registered their application.
Then, for a bear bone authentication request you would put a link like this in the HTML page:
<a href="https://server.example.com/authorize?grant_type=code&scope=openid&client_id=3214244&state=af1Ef">

Login with Example.com

</a>

The user initiates login by clicking on the “Login with Example.com” link, and is taken to the server where she is asked username/password etc. if she is not logged into example.com yet. Once she agrees to login to the RP, the browser is redirected back to the call back URL at the RP by 302 redirect. The PHP Server side code may look like:
<?php header("Location: https://client.example.com/cb?code=8rFowidZfjt&state=af1Ef");

?>
Note: state is the parameter that is used to protect against XSRF. It binds the request to the browser session. It is recommended but not required in OAuth and has been omitted to make the example static.
That should be simple enough?
Calling the Token endpoint to get id_token
Now that the RP has the ‘code’, you need to get the id_token from the token endpoint. The id_token is the user login information assertion. What do you do? Just GET it with HTTP Basic Auth using client_id, client_secret, and the code you got in the first step. Uusing PHP and cURL, it would look like:
<?php
$code = $_GET['code'];
$ch = curl_init('https://server.example.com/token?code=' . $code);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_USERPWD, $client_id . ":" . $client_secret);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$response = curl_exec($ch);
?>

The result, $response, will contain a JSON like this (line wraps for display purposes only):
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsInVz
ZXJfaWQiOiIyNDgyODk3NjEwMDEiLCJhdWQiOiJodHRwOi
8vY2iwiZXhwIjoxxpZW50LmV4YW1wbGUuY29tIMzExMjgxOTcwfSA.
eDesUD0vzDH3T1G3liaTNOrfaeWYjuRCEPNXVtaazNQ"
}

For simple authentication we will ignore "access_token", "token_type" etc. What you only care about is the "id_token".
"id_token" is encoded in a format called JSON Web Token (JWT). JWT is the concatenation of “header”, “body”, “signature” by periods (.). Since you are getting this directly through TLS protected channel that is verifying the identity of the server certificate, you do not need to check the signature for integrity, so you just take out the second portion of it and base64url_decode it to get the information out of the id_token. So in PHP you may do something like:
<?php
$res = json_decode($response, true);
$id_token = $res['id_token'];
$id_array = mb_split(".", $id_token);
$id_body = base64url_decode($id_array[1]);
?>

The resulting assertion, $id_body in the above example, about the user (after pretty formatting) is:
{
"iss": "https://server.example.com",
"user_id": "248289761001",
"aud": "3214244",
"iat": 1311195570,
"exp": 1311281970
}
“iss”
is showing the issuer of this token, in this case, the server.example.com. The issuer must match the expected issuer for the token endpoint, if it is different you must reject the token. the 'iss" is the name space of the user_id, which is unique within the issuer and never reassigned.
When the client stores the user identifier, it MUST store the tuple of the user_id and iss.
“aud” stands for “audience” and it shows who is the audience of this token. In this case, it is the RP's client_id. If it is different, you must reject the token.

"iat" stands for the time the token was issued. This can be ignored in this flow as the client is talking directly to the token endpoint.
“exp” is the expiry time of the token. If the current time is after “exp”, e.g., in PHP, if $exp < time(); the RP should reject the token as well.
So, that is it. Now you know who is the user, i.e., you have authenticated the user.
All of the above in the form of code would be:
<?php
function check_id($id_body, $issuer, $client_id) {
$idb = json_decode($id_body);
if ($idb['iss'] != $issuer ) $err = true;
if ($idb['aud'] != $client_id) $err = true;
if ($idb['exp'] < time()) $err = true;
if ($err) {
return false;
} else {
return true;
}
}
?>
 
Upvote 0
Subir