Authorization Code Flow
Authorization on behalf of a Space user.
Suitable for web applications with a client running on the server side or completely in the client browser (a static web page).
warning
Important security note for static web pages
If your application is a static resource that performs Authorization Code flow to Space, you must disable the Client Credentials Flow for this application. The problem is that in case of a static resource, Space explicitly sends the application's client ID and secret to the client browser. If the Client Credentials flow is enabled, a potential attacker can use this data to modify the application settings in Space.
You can disable the Client Credentials flow when adding the application to a JetBrains Marketplace or when creating an installation link.
Resource owners access the application via an HTML user interface rendered in a user-agent on the device used by the resource owner.
The application credentials as well as any access token issued to the application are stored on the web server (or a web browser in case of a static resource) and are not exposed to or accessible by the resource owner.
The application sends a user to Space via a link that also includes the scope of required resources. After the user logs in to Space, Space redirects the user back to the application using the specified redirect URI. The redirect also contains an authorization code. The application uses the authorization code to obtain an access token from Space.
In Space, a token obtained by the Authorization Code flow is valid only for a limited period of time. After the token is expired, the application must refresh the token using the Refresh Token flow.
If you use Space SDK in your application, you can implement the flow using the helper methods of the
Space
class. Learn moreFor more details on the flows, refer to the Authorization Code flow, refer to the:
The Authorization code flow in Space supports the PKCE extension.
The proof key for code exchange (PKCE) is an additional protection code that further enhances the authorization flow. It was created to offer desktop and mobile client applications a more secure and robust sign-in experience and is recommended by the IETF (The Internet Engineering Task Force) among other OAuth 2.0 best practices.
How does PKCE work:
The client (your application) creates a random string called the "code verifier".
The client creates a "code challenge", which is the hash of the code verifier using a hashing algorithm.
The client sends the code challenge to Space in the authorization request.
Space stores the code challenge and associates it with the authorization code it generates for this request.
After the user approves the authorization request, the client receives an authorization code. The client then requests an access token from Space. Along with the authorization code, the client also sends the original code verifier.
Space generates the code challenge again from the received code verifier. It then compares this newly generated code challenge with the one it stored earlier. If they match, Space issues an access token to the client.
note
If the Authorization code flow is enabled, Space automatically enables the Refresh Token flow: the issued access token is valid only for 600 seconds, after this your application must obtain a new one.
The example below shows how to implement Authorization Code flow with PKCE. To make it work, Authorization Code Flow and Require PKCE must be enabled in the application settings on the Authentication tab.
You will also need to provide the Client ID and Client secret to the application. If you want to allow the application to initiate the Authorization Code flow without providing the secret (less secure), enable Allow public (untrusted) clients.
package com.example
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.html.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.html.*
import space.jetbrains.api.runtime.*
import space.jetbrains.api.runtime.resources.teamDirectory
import space.jetbrains.api.runtime.types.GlobalPermissionContextIdentifier
import space.jetbrains.api.runtime.types.PermissionIdentifier
import space.jetbrains.api.runtime.types.ProfileIdentifier
import java.util.*
import java.util.concurrent.ConcurrentHashMap
// store these in your database, securely and persistently.
val codeVerifiersByAuthProcessId: MutableMap<UUID, String> = ConcurrentHashMap()
// base Ktor client
val ktorClient = ktorClientForSpace()
const val spaceUrl = "https://mycompany.jetbrains.space"
const val appUrl = "https://myapp.url"
const val showUsernameRoute = "/show-username"
const val authorizeInSpaceRoute = "/authorize-in-space"
const val redirectUri = "$appUrl$showUsernameRoute"
// class that describes your app
val spaceAppInstance = SpaceAppInstance(
// 'clientId' and 'clientSecret' are generated when you
// register the application in Space
// Do not store id and secret in plain text
clientId = System.getenv("JB_SPACE_CLIENT_ID"),
clientSecret = System.getenv("JB_SPACE_CLIENT_SECRET"),
spaceServerUrl = spaceUrl,
)
fun Application.module() {
routing {
// Index page
get("/") {
call.respondHtml {
body {
button {
onClick = "window.location.href = '$authorizeInSpaceRoute'"
+"Authorize in Space and show your username"
}
}
}
}
// When the user presses the button, they are redirected to this page.
// There, an ID of the auth process is generated, and a code verifier for this auth process is stored.
// Then the user is redirected to Space for the authentication and authorization.
get(authorizeInSpaceRoute) {
val authProcessId = UUID.randomUUID()
val codeVerifier = Space.generateCodeVerifier()
codeVerifiersByAuthProcessId[authProcessId] = codeVerifier
call.respondRedirect(
Space.authCodeSpaceUrl(
appInstance = spaceAppInstance,
// Specify the scope of resources that the application needs to access.
// In our case, we need to access user profiles.
// Learn more about permissions
scope = PermissionScope.build(
PermissionScopeElement(
context = GlobalPermissionContextIdentifier,
permission = PermissionIdentifier.ViewMemberProfiles
)
),
redirectUri = redirectUri,
/** [OAuthAccessType.OFFLINE] for offline access on behalf of user */
accessType = OAuthAccessType.ONLINE,
state = authProcessId.toString(),
codeVerifier = codeVerifier,
)
)
}
// After the user is authenticated in Space, they are redirected back to our app at this page.
// With this redirect, we receive the auth code, which we can exchange for an auth token.
// We retrieve the stored code verifier and send it to Space, so that we can be sure that the user is the same
// person that started the auth process.
get(showUsernameRoute) {
suspend fun respondAuthError() {
call.respondHtml(HttpStatusCode.Unauthorized) {
head {
title("Authentication error")
}
body {
p {
+"Authentication error."
}
button {
onClick = "window.location.href = '$authorizeInSpaceRoute'"
+"Try again"
}
}
}
}
val authProcessIdString = call.parameters["state"] ?: run {
respondAuthError()
return@get
}
val authProcessId = try {
UUID.fromString(authProcessIdString)
} catch (_: IllegalArgumentException) {
respondAuthError()
return@get
}
val authCode = call.parameters["code"] ?: run {
respondAuthError()
return@get
}
val spaceAccessTokenInfo = try {
Space.exchangeAuthCodeForToken(
ktorClient = ktorClient,
appInstance = spaceAppInstance,
authCode = authCode,
redirectUri = redirectUri,
codeVerifier = codeVerifiersByAuthProcessId[authProcessId] ?: run {
respondAuthError()
return@get
},
)
} catch (_: PermissionDeniedException) {
respondAuthError()
return@get
}
/** If you requested [OAuthAccessType.OFFLINE] access, you can use [SpaceTokenInfo.refreshToken]
* with [SpaceAuth.RefreshToken]. */
val spaceClient = SpaceClient(ktorClient, spaceAppInstance, SpaceAuth.Token(spaceAccessTokenInfo.accessToken))
val profile = spaceClient.teamDirectory.profiles.getProfile(ProfileIdentifier.Me)
val username = profile.username
val msg = "Hello ${username}!"
call.respondHtml {
head {
title(msg)
}
body {
p {
+msg
}
button {
onClick = "window.location.href = '/'"
+"To home page"
}
}
}
}
}
}
fun main() {
embeddedServer(Netty, port = 8080, module = Application::module).start(wait = true)
}
To start the authentication process, the application should redirect the user's browser to the authentication endpoint <Space service URL>/oauth/auth
in the following format:
${Space Service URL}/oauth/auth?response_type=code&state=${State}&redirect_uri=${Client redirect URI}&request_credentials=${Request credentials mode}&client_id=${Client ID}&scope=${Scope}&access_type={online|offline}&code_challenge={code_challenge}&code_challenge_method={code_challenge_method}
In this request:
code_challenge
is required when using the PKCE extention.code_challenge_method
is optional; defaults to "plain" if not present in the request.
For example:
https://mycompany.jetbrains.space/oauth/auth?response_type=code&state=9b8fdea0-fc3a-410c-9577-5dee1ae028da&redirect_uri=https%3A%2F%2Fmyservice.company.com%2Fauthorized&request_credentials=skip&client_id=98071167-004c-4ddf-ba37-5d4599fdf319&scope=0-0-0-0-0%2098071167-004c-4ddf-ba37-5d4599fdf319&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
If you add the access_type=offline
parameter to your request, an authorization code will return not only an access token but also a refresh token. The refresh token allows the application to obtain a new access token without re-prompting the user for consent, when the original access token has expired.
https://mycompany.jetbrains.space/oauth/auth?response_type=code&state=${State}&redirect_uri=${Client redirect URI}&request_credentials=${Request credentials mode}&client_id=${Client ID}&scope=${Scope}&access_type=offline&code_challenge={code_challenge}&code_challenge_method={code_challenge_method}
To obtain tokens from Space, your application needs to provide values for a number of parameters in authorization requests. See the description of the parameters.
If the resource owner grants the access, Space issues an authorization code and delivers it to the application by adding the following parameters to the query component of the redirection URI using the application/x-www-form-urlencoded
format:
Parameter | Description |
---|---|
code | The authorization code generated by Space. The authorization code will expire shortly after it is issued to mitigate the risk of leaks. The application must not use the authorization code more than once. If an authorization code is used more than once, Space will deny the request. The authorization code is bound to the application identifier and redirection URI. |
state | Space adds this value as a parameter to |
Example: Space redirects the browser by sending the following HTTP response:
HTTP/1.1 302 Found
Location: https://myservice.company.com/authorized?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
The application must ignore unrecognized response parameters.
If the resource owner denies the access request or if the request fails for reasons other than a missing or invalid redirection URI, Space informs the application by adding the following parameters to the query component of the redirection URI using the application/x-www-form-urlencoded
format:
- error
A single ASCII [USASCII] error code from the following:
invalid_request
— The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.unauthorized_client
— The application is not authorized to request an authorization code using this method.access_denied
— The resource owner or Space denied the request.unsupported_response_type
— Space does not support obtaining an authorization code using this method.invalid_scope
— The requested scope is invalid, unknown, or malformed.
After the application receives the code it can exchange it for an access token. If access_type=offline
in the initial request, Space will return a refresh token as well.
The application makes a request to the Space token endpoint by sending the following parameters using the application/x-www-form-urlencoded
format with a character encoding of UTF-8 in the HTTP request entity-body:
POST /oauth/token
Host: ${Space Service URL}
Accept: application/json
Authorization: Basic ${base64(${Client ID} + ":" + ${Client secret})}
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=${Code received on a previous step}&redirect_uri=${Client redirect URI}&code_verifier={code_verifier}
In this request:
code_verifier
is required when using the PKCE extension.client_id
is required when using the PKCE extension for Public Clients (untrusted services) and when Auth Header is omitted.
note
When using the PKCE extension:
For confidential clients (trusted services)
Auth Header
is required.For public clients (untrusted services)
Auth Header
can be omitted but, in this case, theclient_id
parameter becomes mandatory.
Example:
POST /oauth/token
Host: Space.company.com
Accept: application/json
Authorization: Basic OTgwNzExNjctMDA0Yy00ZGRmLWJhMzctNWQ0NTk5ZmRmMzE5OmVBVXlLZ1ZmaFNiVg0K
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=https%3A%2F%2Fmyservice.company.com%2Fauthorized&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
See the description of the parameters.
If the token request is valid and authorized, Space issues an access token and, if requested, a refresh token. If the request has failed or is invalid, the Space server returns an error response.
Example of a successful response:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
"access_token":"1443459450185.0-0-0-0-0.98071167-004c-4ddf-ba37-5d4599fdf319.0-0-0-0-0%3B1.MCwCFC%2FYWvLjHdzOdpLleDLITJn4Mz9rAhRklCoZ2dlMkh2aCd1K5QQ89ibsxg%3D%3D",
"expires_in":600,
"refresh_token": "mF36POk6yJV_adQssw5c_RJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
- state
An identifier for the current application state. For example, it can be a key for a local storage object that contains information about the location of the current user in the application.
- redirect_uri
Space redirects the user to this URI after authorization. Your application must handle the request to this URI and obtain the authorization code from the request parameters.
- request_credentials
Specifies whether to redirect the user to the login page each time, even if the consent has been already given. The following values are valid:
required
— logs the user out of Space and redirects them to the login page. Use as a response to a logout request in the application.default
— use when the application does not allow anonymous access.If the user is already logged in to Space, the user is granted access to the application.
If the user is not logged in to Space, the user is redirected to the login page.
- client_id
The ID assigned to your application when you register it in Space. To get the client ID, go to
Administration → Applications and choose your application from the list.
- client_secret
The private identifier assigned to your application when you register it in Space. To get the client secret, go to
Administration → Applications and choose your application from the list.
- access_type
Indicates whether the application requires access to Space when the user is not online. Allowed values:
online
(used by default) andoffline
. If the application requires refreshing access tokens when the user is not online, use theoffline
value. In this case Space issues a refresh token for the application the first time it exchanges an authorization code for a user. Refer to the Refresh Token page for more information.- code_verifier
A high-entropy cryptographic random string that uses the unreserved characters
[A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
, with a minimum length of 43 characters and a maximum length of 128 characters.The client creates a code verifier for each OAuth 2.0 Authorization Request.
It is recommended that the output of a suitable random number generator be used to create a 32-octet sequence. The octet sequence is then base64url-encoded to produce a 43-octet URL safe string to use as the code verifier. For details, see Section 4.1 of RFC7636.
- code_challenge
A code challenge is derived by client from the code verifier using either
plain
orS256
transformations:plain
:code_challenge = code_verifier
S256
:code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
For details, see Section 4.2 of RFC7636.
- code_challenge_method
This parameter defines the type of transformation used to create the
code_challenge
: eitherplain
orS256
.If the client is capable of using
S256
, it must useS256
. Clients are permitted to useplain
transformation only if they cannot supportS256
for some technical reason.For details, see Section 4.2 of RFC7636.
The application makes a refresh request to the token endpoint <Space service URL>/oauth/token
by adding the following parameters to the HTTP request entity-body in the application/x-www-form-urlencoded
format with UTF-8 character encoding:
Because refresh tokens are typically long-lasting credentials used to request additional access tokens, the refresh token is bound to the application to which it was issued. If the application type is confidential or the application was issued application credentials (or assigned other authentication requirements), the application must authenticate with the Space server as described in RFC 6749.
The application makes the HTTP request in the following format using transport-layer security:
POST /oauth/token
Host: ${Space Service URL}
Authorization: Basic ${base64(${Client ID} + ":" + ${Client secret})}
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=${Refresh token received from Space}
Example (with extra line breaks for display purposes only):
POST /oauth/token
Host: mycompany.jetbrains.space
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
Space will:
Require application authentication for confidential applications or for any application that was issued application credentials (or with other authentication requirements)
Authenticate the application if application authentication is included and ensure that the refresh token was issued to the authenticated application.
Validate the refresh token.
If the refresh token is valid and authorized, Space issues an access token.
Along with new access token, Space can issue a new refresh token, in which case the application must discard the old refresh token and replace it with the new one. Space can revoke the old refresh token after issuing a new refresh token to the application. If a new refresh token is issued, the refresh token scope must be identical to that of the refresh token included by the application in the request.
If the request has failed verification or is invalid, Space returns an error response.
- error
A single ASCII [USASCII] error code from the following:
invalid_request
— The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the application, or is otherwise malformed.invalid_client
— Application authentication failed (e.g., unknown application, no application authentication included, or unsupported authentication method). Space can return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the application attempted to authenticate via the "Authorization" request header field, the Space server will respond with an HTTP 401 (Unauthorized) status code and include the "WWW-Authenticate" response header field matching the authentication scheme used by the application.invalid_grant
— The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another application.unauthorized_client
— The authenticated application is not authorized to use this authorization grant type.unsupported_grant_type
— The authorization grant type is not supported by Space.invalid_scope
— The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner.
The parameters are included in the entity-body of the HTTP response using the "application/json" media type. The parameters are serialized into a JSON structure by adding each parameter at the highest structure level. Parameter names and string values are included as JSON strings. Numerical values are included as JSON numbers. The order of parameters does not matter and can vary.
For example:
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error":"invalid_request"
}