User Authentication¶
User authentication is done using one of several different backends.
Authenticating with UCloud¶
UCloud provides various backends for authentication. These are all implemented in the authentication service. As of 06/06/24 the following backends are supported:
Authentication with username/password
Authentication via WAYF
Optional backend for OpenID (disabled in current production env)
Below we will be covering the technical details of each backend.
All authentication information for a user is stored in a PostgreSQL table and is managed only by the authentication service. We will cover the concrete data stored later. A column in the table is used for differentiating which authentication backend is used.
WAYF is considered the primary authentication backend.
Password¶
Users can login with a simple username/password combination. Only administrators of the system can create new users. These are created in the “Admin” panel of the UCloud web interface.
We allow users to change their password through the web interface (by going to “Settings” in the user menu). Users are required to provide their current password in order to change to a new one.
Currently no password policy is implemented.
Login attempts are logged through normal auditing. We limit number of incorrect login attempts according to the following recommendations.
Passwords are stored following recommendations by
OWASP.
Password hashing is provided via OpenJDK. Specifically we use
PBKDF2WithHmacSHA512
with the following parameters:
Salt length: 16 bytes (Generated via
SecureRandom
)Iterations: 10000
Key length: 256
WAYF¶
WAYF (Where Are You From) is a danish identity federation for research and infrastructure. WAYF provides authentication via the local organization (e.g. SDU). At a technical level this is implemented with SAML.
UCloud implements the SAML integration by using Onelogin’s library for SAML (com.onelogin:java-saml-core
, see the
build.gradle
of auth-service
for the specific version). Minor modifications to the library were made to support a
different webserver (ktor). These changes are implemented in dk.sdu.cloud.auth.services.saml.SamlRequestProcessor
.
The SAML library is configured according to WAYF’s own recommendations.
Sessions and Tokens¶
Once a user has been successfully authenticated with UCloud a number of tokens are issued for the user. These tokens are used during the authentication workflow.
The table below summarizes the tokens and where they are stored when the web interface is being used.
Name | Token Type | Storage Type | Purpose |
---|---|---|---|
accessToken |
JWT | Local Storage | Authenticate API calls |
refreshToken |
Opaque | HTTP Only Cookie | Used for creating new accessToken s |
csrfToken |
Opaque | Local Storage | Used in combination with refreshToken to avoid CSRF |
The accessToken
is used to authenticate all API calls. It is passed as a bearer token in the Authorization
header.
The token contains a JSON web token. These are tokens which contain a JSON encoded payload and are signed by the
authority issuing them. The JWTs in UCloud are signed with the SHA256withRSA
algorithm. Each service can verify a
JWT (without contacting a central server) by using the authentication’s service public certificate and additionally
verifying that the issuer is set to cloud.sdu.dk
.
We store the accessToken
in local
storage. This is a technical
requirement given that our JavaScript code must be able to attach the token to each request in the Authorization
header. However, storing secret tokens, such as the accessToken
, in local storage can be problematic since it can
easily be stolen by malicious JavaScript. This could happen in case of a successful XSS attack. To minimize the impact
of a successful attack we ensure that the JWTs are relatively short lived (10 minutes). We do not implement a JWT
blacklist and rely only on the short expiry time of the JWTs.
Given that JWTs expire after 10 minutes we need a different mechanism for keeping the user logged in. For this we use
the refreshToken
. The refresh token is an opaque token which can be used to generate a new accessToken
. A user can
keep using the same refresh token for creating new access tokens until the refresh token is invalidated. A refresh token
will be invalidated once the user logs out.
In order to protect against XSS attacks the refreshToken
is stored as a cookie with the following flags:
HttpOnly
SameSite Strict
Secure
Expires after 30 days
When refreshing the token via a cookie the csrfToken
must be passed in the X-CSRFToken
header. If the CSRF token is
not present or does not match the server’s records the token will not be refreshed.
JWT Payload¶
The following is an example of a JWT payload. This payload is decodable by anyone who has a JWT. As a result it is crucial that we do not store sensitive data in it.
{
// Token properties
"iat": 1234,
"exp": 5678,
"iss": "cloud.sdu.dk",
// Authorization properties
"role": "ADMIN",
"aud": ["all:write"],
// Extension metadata
"extendedByChain": [],
// Session reference
"publicSessionReference": "ref",
// User metadata
"sub": "user1",
"principalType": "password",
"firstNames": "User",
"lastName": "User",
"orgId": "sdu.dk",
"serviceLicenseAgreement": true,
"twoFactorAuthentication": true
}
Token Properties¶
These properties are about the token itself and are used as part of the verification process.
Property | Description |
---|---|
iat |
Unix timestamp indicating when the token was issued at |
exp |
Unix timestamp indicating when the token will expire |
iss |
The issuer of this token. For UCloud this should always be cloud.sdu.dk |
jti |
A unique ID of this token. If this field is present then this JWT is used as a one-time token. |
One-Time Tokens¶
All users are allowed to use their normal JWT to create a one-time token. This one-time token is designed to be used only once and we keep a list of already claimed JWTs to ensure this. A one-time token is created for a specific security scope and this scope must be covered by the JWT requesting it. The JWT created will only last for thirty seconds.
Token Extension (Deprecated)¶
Token extension is a mechanism used for when a service needs to perform actions on behalf of a user that cannot be performed immediately. For example, an application service might need to upload files back into the system after a long computation. The service cannot use the user’s JWT since that will have expired by the time the computation is done.
Services are allowed to extend a user JWT for a set of security scopes. The extended token will be a tuple of
accessToken
and optionally a refreshToken
. The accessToken
(either returned directly or created later) will only
be valid for the given security scope.
The authentication service contains a whitelist of security scopes each service is allowed to ask for. Any request not covered by this whitelist will be rejected. This minimizes the dangers of a single service being compromised.
The service that extended the token is added to extendedByChain
. This allows us to track requests performed by a
service on behalf of a user.
Session References¶
A session reference is an opaque token which has a 1:1 mapping with a refreshToken
. This allows us, in combination
with auditing information, to determine from which request a JWT was minted. The session reference is not considered
secret and does not allow a user to create new JWTs.
User Metadata¶
Property | Description |
---|---|
sub |
The username of this user. |
principalType |
The type of users. (wayf/password/service) |
firstNames |
First name(s). |
lastName |
Last name. |
uid |
Unique user ID |
orgId |
Organization ID (provided by WAYF) |
serviceLicenseAgreement |
Indicates if the ToS agreement has been agreed |
twoFactorAuthentication |
Indicates if 2FA is enabled |
Table of Contents¶
1. Remote Procedure Calls
Name | Description |
---|---|
browseIdentityProviders |
No description |
listUserSessions |
No description |
passwordLogin |
No description |
startLogin |
No description |
bulkInvalidate |
No description |
claim |
No description |
invalidateSessions |
No description |
logout |
No description |
refresh |
No description |
requestOneTimeTokenWithAudience |
No description |
tokenExtension |
No description |
webLogout |
No description |
webRefresh |
No description |
2. Data Models
Name | Description |
---|---|
AccessToken |
No description |
AccessTokenAndCsrf |
No description |
ClaimOneTimeToken |
No description |
IdentityProvider |
No description |
OneTimeAccessToken |
No description |
OptionalAuthenticationTokens |
No description |
Session |
No description |
BulkInvalidateRequest |
No description |
ListUserSessionsRequest |
No description |
RequestOneTimeToken |
No description |
TokenExtensionRequest |
No description |
Remote Procedure Calls¶
browseIdentityProviders
¶
Request | Response | Error |
---|---|---|
Unit |
BulkResponse<IdentityProvider> |
CommonErrorMessage |
listUserSessions
¶
Request | Response | Error |
---|---|---|
ListUserSessionsRequest |
Page<Session> |
CommonErrorMessage |
passwordLogin
¶
Request | Response | Error |
---|---|---|
Unit |
Unit |
CommonErrorMessage |
startLogin
¶
Request | Response | Error |
---|---|---|
FindByIntId |
Unit |
CommonErrorMessage |
bulkInvalidate
¶
Request | Response | Error |
---|---|---|
BulkInvalidateRequest |
Unit |
CommonErrorMessage |
claim
¶
Request | Response | Error |
---|---|---|
ClaimOneTimeToken |
Unit |
Unit |
invalidateSessions
¶
Request | Response | Error |
---|---|---|
Unit |
Unit |
CommonErrorMessage |
logout
¶
Request | Response | Error |
---|---|---|
Unit |
Unit |
Unit |
refresh
¶
Request | Response | Error |
---|---|---|
Unit |
AccessToken |
CommonErrorMessage |
requestOneTimeTokenWithAudience
¶
Request | Response | Error |
---|---|---|
RequestOneTimeToken |
OneTimeAccessToken |
Unit |
tokenExtension
¶
Request | Response | Error |
---|---|---|
TokenExtensionRequest |
OptionalAuthenticationTokens |
CommonErrorMessage |
webLogout
¶
Request | Response | Error |
---|---|---|
Unit |
Unit |
CommonErrorMessage |
webRefresh
¶
Request | Response | Error |
---|---|---|
Unit |
AccessTokenAndCsrf |
CommonErrorMessage |
Data Models¶
AccessToken
¶
data class AccessToken(
val accessToken: String,
)
Properties
accessToken
: String
String
AccessTokenAndCsrf
¶
data class AccessTokenAndCsrf(
val accessToken: String,
val csrfToken: String,
)
ClaimOneTimeToken
¶
data class ClaimOneTimeToken(
val jti: String,
)
Properties
jti
: String
String
IdentityProvider
¶
data class IdentityProvider(
val id: Int,
val title: String,
val logoUrl: String?,
)
OneTimeAccessToken
¶
data class OneTimeAccessToken(
val accessToken: String,
val jti: String,
)
OptionalAuthenticationTokens
¶
data class OptionalAuthenticationTokens(
val accessToken: String,
val csrfToken: String?,
val refreshToken: String?,
)
Session
¶
data class Session(
val ipAddress: String,
val userAgent: String,
val createdAt: Long,
)
BulkInvalidateRequest
¶
data class BulkInvalidateRequest(
val tokens: List<String>,
)
ListUserSessionsRequest
¶
data class ListUserSessionsRequest(
val itemsPerPage: Int?,
val page: Int?,
)
RequestOneTimeToken
¶
data class RequestOneTimeToken(
val audience: String,
)
Properties
audience
: String
String
TokenExtensionRequest
¶
data class TokenExtensionRequest(
val validJWT: String,
val requestedScopes: List<String>,
val expiresIn: Long,
val allowRefreshes: Boolean?,
)