Note: This is not an officially supported Google product.
Pseudo IdP is a simple, fake, OAuth2 or OIDC Identity Provider. It exposes the standard OIDC Discovery, Authorization, Token, and UserInfo endpoints, allowing for custom behavior at each configured through a web UI.
Test that your OAuth/OIDC clients perform all of their required security checks by configuring the Pseudo IdP to misbehave. You can return invalid token signatures, incorrect or missing CSRF token state and nonces, or test for more complicated Mix-Up attacks for clients that support multiple IdPs.
First you will need to build the front end components. Make sure you have npm and the Angular CLI installed.
Then, from the source root, run these commands to install necessary npm nodes and build the frontend.
cd ./frontend
npm install
ng build
Psuedo IdP can run either as a Google Cloud App Engine Service or as a standalone Go binary. Running on the App Engine Standard platform gives you the HTTPS support required by most OAuth clients without any additional setup. If you want to host the service via other methods of your choosing you will likely have to setup HTTPS endpoints via NGINX or some other means.
To deploy to App Engine, first install the GCloud CLI and create a Project (steps 1-5) to host the service.
Once your GCP Project is created and configured, you can deploy the app by
moving to the src directory and using gcloud. But first, let's setup a
password by running
cd ./src/hash
go run hash_salt.go
Enter a password and then set the resulting hash and a username in the
src/app.yaml file in the environment variables section.
env_variables:
LOG_USERNAME: test
LOG_PASSWORD: $2a$10$ze/6a6kINJkz4fUjYUSWnuTElSotXF9VS9ziqQpKNv/6tOt84Y6K2
Now we can deploy the app using
cd ./src
gcloud app deploy
once that is complete running the browse command will launch a browser to your running instance
gcloud app browse -s pseudoidp
To start the standalone server the src directory and running the standalone go program. This is convenient if you would like to test locally or if you would like to use your own hosting solution. But first, let's setup a password by running
cd ./src/hash
go run hash_salt.go
Enter a password and then set the resulting hash and a username in your environment variables with the hash in single quotes.
export LOG_USERNAME=test
export LOG_PASSWORD='$2a$10$s/jpYKi4n7NnKbpw/JKIX.GSVVId3lla6FbbnkECAMUnVh1azvnYm'
Then run the standalone module to start the server.
cd ./src
go run ./standalone/standalone_main.go
Test out your newly deployed Pseudo IdP with the OIDC Debugger client at https://openidconnect.net/.
Pseudo IdP's default configuration mimics a valid OIDC IdP. To configure the
OIDC Debugger, click the Configuration button, choose the Custom server
template, and enter the OIDC discovery document URL for your deployment
https://<your-domain>/.well-known/openid-configuration. Press Use Discover Document to auto-populate the other fields and Save. Then follow various
steps to test out the OIDC flow, exchange an auth code, and verify the token.
The site also links to https://jwt.io where you can inspect the JWT further.
You can view the requests and responses the IdP makes at
https://<your-domain>/log. Log in with the username and password you set while
deploying the application. The log is useful for debugging how the client
interacts with the IdP and to ensure the IdP is responding as you expect.
The power of the tool comes from modifying its behavior to test the security guarantees of the client. As an example, let's see how the OIDC Debugger client behaves when the Pseudo IdP return invalid signatures.
First, load up the configuration management UI at https://<your-domain>/. You
will see multiple configuration tabs across the top of the screen. Select ID Token Config to modify the content of the JWT ID Token.
Press the checkbox next to Remove Signature and click the Submit button to
update the configuration. This setting strips the signature element of the JWT
Token. Accepting JWTs with empty signature elements was the root cause of
CVE-2020-28042, so this is a
real issue that existed in the wild. You can learn more about the JWT format at
jwt.io.
Now let's see how the OIDC Debugger handles our incorrectly signed JWT. Navigate to https://openidconnect.net/ and try the flow again.
Press the Verify button after receiving the token you'll see that the token
verification fails. Inspecting the error code further in your browser's
developer tools, we can see that the returned error says jwt signature is required, so we've learned that the OIDC Debugger client properly handles JWTs
with missing signatures. If it did accept those JWTs, and it wasn't just a test
service, that would be a serious security issue that would need to be mitigated.
Check out the Example Configurations section for many more example tests.
You can also edit the JSON formatted configuration by pressing the Directly Edit JSON Config button. This is useful for copying and saving a configuration
or loading a saved configuration, while updates via the forms is likely easier
for exploratory use. You can return to the forms view by pressing the Edit Config with Forms button.
The
Authorization Endpoint
is a client's first stop in a typical OAuth/OIDC flow. For Pseudo IdP it is at
https:///oauth2/auth. A normal IdP receiving an authorize request
would authenticate the user and ask consent to grant the requesting client
access to the user's resources. Then it would redirect the user's browser back
to the client's callback endpoint. Pseudo IdP ignores the authentication step
and has three modes of operation. It can redirect the user's browser to some
endpoint, either based on URL parameters or custom configured redirect options.
This is useful to test for Login CSRF, Mix-up attacks, or others where the
client should perform some validation at its callback. It can also return an
error or it can block the request and timeout.
The OIDC Spec has a summary of authorization endpoint parameters for OAuth2 and the OIDC extensions.
-
Endpoint Action - Determines how the /oauth2/auth endpoint behaves.
redirectreturns a 302 HTTP redirect with a location and parameters based on the configuration.errorreturns a specified HTTP error code.blocksleeps on receiving the request causing it to time out.
-
Redirect Config
-
Redirect Target
-
Use custom redirect - By default, the redirect target is defined in the
redirect_uriinput parameter. SelectingUse custom redirectallows you to specify a custom redirect URI and ignore the input value. -
Target URL - Specify a custom redirect URL if
Use custom redirectis selected. -
Custom processor key - A custom processor key value for customized evaluation of the redirect uri.
-
-
Default Parameter Action
passthroughkeeps parameters received in the input URL on the redirect URL unless otherwise configured in theParameterssection.omitdoes not include any parameters in the redirect URL unless configured in theParameterssection.
-
Parameters - Add parameter configuration with the
+button. Remove them with the-button.- Identifier of the parameter - The name of the parameter.
- Parameter Action - How the output parameter value is determined.
passthroughkeeps the value as it is on the input URL.setsets the output parameter to a configured value. Set supports templated parameters that can access various server and request properties.omitdoes not include the parameter in the output.randomsets the parameter to a random base64 string.customuses a custom processor to evaluate the parameter.
- JSON Value Type - This is ignored for the Authorization endpoint, but is used in other endpoints that return JSON data.
-
Use Hash Fragment - Put all parameters in the URL Hash Fragment. This is useful for Implicit flows or clients that are otherwise expecting hash frament parameters.
-
The Token Endpoint takes in a authorization grant or refresh token and returns an access token, maybe a refresh token, and for OIDC, an Identity Token. For Pseudo IdP it is at https:///oauth2/token
-
Endpoint Action - Determines how the /oauth2/token endpoint behaves.
redirectreturns a successful token response with parameters based on the configuration.errorreturns a specified HTTP error code.blocksleeps on receiving the request causing it to time out.
-
Response Config
- Parameters - Add parameter configuration with the
+button. Remove them with the-button.- Identifier of the parameter - The name of the parameter.
- Parameter Action - How the output parameter value is determined.
passthroughkeeps the value as it is on the input URL.setsets the output parameter to a configured value. Set supports templated parameters that can access various server and request properties.omitdoes not include the parameter in the output.randomsets the parameter to a random base64 string.customuses a custom processor to evaluate the parameter.
- JSON Value Type - A value is assumed to have the string type
unless this is set to a non-string value.
stringthe set value is interpreted as a string. This is the default.arraythe value is interpreted as an array of strings.numberthe value is interpreted as a number. It must successfully parse as a number.booleanthe value is interpreted as a boolean value. It must betrueorfalse.objectthe value is interpreted as a JSON object. The value must be JSON formatted text.
- Parameters - Add parameter configuration with the
The OIDC UserInfo endpoint returns additional user info. For Pseudo IdP it is at https:///oauth2/userinfo.
-
Endpoint Action - Determines how the /oauth2/userinfo endpoint behaves.
redirectreturns a successful userinfo response with parameters based on the configuration.errorreturns a specified HTTP error code.blocksleeps on receiving the request causing it to time out.
-
Response Config
- Parameters - Add parameter configuration with the
+button. Remove them with the-button.- Identifier of the parameter - The name of the parameter.
- Parameter Action - How the output parameter value is determined.
passthroughkeeps the value as it is on the input URL.setsets the output parameter to a configured value. Set supports templated parameters that can access various server and request properties.omitdoes not include the parameter in the output.randomsets the parameter to a random base64 string.customuses a custom processor to evaluate the parameter.
- JSON Value Type - A value is assumed to have the string type
unless this is set to a non-string value.
stringthe set value is interpreted as a string. This is the default.arraythe value is interpreted as an array of strings.numberthe value is interpreted as a number. It must successfully parse as a number.booleanthe value is interpreted as a boolean value. It must betrueorfalse.objectthe value is interpreted as a JSON object. The value must be JSON formatted text.
- Parameters - Add parameter configuration with the
OIDC specifies a Discovery Endpoint that tells a client where the Authorization, Token and other endpoints are located.
-
Endpoint Action - Determines how the /.well-known/openid-configuration endpoint behaves.
redirectreturns a successful discovery response with parameters based on the configuration.errorreturns a specified HTTP error code.blocksleeps on receiving the request causing it to time out.
-
Response Config
- Parameters - Add parameter configuration with the
+button. Remove them with the-button.- Identifier of the parameter - The name of the parameter.
- Parameter Action - How the output parameter value is determined.
passthroughkeeps the value as it is on the input URL.setsets the output parameter to a configured value. Set supports templated parameters that can access various server and request properties.omitdoes not include the parameter in the output.randomsets the parameter to a random base64 string.customuses a custom processor to evaluate the parameter.
- JSON Value Type - A value is assumed to have the string type
unless this is set to a non-string value.
stringthe set value is interpreted as a string. This is the default.arraythe value is interpreted as an array of strings.numberthe value is interpreted as a number. It must successfully parse as a number.booleanthe value is interpreted as a boolean value. It must betrueorfalse.objectthe value is interpreted as a JSON object. The value must be JSON formatted text.
- Parameters - Add parameter configuration with the
The ID Token configuration drives a
custom processor to create and sign a
JWT. In the default configuration, this is used by the Token
Endpoint configuration to set the id_token parameter.
-
JWT Signature Algorithm - Sets the signature algorithm for the JWT. Available options are
RS256.RS384.RS512.ES256,ES384,ES512,HS256, andnone. -
Remove Signature - Remove the signature component of the JWT.
-
Use Incorrect Key - Sign the token with a key that is the requested type, but is not in the service's /.well-known/jwks.json file.
-
Claims - Add claims configuration with the
+button. Remove them with the-button.- Claim ID - The name of the claim.
- Claim Values - One or more values for a claim. Supports templated parameters that can access various server and request properties.
- JSON Value Type - A value is assumed to have the string type unless
this is set to a non-string value.
stringthe set value is interpreted as a string. This is the default.arraythe value is interpreted as an array of strings.numberthe value is interpreted as a number. It must successfully parse as a number.booleanthe value is interpreted as a boolean value. It must betrueorfalse.objectthe value is interpreted as a JSON object. The value must be JSON formatted text.
Parameters in set mode and Claims support
Go templates. The template evaluation can
access data in the RequestInput data structure.
- Domain - The domain name of the IdP service.
- HTTPMethod - The HTTP method called. GET, POST, etc.
- Path - The URL Path for the request.
- Proto - The URL proto, HTTP, HTTPS, etc.
- Headers - Array of HTTP Headers from the request.
- URLParams - URL Parameters.
- FormParams - Form parameters for POST requests.
- Session - Persisted session details key'd by the Auth Code.
- Code - The Auth Code.
- Nonce - The OIDC Nonce if specified.
- CodeChallenge - The PKCE Code challenge if specified.
- CodeChallengeMethod - The PKCE Code challenge method if specified.
- ClientID - The Client ID from the Authorization request.
- RedirectURI - The requested redirect URI.
- Time - Request time in the Go Time type.
The default configuration for the Discover endpoint uses multiple templates to create the various endpoint URLs for the current domain.
https://{{.Domain}}/oauth2/token
The default ID Token configuration sets the nonce claim based on the input
session nonce if it is available.
{{if .Session}}{{.Session.Nonce}}{{end}}
The default expiration of the token is set to one day from the request time.
{{with $tomorrow := .Time.AddDate 0 0 1}}{{$tomorrow.Unix}}{{end}}
Most clients use the state field for CSRF protection and other state tracking. The default configuration passes this parameter through as is, but you can modify it to check that the client is verifying the state. Below is the JSON representation of that configuration. You can do the same from the UI by adding a Parameter config to the Authorization Endpoint to modify the 'state' parameter.
{
"auth_action": {
"action_type": "redirect",
"redirect": {
...
"parameters": [
...
{
"id": "state",
"action": "set",
"values": [
"nottherealstate"
],
"custom_key": "",
"json_type": ""
}
],
}
}
...
}
The client must check the ID Token nonce value in OIDC flows. You can verify this by changing the nonce claim generated in the ID Token Configuration.
{
"id": "nonce",
"values": [
"unexpectednonec"
],
"json_type": "string"
},
Change the exp claim to be in the past.
{
"id": "exp",
"values": [
"{{with $tomorrow := .Time.AddDate 0 0 -1}}{{$tomorrow.Unix}}{{end}}"
],
"json_type": "number"
},
Some JWT Signature verifications have had issues with the None signature type.
Current guidance is to block any use of the none algorithm type.
"alg": "none",
Pseudo IdP uses the RSA Public Key as the secret for the HS256 signing algorithm to replicate CVE-2016-5431.
"alg": "HS256",
Select the "Remove Signature" option in the ID Token Config section and the signature segment of the JWT is removed.
The Client must validate the issuer claim.
{
"id": "iss",
"values": [
"https://nottheissuereryouwerelookingfor.com"
],
"json_type": "string"
},
In a Mix up attack, one provider tricks the client into sending it an authorization code or token from another provider. In this case we have the Authorization endpoint redirect the user to a different OAuth2 Identity Provider, fixing the Client ID value and omitting the Nonce, if any. Daniel Fett's blog has in-depth coverage of Mix-up Attacks and mitigations.
"auth_action": {
"action_type": "redirect",
"redirect": {
"redirect_target": {
"use_custom_redirect_uri": true,
"target": "https://honestidp/auth",
"custom_key": ""
},
"default_parameter_action": "passthrough",
"parameters": [
{
"id": "client_id",
"action": "set",
"values": [
"clientidathonestidp"
],
"custom_key": "",
"json_type": ""
},
{
"id": "nonce",
"action": "omit",
"values": null,
"custom_key": "",
"json_type": ""
}
]
},
"error": {
"error_code": 0,
"error_content": ""
}
},
"token_action": {
"action_type": "block",
"respond": {
"parameters": null
},
"error": {
"error_code": 0,
"error_content": ""
}
},
The use of a Nonce value by the Client and the Identity Provider prevents the Mix Up version above, however the issue still exists using a Chosen Nonce variation. Here a session is started with the valid IdP and the nonce from that transaction is inserted into the flow for the Pseudo IdP.
"auth_action": {
"action_type": "redirect",
"redirect": {
"redirect_target": {
"use_custom_redirect_uri": true,
"target": "https://honestidp/auth",
"custom_key": ""
},
"default_parameter_action": "passthrough",
"parameters": [
{
"id": "client_id",
"action": "set",
"values": [
"clientidathonestidp"
],
"custom_key": "",
"json_type": ""
},
{
"id": "nonce",
"action": "set",
"values": "chosennonce",
"custom_key": "",
"json_type": ""
}
]
},
"error": {
"error_code": 0,
"error_content": ""
}
},
"token_action": {
"action_type": "block",
"respond": {
"parameters": null
},
"error": {
"error_code": 0,
"error_content": ""
}
},
The Pseudo IdP can also be configured to perform the OAuth2 Implicit Flow.
"auth_action": {
"action_type": "redirect",
"redirect": {
"redirect_target": {
"use_custom_redirect_uri": false,
"target": "",
"custom_key": ""
},
"default_parameter_action": "passthrough",
"parameters": [
{
"id": "id_token",
"action": "custom",
"values": null,
"custom_key": "signed_token_id",
"json_type": ""
},
{
"id": "access_token",
"action": "random",
"values": null,
"custom_key": "",
"json_type": ""
},
{
"id": "token_type",
"action": "set",
"values": [
"Bearer"
],
"custom_key": "",
"json_type": ""
},
{
"id": "expires_in",
"action": "set",
"values": [
"3600"
],
"custom_key": "",
"json_type": ""
}
]
},
"error": {
"error_code": 0,
"error_content": ""
}
},
You can add custom parameter evaluation function to Pseudo IdP's Go backend
implementation when more complex parameter computation is needed. As an example,
the generation of the ID Token in the default configuration is done by a custom
parameter in src/config/idtoken.go.
A custom parameter evaluation function takes in information about the request and the current backend configuration and returns either an error if processing failed, or one or more strings of output.
func(input *sessionmgmt.RequestInput, config *Config) ([]string, error)
To add a new custom parameter, follow these steps:
-
Add a new Go file in the
src/configdirectory. Name it something that represents what your parameter will evaluate. Add it to theconfigpackage. -
Add a
init()function to register the parameter using theRegisterCustomParamfunction. The first parameter is the ID of the parameter that will be used in the configuration to invoke the custom parameter evaluation function in the second parameter.func init() { RegisterCustomParam("signed_token_id", GenerateToken) } -
Implement your custom evaluation in the function registered in step #2.
-
Use your parameter in the configuration by selecting the
customParameter Action and specifying your parameter's ID.
You can add new configuration options as needed to the src/config/config.go
file. The entries in the Config structure are automatically converted to a
JSON Schema and loaded by the frontend HTML Forms. Adding a new config entry can
be useful if your custom parameter evaluation needs additional configurable
data. This is done as an example with the src/config/idtoken.go custom
parameter and the IDTokenConfig entry.
Each field in the Config structure and sub-structures should have a struct tag describing how it is converted to JSON and how it is represented in the frontend forms.
- json: Sets the field name in JSON.
- jsonschema: Allows setting JSON Schema parameters for the values.
- title= The title of the field in the form.
- default= The default value of the field in the form when the form is reset.
- enum= Turns the form in a selection box of the options listed.
- jsonschema_extras: Sets additional behavioral configuration of the forms.
-
hide= Hide this field in the form based on a condition. Conditions only allow other fields to be compared to field values through the Javascript operators === or !==. In the example below, the Values field will be hidden whenever the Action field is not
setAction string `json:"action" jsonschema:"title=Parameter Action,enum=passthrough,enum=set,enum=omit,enum=random,enum=custom,default=passthrough"` Values []string `json:"values" jsonschema:"title=Values,default=example_value" jsonschema_extras:"hide=action !== set"`
-
You can run the Angular Frontend tests as
cd ./frontend
ng test
You can run the backend Go tests as
cd ./src
go test ./ ...
Developing the frontend locally can seem a bit tricky, as it relies on the backend for its display the configuration forms. However, thanks to Angular's proxy feature, it turns out to not be so bad. For developing on the frontend:
- Run the local standalone backend with
go run ./standalone/standalone_main.go.- If you are not using the default port of 8080, you will also need to
update the Angular proxy config in
frontend/src/proxy.conf.json.
- If you are not using the default port of 8080, you will also need to
update the Angular proxy config in
- Switch to the
frontenddirectory and runng serve. Use the URL it provides to navigate to the frontend.ng serveautomatically rebuilds the frontend on file changes making it much easier to test out frontend changes without having to restart the backend.











