Because Uitsmijter does not store any user data to authenticate a login, request providers are written to check if given
credentials are valid. Each tenant
has a set of providers to do certain tasks.
The User Login Provider is responsible for the user backend which knows how to verify
user credentials. The User Validation Provider is responsible to check if a
username
still exists in the backend user store.
The design of the authorization server is built to work with different - especially already existing - backends. It is one of the goals to easily be attachable to existing projects. Replacing an old in-app login form with an SSO should be very simple and straight forward. Tasks like user migration is not necessary as user data stays in its existing location. Changing user backends means changing the provider code and not migrating user data.
Provides are written in ECMA-Script (better known as JavaScript). The runtime is based upon Webkit and has a few additional convenience functions:
Function | Description |
---|---|
console.log | Logs to the info level |
console.error | Logs to the error level |
say | A shorthand for console.log |
fetch | Method to fetch an external resource |
commit | Commit the provider’s result and hand back to the process |
md5 | Hashes a string into a md5 checksum |
sha256 | Hashes a string into a sha256 checksum |
Each tenant
has to implement a User Login Provider code snippet and a
User Validation Provider (both are called: provider)
Minimal example:
providers:
- class UserLoginProvider {
constructor(credentials) { commit(true); }
get canLogin() { return true; }
get userProfile() { return {message:"DO NOT USE THIS IN PRODUCTION"}; }
get role() { return "development"; }
}
- class UserValidationProvider {
constructor(args) { commit(true); }
get isValid() { return true }
}
The providers are responsible for verifying the user and getting the profile of a user into the authorization server. Providers are only glue code and normally should not implement any business logic at all. Usually, providers are sending a request to some service and committing the result back.
Example:
providers:
- |
class UserLoginProvider {
isLoggedIn = false;
profile = {};
role = null;
constructor(credentials) {
fetch(`http://checkcredentials.checkcredentials.svc.cluster.local/validate-login`, {
method: "post",
body: { username: credentials.username, passwordHash: sha256(credentials.password) }
}).then((result) => {
var subject = {};
profile = JSON.parse(result.body);
if (result.code == 200) {
this.isLoggedIn = true;
this.role = profile.role;
subject = {subject: profile.userId};
}
commit(result.code, subject);
});
}
get canLogin() { return this.isLoggedIn; }
get userProfile() { return this.profile; }
get role() { return this.role; }
}
- |
class UserValidationProvider {
isValid = false;
constructor(args) {
fetch(`http://checkcredentials.usertrunk.svc.cluster.local/validate-user`, {
method: "post",
body: {
username: args.username,
}
}).then((result) => {
response = JSON.parse(result.body);
if (result.code == 200 && response.isDeleted === false) {
this.isValid = true;
}
commit(this.isValid);
});
}
get isValid() {
return this.isValid;
}
}
For the UserLoginProvider
you have to commit
the results within the constructor
method. You also have to
provide the three getters:
- canLogin
- userProfile
- role
For the UserValidationProvider
you have to commit
a status within the constructor
method that indicates that
your operation is done. You also have to provide one getter:
- isValid
The execution time of a provider is limited. The advanced setting SCRIPT_TIMEOUT
can manipulate that behaviour in the
future, but the default (and this is what you should use, if not less) is set to 30 seconds. The provider has to
complete all tasks within this time limit, this includes performing all necessary requests and reply with the
result.