Branch | Build Status |
---|---|
master | |
develop |
- Java 17 (Temurin)
- Grails 5.1.7+
- Gradle 7.3.3+
All of the above can be installed and easily maintained by using SDKMAN!.
The preferred way of running Mauro Data Mapper is using the mdm-docker deployment. However you can also run the backend on its own from mdm-application-build.
In the docker-compose.yml
file add:
mauro-data-mapper:
build:
args:
ADDITIONAL_PLUGINS: "uk.ac.ox.softeng.maurodatamapper.plugins:mdm-plugin-authentication-openid-connect:2.1.0"
Please note, if adding more than one plugin, this is a semicolon-separated list
In the build.gradle
file add:
grails {
plugins {
runtimeOnly 'uk.ac.ox.softeng.maurodatamapper.plugins:mdm-plugin-authentication-openid-connect:2.1.0'
}
}
Described by https://auth0.com/docs/flows/authorization-code-flow
- UI requests known providers from us
- User clicks link provided by UI
- UI has to add the
redirect_uri
url param to the url
- UI has to add the
- User is taken to authentication request url
- User authenticates
- Auth returns response with params in url
- session_state
- code
- state
- UI sends these params to API as the body of a login request
- session_state
- code
- state
- redirect_uri = exact uri used by UI
- API calls the access token endpoint using these parameters as a urlencoded form post
(also use basic auth header with username:
client_secret
,password:$clientSecret
)- client_id
- client_secret
- grant_type
- code
- redirect_uri
- session_state
- Response back in JSON form
- access_token
- expires_in
- refresh_expires_in
- token_type
- id_token
- not-before-policy
- session_state
- scope
- API verifies the id_token which is a JWT
- API retrieves userinfo to create user if one does not exist, otherwise grabs user for the email
- API stores the token data into the database?
- Every subsequent API call is interecepted and the token is checked to ensure the access_token is still valid
- Done by checking "now" vs decodedjwt "getExpiresAt"
- access tokens are not always JWT, they are provider specific, so we treat them as coded strings as we dont want to rely on functionality which may not be there
- If expired then we check for a refresh token and if that has expired
- If refresh token and not expired then we call for a new access token and update whats stored
- If no refresh token or expired then we invalidate the session and return
State is there to protect the end user from cross site request forgery(CSRF) attacks. It is introduced from OAuth 2.0 protocol RFC6749. Protocol states that:
Once authorization has been obtained from the end-user, the authorization server redirects the end-user's user-agent back to the client with the required binding value contained in the "state" parameter. The binding value enables the client to verify the validity of the request by matching the binding value to the user-agent's authenticated state
This is therefore a UI communication thing which the API can supply as a random UUID.
An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHOULD be used for preventing cross-site request forgery
Nonce serves a different purpose. It binds the tokens with the client. It serves as a token validation parameter and is introduced from OpenID Connect specification.
String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string
Nonce is therefore an API thing which the API will generate cryptographically from the session id. Therefore no 2 sessions will have the same nonce and it will be impossible to guess or fake as the session id cannot be manually configured.
The nonce parameter value needs to include per-session state and be unguessable to attackers. One method to achieve this for Web Server Clients is to store a cryptographically random value as an HttpOnly session cookie and use a cryptographic hash of the value as the nonce parameter. In that case, the nonce in the returned ID Token is compared to the hash of the session cookie to detect ID Token replay by third parties. A related method applicable to JavaScript Clients is to store the cryptographically random value in HTML5 local storage and use a cryptographic hash of this value.
The API supplies all of the authentication url prebuilt and requires the UI to add the redirect_uri
parameter
https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
A failed attempt will nullify the code returned by the UI, requiring a request for a new code
https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
https://www.keycloak.org/docs/latest/securing_apps/index.html#endpoints
https://developers.google.com/identity/protocols/oauth2/openid-connect
Note that refresh token is not sent as the access token has a 51min expiry life and therefore is unlikely to be needed. I believe that making any access check request "resets" the expiry counter but this will need to be checked It may also be the same for Keycloak with the "reset"