JetBrains Space Help

Authorization Code Flow

Basics

  • Authorization on behalf of a Space user.

  • Suitable for web applications with a client running on the server side.

  • 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 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 with the help of the SpaceHttpClient().withCallContext() method.

  • For more details on the flows, refer to the Authorization Code flow, refer to the:

How to implement

Initial request

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

To instruct Space to issue a refresh token, add the access_type=offline parameter to your request:

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 an access token from Space, your application needs to provide values for a number of parameters in authorization requests. See the description of the parameters.

Handle response

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

The exact value received from the application in the authorization request.

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.

Handle error response

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.

error_description

Human-readable ASCII [USASCII] text providing additional information, used to assist the application developer in understanding what went wrong.

Exchange authorization code for an access token

After the application receives the code it can exchange it for an access token.

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.

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.

Handle access token response

If the access token request is valid and authorized, Space issues an access 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":3600, }

Athorization code flow parameters

response_type

Specifies the grant type in an OAuth 2.0 request. Set value to code

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

A URI in your application that can handle responses from Space.

request_credentials

A parameter that determines whether the user should be asked to log in. The following values are valid:

  • skip — use when the application allows 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 and the guest account is not banned, the user is granted access to the application as a guest.

    • If the user is not logged in to Space and the guest account is banned, the user is redirected to the login page.

  • silent — same as skip, but redirects the user to the application in all cases. If the guest account is banned, the user is redirected to the application with an authentication error.

  • 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.png 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.png Administration → Applications and choose your application from the list.

scope

A space separated list of rights required to access specific resources in Space.

The rights are grouped in four categories: Global, Profile, Team, Project. Use the following syntax to specify the rights (shown here in BNF notation):

<SCOPE> ::= <ALL> | <TOKEN_LIST> <ALL> ::= '**' <TOKEN_LIST> ::= <TOKEN> (' ' <TOKEN>)* <TOKEN> ::= <GLOBAL_PERMISSIONS_TOKEN> | <ENTITY_PERMISSIONS_TOKEN> <GLOBAL_PERMISSIONS_TOKEN> ::= <PERMISSIONS> <ENTITY_PERMISSIONS_TOKEN> ::= <ENTITY> ':' <PERMISSIONS> <ENTITY> ::= 'Team' | 'Project' | 'Profile' | 'etc.' <PERMISSIONS> ::= <ALL_PERMISSIONS> | <PERMISSION_LIST> <ALL_PERMISSIONS> ::= '*' <PERMISSION_LIST> ::= <PERMISSION> (',' <PERMISSION>)*

Wildcards are accepted. For global rights, category is omitted.

Example:

AddNewProfile,AddNewTeam Team:EditTeam Profile:EditAbsences,EditLanguages Project:*

The rights you specify in scope should be first added to the list of requested rights and authorized for your application in Space.

access_type

Indicates whether the application requires access to Space when the user is not online. Allowed values: online (used by default) and offline. If the application requires refreshing access tokens when the user is not online, use the offline 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 or S256 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: either plain or S256.

If the client is capable of using S256, it must use S256. Clients are permitted to use plain transformation only if they cannot support S256 for some technical reason.

For details, see Section 4.2 of RFC7636.

Refresh an access token

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:

grant_type

Specifies the grant type in an OAuth 2.0 request. Required. Set value to refresh_token.

refresh_token

Required. The refresh token issued to the application.

scope

Required. A space separated list of rights required to access specific resources in Space.

The rights are grouped in four categories: Global, Profile, Team, Project. Use the following syntax to specify the rights (shown here in BNF notation):

<SCOPE> ::= <ALL> | <TOKEN_LIST> <ALL> ::= '**' <TOKEN_LIST> ::= <TOKEN> (' ' <TOKEN>)* <TOKEN> ::= <GLOBAL_PERMISSIONS_TOKEN> | <ENTITY_PERMISSIONS_TOKEN> <GLOBAL_PERMISSIONS_TOKEN> ::= <PERMISSIONS> <ENTITY_PERMISSIONS_TOKEN> ::= <ENTITY> ':' <PERMISSIONS> <ENTITY> ::= 'Team' | 'Project' | 'Profile' | 'etc.' <PERMISSIONS> ::= <ALL_PERMISSIONS> | <PERMISSION_LIST> <ALL_PERMISSIONS> ::= '*' <PERMISSION_LIST> ::= <PERMISSION> (',' <PERMISSION>)*

Wildcards are accepted. For global rights, category is omitted.

Example:

AddNewProfile,AddNewTeam Team:EditTeam Profile:EditAbsences,EditLanguages Project:*

The rights you specify in scope should be first added to the list of requested rights and authorized for your application in Space.

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:

  1. Require application authentication for confidential applications or for any application that was issued application credentials (or with other authentication requirements)

  2. Authenticate the application if application authentication is included and ensure that the refresh token was issued to the authenticated application.

  3. 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.

Refresh token flow errors

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.

error_description

Human-readable ASCII [USASCII] text providing additional information, used to assist the application developer in understanding the error that occurred.

error_uri

A URI identifying a human-readable web page with information about the error, used to provide the application developer with additional information about the error.

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" }

The Authorization Code flow is used to authorize the application on behalf of a user. In this flow, the application sends a user to Space via a link. 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. When registering the application, you should select Authorization Code Flow and specify the Redirect URIs. Note that 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.

There is a number of ways to implement the Authorization Code flow in your application. One of the ways is to:

  1. Use the Ktor's Authentication feature for obtaining an access token.

  2. Use the withPermanentToken method of the SpaceHttpClient class to access Space endpoints with the obtained token.

For example, the following application logs in to Space on behalf of a user and shows the Hello {username}! message on its index page:

package space.auth.example import io.ktor.application.* import io.ktor.response.* import io.ktor.request.* import io.ktor.routing.* import io.ktor.http.* import io.ktor.html.* import kotlinx.html.* import io.ktor.auth.* import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.features.* import io.ktor.sessions.* import kotlinx.datetime.Clock import space.jetbrains.api.runtime.* import space.jetbrains.api.runtime.resources.teamDirectory import space.jetbrains.api.runtime.resources.todoItems import space.jetbrains.api.runtime.types.ProfileIdentifier import kotlin.time.ExperimentalTime import kotlin.time.seconds const val spaceUrl = "https://mycompany.jetbrains.space" // base Space client val baseClient = SpaceHttpClient(HttpClient()) // Space access token var spaceToken: ExpiringToken? = null // check token expiration fun TokenInfo.expired(): Boolean { return if (this.expires != null) { (Clock.System.now() > this.expires!!) } else true } // OAuth provider val spaceOauthProvider = OAuthServerSettings.OAuth2ServerSettings( name = "Space", authorizeUrl = "$spaceUrl/oauth/auth", accessTokenUrl = "$spaceUrl/oauth/token", requestMethod = HttpMethod.Post, // 'clientId' and 'clientSecret' are generated when you // [[[register the application|https://www.jetbrains.com/help/space/register-app-in-space.html#specify-authentication-options]]] 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"), // list of [[[scopes|https://www.jetbrains.com/help/space/authentication-a.html#scopes]]] defaultScopes = listOf("**"), ) fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args) @ExperimentalTime @kotlin.jvm.JvmOverloads fun Application.module(testing: Boolean = false) { install(Authentication) { // we need OAuth 2.0 flows oauth("space-oauth") { // it requires a client to perform auth flow client = HttpClient() // provider with OAuth server settings providerLookup = { spaceOauthProvider } // redirect that will return user back to the app urlProvider = { redirectUrl("/login") } } } routing { // index page get("/") { // If token is not valid, log in to Space if (spaceToken == null || spaceToken!!.expired()) { call.respondRedirect("/login") return@get } // If token is valid, get username from Space val spaceClient = baseClient.withPermanentToken( spaceToken!!.accessToken, spaceUrl) val profile = spaceClient.teamDirectory.profiles.getProfile( ProfileIdentifier.Me) val username = profile.username // and return HTML page with greeting val msg = "Hello ${username}!" call.respondHtml { head { title(msg) } body { p { +msg } } } } authenticate("space-oauth") { // login page route("/login") { handle { // Get the OAuth principal (incl. the token). // Ktor Authentication feature performs the // authentication code flow by default val principal = call.authentication. principal<OAuthAccessTokenResponse.OAuth2>() ?: error("No principal") // Get token val token = principal.accessToken val expires = (Clock.System.now() + principal.expiresIn.seconds) spaceToken = ExpiringToken(token, expires) call.respondRedirect("/") } } } } } // generate redirect URLs private fun ApplicationCall.redirectUrl(path: String): String { val host = request.host() return "https://$host$path" }

If you don't want to use the Ktor Authentication and Session features, you can implement the Authorization Code and Refresh Token flows by yourself. In this case, you can use the withCallContext() method of the SpaceHttpClient class to communicate with Space and obtain new tokens. For example:

// URL of your Space instance const val spaceUrl = "https://mycompany.jetbrains.space" // Create a base Space client val baseClient = SpaceHttpClient(HttpClient()) val spaceClient = baseClient.withCallContext( SpaceHttpClientCallContext(spaceUrl, ExpiringTokenSource { // This function will run once the token expires obtainToken() } )) fun obtainToken(): ExpiringToken { // TODO: Obtain a token from Space }

Scopes

Scope is a mechanism in OAuth 2.0 to limit an application's access to a user account. By default, the Space API client uses the ** scope, which requests all rights available to the application.

There is a number of ways to specify the required scope depending on your authentication flow. For example, if you use the Ktor's Authentication feature to obtain a token from Space, define the scope in the OAuth settings provider (see the full example):

val spaceOAuthProvider = OAuthServerSettings.OAuth2ServerSettings( name = "space-oauth", authorizeUrl = "$spaceUrl/oauth/auth", accessTokenUrl = "$spaceUrl/oauth/token", requestMethod = HttpMethod.Post, accessTokenRequiresBasicAuth = true, clientId = System.getenv("JB_SPACE_CLIENT_ID"), clientSecret = System.getenv("JB_SPACE_CLIENT_SECRET"), // request the right to 'View absences' defaultScopes = listOf("Profile:ViewAbsences") )

Learn more about scopes and application permissions in Request Permissions.

Last modified: 29 September 2022