├── README.md ├── authentication.md ├── configuration.md ├── cookie-authentication.md ├── csrf.md ├── email-verification.md ├── error-handling.md ├── example-config.yaml ├── handlers.md ├── idsite.md ├── login.md ├── logout.md ├── multi-tenancy.md ├── oauth2.md ├── password-reset.md ├── registration.md ├── requests.md ├── single-page-apps.md ├── social.md ├── user-agent.md └── user-context.md /README.md: -------------------------------------------------------------------------------- 1 | #Stormpath is Joining Okta 2 | We are incredibly excited to announce that [Stormpath is joining forces with Okta](https://stormpath.com/blog/stormpaths-new-path?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement). Please visit [the Migration FAQs](https://stormpath.com/oktaplusstormpath?utm_source=github&utm_medium=readme&utm-campaign=okta-announcement) for a detailed look at what this means for Stormpath users. 3 | 4 | We're available to answer all questions at [support@stormpath.com](mailto:support@stormpath.com). 5 | 6 | # stormpath-framework-spec 7 | Language-agnostic API specification for Stormpath Framework Integrations 8 | -------------------------------------------------------------------------------- /authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | The underlying SDKs provide Authenticators, which are used for authenticating 4 | a user in various contexts. A full list can be found here: 5 | 6 | https://github.com/stormpath/stormpath-sdk-spec/blob/master/specifications/authenticators.md 7 | 8 | Those authenticators have an interface which is *not* HTTP-specific, the 9 | interfaces work with the necessary parameters for achieving authentication. 10 | 11 | To provide extra convenience, we have defined Account Resolvers to help the application read the HTTP request and return authentication information to the application. In addition, the framework integrations MAY provide additional convenience methods to respond to the incoming request. 12 | 13 | **Account Resolvers** 14 | 15 | The framework integration will provide the application with some means of resolving authentication information. This means: 16 | 17 | * Reading the request headers for authentication information 18 | * Resolving the access token or API key presented to a Stormpath account 19 | * Return this information to the application, along with information about how the request was authenticated. 20 | 21 | This information will allow the application to make authorization decisions for the request. 22 | 23 | **Unauthenticated Requests** 24 | 25 | As a convenience, the framework integration MAY provide the application with a means to use the authentication information to return an appropriate response if the request is unauthenticated. 26 | 27 | If implemented, these convenience functions will render an error in the best content type as according to the Accept headers and produces configuration. 28 | 29 | If `application/json` is preferred: Render `401 Unauthorized` with a blank body. 30 | 31 | If `text/html` is preferred: Redirect to `stormpath.web.login.uri`. Make sure to pass information to redirect the user back to the current page. 32 | 33 | ## How Requests are Authenticated 34 | 35 | There are multiple ways that clients can present their authentication credentials to the web framework: 36 | 37 | **Cookie Authentication** 38 | 39 | The framework integration should have the ability to accept an OAuth access or refresh token in the cookies as detailed in the [cookie authentication](cookie-authentication.md) document. 40 | 41 | **Bearer Authentication** 42 | 43 | The framework integration should be able to read an OAuth access token presented in a Bearer Authentication scheme. For reference, access token may be generated by the `password` or `client_credentials` grant types. As an example, this is a HTTP header that looks like this: 44 | 45 | ``` 46 | Authorization: Bearer 47 | ``` 48 | 49 | **Basic Authentication** 50 | 51 | The framework integration should be able to read API keys for an Account resource mapped to the application presented in Basic Authentication form. As an example, this is a HTTP header that looks like this: 52 | 53 | ``` 54 | Authorization: Basic 55 | ``` -------------------------------------------------------------------------------- /configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration Options 2 | 3 | ## Overview 4 | 5 | The framework integration has a set of configuration options that are specific 6 | to web applications, this configuration extends the [base configuration loaded by the 7 | underlying SDK](https://github.com/stormpath/stormpath-sdk-spec/blob/master/specifications/config.md). 8 | 9 | The entire framework configuration reference can be found in the 10 | [web-config.yaml](web-config.yaml) file. 11 | 12 | The configuration options inherently refer to high level features that the 13 | framework integration should implement, you will find that each high level 14 | feature has it's own markdown file in this repository. 15 | 16 | ## Application validation 17 | 18 | Our framework integrations will need to know which Stormpath Application should 19 | be used (as all authentication features of Stormpath are tied to Applications). 20 | 21 | The developer needs to specify the application, by name or href, using one of 22 | these configuration properties: 23 | 24 | ```yaml 25 | stormpath: 26 | application: 27 | name: null 28 | href: null 29 | ``` 30 | 31 | At startup, the integration should use these validation rules: 32 | 33 | * If the key `application.href` exists, but does not contain the substring `/applications/`, throw an error. This is a simple sanity check to see if the href provided is a valid Stormpath application href. Use this exception message: 34 | 35 | > `'(the invalid href)' is not a valid Stormpath Application href.` 36 | 37 | * If the key `application.href` exists, try to look up the Stormpath Application with that href. If the application can't be found, use this error message: 38 | 39 | > The provided application could not be found. The provided application 40 | href was: %href% 41 | 42 | * If the key `application.name` exists, try to look up the Stormpath Application with that name. If the application can't be found, use this error message: 43 | 44 | > The provided application could not be found. The provided application 45 | name was: %name% 46 | 47 | * If neither `application.href` or `application.name` are specified, attempt to resolve the application using the rules below. 48 | 49 | ## Application resolution 50 | 51 | If an application is not specified in configuration, the integration should attempt to use the 52 | tenant's only application (if the tenant only has one application). 53 | 54 | **NOTE**: all tenants have an application called "Stormpath". This application cannot be 55 | modified and should be **excluded** when looking to see if the tenant only has one 56 | application. 57 | 58 | * If a single application (besides "Stormpath") exists in the tenant, use that application. 59 | 60 | * If zero or more than one applications (besides "Stormpath") exist in the tenant, throw this error: 61 | 62 | > Could not automatically resolve a Stormpath Application. Please specify 63 | your Stormpath Application in your configuration. 64 | 65 | ## Account Store Resolution 66 | 67 | Account stores are required for login and registration. As such, we need to 68 | look at the account stores that are mapped to the application that is specified 69 | by the developer. The following cases need to be validated. 70 | 71 | * If the specified application does not have any account stores mapped to it, 72 | this exception should be thrown: 73 | 74 | > No account stores are mapped to the specified application. 75 | Account stores are required for login and registration. 76 | 77 | * If `stormpath.web.register.enabled` is true, and there is no default account 78 | store mapped to the application: 79 | 80 | > No default account store is mapped to the specified application. A default 81 | account store is required for registration. 82 | 83 | ## Account Schema 84 | 85 | If the account schema is requiring a field, but the developer attempts to override the requirement with `stormpath.web.register.form.fields..required = false`, we should provide a warning to the developer, as this is an invalid configuration, because the account schema will be the setting that is used, and the field will still be required when processing the registration form. -------------------------------------------------------------------------------- /cookie-authentication.md: -------------------------------------------------------------------------------- 1 | Back to Top 2 | 3 | # Cookie Authentication 4 | 5 | When a user authenticates with a web browser, we need to persist that authentication 6 | result so that the user does not have to present their credentials on every request. 7 | To achieve this we will use our OAuth2 password grant flow, and store the 8 | tokens in cookies. 9 | 10 | **NOTE**: This is *not* a "session" implementation, there is no server-side 11 | state being managed for the developer. This is pure authentication, using 12 | cookies as the secure storage location on the client. 13 | 14 | ### Authentication Flow 15 | 16 | 1. The user visits the login page and submits their login and password. 17 | 18 | 2. The integration uses the SDK to perform the password grant exchange, using 19 | the Stormpath Application's `/oauth/token` endpoint. This means that the login route is going to be using the SDK of choice to interact with the /v1/applications/:appId/oauth/token endpoint. 20 | 21 | 3. The integration collects the access token and refresh token from the SDK 22 | response in the previous step. It then responds to the browser request by 23 | settings these values in cookies. 24 | 25 | 4. Subsequent requests by the browser will supply these cookies which contain the 26 | access and refresh tokens. The integration should perform the following 27 | logic, using the SDK's relevant JWT and OAuth authenticators: 28 | 29 | * If the access token is valid, authenticate the request 30 | 31 | * If the access token is invalid or missing, attempt to use the refresh token to get a 32 | new access token. 33 | 34 | * If a new access token is obtained, authenticate the request and store this 35 | new token value in the access token cookie. 36 | 37 | * If a new access token cannot be obtained, reject the request and delete the 38 | refresh token cookie and the access token cookie. 39 | 40 | ### Rejecting Requests 41 | 42 | If the request cannot be authenticated, how to respond will depend on the 43 | content preference of the request: 44 | 45 | * `text/html` - respond with a 302 redirect to the login view, and preserve the current URL so the user can be redirected to their original destination after login. If possible, use built-in features of the host framework to do this securely; otherwise, use a secure method of passing the destination URL forward. 46 | 47 | * `application/json` - respond with `401 Unauthorized` and an empty body. 48 | 49 | ### Cookie Flags 50 | 51 | For each cookie, access token and refresh token, use these rules to determine 52 | the flags to set on the cookies when you are sending them to the browser: 53 | 54 | * The `expires` flag should be determined by the `ttl` value for the 55 | token, as defined by the Oauth Policy of the Stormpath Application. 56 | 57 | * The `HttpOnly` flag should be set as follows: 58 | 59 | * If `stormpath.web.[accessTokenCookie|refreshTokenCookie].httpOnly` is false, do not set this flag 60 | * Othwerwise, set this flag to true 61 | 62 | * The `path` flag should be set as follows: 63 | 64 | If `stormpath.web.[accessTokenCookie|refreshTokenCookie].path` has a non-null 65 | value, use that value as the cookie path. If the value is undefined or null, 66 | default to the web application framework default value. If the framework does 67 | not provide a default value, fall back to `stormpath.web.basePath` if defined. 68 | If `stormpath.web.basePath` is undefined or null, fallback to `/`. 69 | 70 | * The `domain` flag should be set as follows: 71 | 72 | * If `stormpath.web.[accessTokenCookie|refreshTokenCookie].domain` has a 73 | non-null value, use that value. Otherwise, fallback to the web framework 74 | domain value (if it defines one). Finally, fallback to "" as the value. 75 | 76 | * The `secure` flag should be set as follows: 77 | 78 | * If `stormpath.web.[accessTokenCookie|refreshTokenCookie].secure` is defined, use that value 79 | * Othwerwise, the framework should make a best effort to determine if the 80 | incoming request is HTTPS and set the `secure` flag if so. 81 | 82 | ## Configuration Options 83 | 84 | The developer has control over the name of the cookie and the flags that 85 | control the cookie behavior in the browser. The lifetime of the cookies is 86 | controlled by the application's OAuth Policy, and as such is not represented 87 | in these option blocks. 88 | 89 | ```yaml 90 | web: 91 | accessTokenCookie: 92 | name: "access_token" 93 | httpOnly: true 94 | secure: null 95 | path: null 96 | domain: null 97 | refreshTokenCookie: 98 | name: "refresh_token" 99 | httpOnly: true 100 | secure: null 101 | path: null 102 | domain: null 103 | ``` 104 | 105 | #### httpOnly 106 | 107 | This option determines whether or not this cookie can be read by JavaScript code 108 | on the browser. By default this is true, as this is secure. 109 | 110 | **NOTE**: Disabling this option is a potential security issue, as your access 111 | tokens could be hijacked via XSS. 112 | 113 | Back to Top 114 | 115 | 116 | #### secure 117 | 118 | This flag informs the browser what types of transport can be used for sending 119 | the cookie. If this option is `true`, then the browser will only send the 120 | cookie if the connection to the server is using HTTPS. 121 | 122 | This value is null by default, which means that either (a) the developer must 123 | explicitly set this value or (b) the framework should make a best effort attempt 124 | to determine if the incoming request is occurring over an HTTPS connection. 125 | 126 | It is our recommendation that the developer define this value as `true`, via 127 | configuration, in their production environment. 128 | 129 | **NOTE**: Setting this option to false is a security problem, as sending 130 | cookies over a non-https connection is vulnerable to man-in-the-middle attacks. 131 | 132 | Back to Top 133 | 134 | 135 | #### path 136 | 137 | This option determines what URI paths will be able to read the cookie. 138 | 139 | Back to Top 140 | 141 | 142 | #### domain 143 | 144 | This option determines what domain(s), can read this cookie value. 145 | 146 | Back to Top 147 | 148 | -------------------------------------------------------------------------------- /csrf.md: -------------------------------------------------------------------------------- 1 | # Cross-Site Request Forgery (CSRF) 2 | 3 | Because our library accepts data from HTML-based form posts, we need to have a 4 | CSRF solution for any endpoint that we handle POST requests for. 5 | 6 | POST requests to any endpoint that we handle, where the Content Type 7 | of the request is `application/x-www-form-urlencoded` MUST be verified with a 8 | CSRF token. 9 | 10 | How this is achieved is not defined by our specification, for solutions please 11 | refer to the [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet][]. 12 | 13 | ### Informational: Regarding JSON 14 | 15 | If the Content Type of the POST request is `application/json`, it can be assumed 16 | that this is not a CSRF attack because an HTML form cannot be maliciously 17 | modified to produce this content type. 18 | 19 | Note: the framework MUST NOT process a POST body as JSON if the Content-Type is 20 | not JSON, to avoid the text/plain workaround that is described in 16.3.1 CSRF 21 | protection and JSON. 22 | 23 | Despite this, some frameworks still require `application/json` POSTS to supply 24 | a token. In these situations, we should use documentation to describe how 25 | clients can obtain token and submit it with the request. 26 | 27 | [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet 28 | -------------------------------------------------------------------------------- /email-verification.md: -------------------------------------------------------------------------------- 1 | Back to Top 2 | 3 | # Email Verification 4 | 5 | ## Feature Description 6 | 7 | This document describes the endpoints and logic that must exist in order to 8 | facilitate self-service verification of newly registered user accounts. 9 | 10 | If the application's default account store has the email verification workflow 11 | enabled, and `stormpath.web.verifyEmail.enabled` is not set to `false`, our library 12 | MUST intercept incoming requests at `stormpath.web.verifyEmail.uri` and follow the 13 | request handling procedure that is defined below. 14 | 15 | ## Request Handling 16 | 17 | #### GET Requests That Prefer `text/html` 18 | 19 | * If there is a `?sptoken` query parameter in the URL: 20 | 21 | * Attempt to consume the `sptoken` by posting it to the tenant collection, `/v1/accounts/emailVerificationTokens/:sptoken` 22 | 23 | * If the token is valid and `autoLogin` is enabled, follow the standard [post login logic](login.md#-post-response-handling) (like setting cookies, and redirecting the user) 24 | 25 | * If the token is valid and `autoLogin` is disabled, redirect to `stormpath.web.verifyEmail.nextUri` 26 | 27 | * If the token is invalid, render a form that allows them to request a new link 28 | by submitting their email address. The form should show the error: 29 | 30 | > This verification link is no longer valid. Please request a new link from 31 | the form below. 32 | 33 | * If there isn't a `?sptoken` query parameter in the URL: 34 | 35 | * Render a form that allows them to request a new link by submitting their 36 | email address. 37 | 38 | #### GET Requests That Prefer `application/json` 39 | 40 | * If there is a `?sptoken` query parameter in the URL: 41 | 42 | * Attempt to consume the `sptoken` by posting it to the tenant collection, `/v1/accounts/emailVerificationTokens/:sptoken` 43 | 44 | * If the token is valid and `autoLogin` is enabled on the registration route, follow the standard [post login logic](login.md#-post-response-handling) (like setting cookies, and responding with the account object) 45 | 46 | * If the token is valid and `autoLogin` is disabled on the registration route, respond with `200 OK` and an empty body 47 | 48 | * If validation fails, respond with the JSON error from the API, according to 49 | the [Error Handling][] specification. 50 | 51 | * If there is't an `?sptoken` query parameter in the URL, respond with this 52 | error: 53 | 54 | ```javascript 55 | { 56 | status: 400, 57 | message: 'sptoken parameter not provided.' 58 | } 59 | ``` 60 | 61 | #### POST Requests 62 | 63 | This endpoint accepts post requests from the form which allows you to request 64 | a new link by entering your email address or username. The endpoint should parse the POST 65 | body as `application/json` and `application/x-www-form-urlencoded`. 66 | 67 | The format of the request is (JSON example): 68 | 69 | ```javascript 70 | { 71 | "login": "foo@bar.com" 72 | } 73 | ``` 74 | 75 | This data should be posed to the `/v1/applications/:id/verificationEmails` endpoint. 76 | 77 | Regardless of whether or not the email address is associated with a user 78 | account, we should respond according to the request preference: 79 | 80 | * `text/html`, redirect to `stormpath.web.login.uri` and 81 | append `?status=unverified` 82 | 83 | * `application/json`, the status of the response should be `200 OK` with no 84 | body. 85 | 86 | ## Options 87 | 88 | ```yaml 89 | stormpath: 90 | web: 91 | # Unless verifyEmail.enabled is specifically set to false, the email 92 | # verification feature must be automatically enabled if the default account 93 | # store for the defined Stormpath application has the email verification 94 | # workflow enabled. 95 | verifyEmail: 96 | enabled: null 97 | uri: "/verify" 98 | nextUri: "/login?status=verified" 99 | view: "verify" 100 | ``` 101 | 102 | 103 | #### enabled 104 | 105 | Default: `null` 106 | 107 | Unless explicitly set to false, the email verification feature must be 108 | automatically enabled if the default account store for the defined Stormpath 109 | application has the email verification workflow enabled. 110 | 111 | Back to Top 112 | 113 | #### autoLogin 114 | 115 | Default: `false` 116 | 117 | Defined in: `stormpath.web.register.autoLogin` 118 | 119 | If `true`, then once a user has successfully verified their email, they application will run the standard [post login logic](login.md#-post-response-handling) (like setting cookies), and respond with the same data as the `login` endpoint. 120 | 121 | #### uri 122 | 123 | Default: `/verify` 124 | 125 | The URI that we'll attach an interceptor to for GET and POST requests, if 126 | `enabled` is `true`. 127 | 128 | Back to Top 129 | 130 | 131 | #### nextUri 132 | 133 | Default: `/login` 134 | 135 | Where to send the user after successful verification. 136 | 137 | Back to Top 138 | 139 | #### view 140 | 141 | Default: `verify` 142 | 143 | A string key which identifies the view template that should be used. The 144 | default value may look different for your framework. The point of this value 145 | is to allow the developer to override our default view with their own. 146 | 147 | [Error Handling]: error-handling.md 148 | -------------------------------------------------------------------------------- /error-handling.md: -------------------------------------------------------------------------------- 1 | ### Error Handling 2 | 3 | Stormpath REST API error messages look like the following: 4 | 5 | ``` 6 | { 7 | "status": 400, 8 | "message": "Invalid username or password.", 9 | "code": 7100, 10 | "developerMessage": "Login attempt failed because the specified password is incorrect.", 11 | "moreInfo": "http://docs.stormpath.com/errors/7100" 12 | } 13 | ``` 14 | 15 | The only properties that should ever be exposed to the end-user are the message 16 | and status properties. As such, this error would be sent to the end-user like 17 | so: 18 | 19 | ``` 20 | { 21 | "status": 400, 22 | "message": "Invalid username or password." 23 | } 24 | ``` 25 | 26 | ### Error handling rules: 27 | 28 | * When rendering errors to the end user via an HTML view, use the message 29 | property from the REST API (if available). 30 | 31 | * When rendering errors as JSON: 32 | * Send the message and status properties ONLY. 33 | * Set the HTTP status of the response to the value of the API error response. 34 | If the API error does not have a status, then use 400. 35 | 36 | 37 | * If the error is created by the framework integration (it is not from the REST 38 | API), then use status 400 and a human-friendly message string. 39 | 40 | * If there are multiple errors for a given request, choose the most relevant one 41 | and respond with that single error. 42 | 43 | * Unexpected errors, such as network communication errors with the Stormpath 44 | API, should be returned as a 500 Internal Server Error message. 45 | 46 | * Error responses from the `/oauth/token` endpoint should pass ONLY the `error` 47 | and `message` properties. -------------------------------------------------------------------------------- /example-config.yaml: -------------------------------------------------------------------------------- 1 | stormpath: 2 | application: 3 | href: null 4 | name: null 5 | 6 | web: 7 | 8 | basePath: null 9 | 10 | domainName: null # Required if using subdomain-based multi-tenancy 11 | 12 | multiTenancy: 13 | 14 | # When enabled, the framework will require the user to authenticate against 15 | # a specific Organization. The authenticated organization is persisted in 16 | # the access token that is issued. 17 | # 18 | # At the moment we only support a sub-domain based strategy, wherby the 19 | # user must arrive on the subdomain that correlates with their tenant in 20 | # order to authenticate. If they visit the parent domain, they will be 21 | # required to identify the organization they wish to use. 22 | 23 | enabled: false 24 | strategy: "subdomain" 25 | 26 | 27 | oauth2: 28 | enabled: true 29 | uri: "/oauth/token" 30 | client_credentials: 31 | enabled: true 32 | password: 33 | enabled: true 34 | validationStrategy: "local" 35 | 36 | accessTokenCookie: 37 | name: "access_token" 38 | httpOnly: true 39 | 40 | # See cookie-authentication.md for explanation of 41 | # how `null` values behave for these properties. 42 | secure: null 43 | path: null 44 | domain: null 45 | 46 | refreshTokenCookie: 47 | name: "refresh_token" 48 | httpOnly: true 49 | 50 | # See cookie-authentication.md for explanation of 51 | # how `null` values behave for these properties. 52 | secure: null 53 | path: null 54 | domain: null 55 | 56 | # By default the Stormpath integration will respond to JSON and HTML 57 | # requests. If a requested type is not in this list, the Stormpath 58 | # integration should pass on the request, and allow the developer or base 59 | # framework to handle the response. 60 | # 61 | # If the request does not specify an Accept header, or the preferred content 62 | # type is */*, the Stormpath integration will respond with the first type in 63 | # this list. 64 | produces: 65 | - application/json 66 | - text/html 67 | 68 | # For the common account fields (givenName, surname, etc) the `required` 69 | # property is derived from the account schema of the default account store 70 | # of the application or organization. This value can be locally overridden 71 | # as `true`, even if `false` in the account schema. This will provide form 72 | # validation at the framework level only. Attempting to override with `false` 73 | # when the schema defines `true` will result in a configuration warning, and 74 | # a the end-user will receive a form submission error if the required field 75 | # is not provided. 76 | 77 | register: 78 | enabled: true 79 | uri: "/register" 80 | nextUri: "/" 81 | autoLogin: false 82 | form: 83 | fields: 84 | # This field will be shown as the only field if the user is visiting 85 | # the parent domain of a sub-domain based multi-tenant configuration. 86 | # The user will be redirected to the correct subdomain to finish the 87 | # workflow. 88 | organizationNameKey: 89 | enabled: null 90 | visible: true 91 | label: "Organization" 92 | placeholder: "e.g. my-company" 93 | required: true 94 | type: "text" 95 | givenName: 96 | enabled: true 97 | visible: true 98 | label: "First Name" 99 | placeholder: "First Name" 100 | required: null 101 | type: "text" 102 | middleName: 103 | enabled: false 104 | visible: true 105 | label: "Middle Name" 106 | placeholder: "Middle Name" 107 | required: null 108 | type: "text" 109 | surname: 110 | enabled: true 111 | visible: true 112 | label: "Last Name" 113 | placeholder: "Last Name" 114 | required: null 115 | type: "text" 116 | username: 117 | enabled: false 118 | visible: true 119 | label: "Username" 120 | placeholder: "Username" 121 | required: null 122 | type: "text" 123 | email: 124 | enabled: true 125 | visible: true 126 | label: "Email" 127 | placeholder: "Email" 128 | required: null 129 | type: "email" 130 | password: 131 | enabled: true 132 | visible: true 133 | label: "Password" 134 | placeholder: "Password" 135 | required: true 136 | type: "password" 137 | confirmPassword: 138 | enabled: false 139 | visible: true 140 | label: "Confirm Password" 141 | placeholder: "Confirm Password" 142 | required: true 143 | type: "password" 144 | fieldOrder: 145 | - "organizationNameKey" 146 | - "username" 147 | - "givenName" 148 | - "middleName" 149 | - "surname" 150 | - "email" 151 | - "password" 152 | - "confirmPassword" 153 | view: "register" 154 | 155 | # Unless verifyEmail.enabled is specifically set to false, the email 156 | # verification feature must be automatically enabled if the default account 157 | # store for the defined Stormpath application has the email verification 158 | # workflow enabled. 159 | verifyEmail: 160 | enabled: null 161 | uri: "/verify" 162 | nextUri: "/login?status=verified" 163 | view: "verify" 164 | 165 | login: 166 | enabled: true 167 | uri: "/login" 168 | nextUri: "/" 169 | view: "login" 170 | form: 171 | fields: 172 | # This field will be shown as the only field if the user is visiting 173 | # the parent domain of a sub-domain based multi-tenant configuration. 174 | # The user will be redirected to the correct subdomain to finish the 175 | # workflow. 176 | organizationNameKey: 177 | enabled: null 178 | visible: true 179 | label: "Organization" 180 | placeholder: "e.g. my-company" 181 | required: true 182 | type: "text" 183 | login: 184 | enabled: true 185 | visible: true 186 | label: "Username or Email" 187 | placeholder: "Username or Email" 188 | required: true 189 | type: "text" 190 | password: 191 | enabled: true 192 | visible: true 193 | label: "Password" 194 | placeholder: "Password" 195 | required: true 196 | type: "password" 197 | fieldOrder: 198 | - "organizationNameKey" 199 | - "login" 200 | - "password" 201 | 202 | logout: 203 | enabled: true 204 | uri: "/logout" 205 | nextUri: "/" 206 | 207 | # If using subdoain multi-tenancy, this form is shown on the parent domain 208 | # when organization context is unkonwn. Configuration of this form is limited 209 | # to label, placeholder and view template location. 210 | 211 | organizationSelect: 212 | view: "organization-select" 213 | form: 214 | fields: 215 | organizationNameKey: 216 | label: "Enter your organization name to continue" 217 | placeholder: "e.g. my-company" 218 | 219 | # Unless forgotPassword.enabled is explicitly set to false, this feature 220 | # will be automatically enabled if the default account store for the defined 221 | # Stormpath application has the password reset workflow enabled. 222 | forgotPassword: 223 | enabled: null 224 | uri: "/forgot" 225 | view: "forgot-password" 226 | nextUri: "/login?status=forgot" 227 | 228 | # Unless changePassword.enabled is explicitly set to false, this feature 229 | # will be automatically enabled if the default account store for the defined 230 | # Stormpath application has the password reset workflow enabled. 231 | changePassword: 232 | enabled: null 233 | autoLogin: false 234 | uri: "/change" 235 | nextUri: "/login?status=reset" 236 | view: "change-password" 237 | errorUri: "/forgot?status=invalid_sptoken" 238 | 239 | # If idSite.enabled is true, the user should be redirected to ID site for 240 | # login, registration, and password reset. They should also be redirected 241 | # through ID Site on logout. 242 | idSite: 243 | enabled: false 244 | loginUri: "" 245 | forgotUri: "/#/forgot" 246 | registerUri: "/#/register" 247 | 248 | # A callback so Stormpath can pass information to the web application. This is 249 | # currently being used for ID Site, but may be used in the future for SAML, 250 | # Stormpath handled social login, webhooks, and other messages from Stormpath. 251 | callback: 252 | enabled: true 253 | uri: "/stormpathCallback" 254 | 255 | # Social login configuration. This defines the callback URIs for OAuth 256 | # flows, and the scope that is requested of each provider. Some providers 257 | # want space-separated scopes, some want comma-separated. As such, these 258 | # string values should be passed directly, as defined. 259 | # 260 | # These settings have no affect if the application does not have an account 261 | # store for the given provider. 262 | social: 263 | facebook: 264 | uri: "/callbacks/facebook" 265 | scope: "email" 266 | github: 267 | uri: "/callbacks/github" 268 | scope: "user:email" 269 | google: 270 | uri: "/callbacks/google" 271 | scope: "email profile" 272 | linkedin: 273 | uri: "/callbacks/linkedin" 274 | scope: "r_basicprofile r_emailaddress" 275 | 276 | # The /me route is for front-end applications, it returns a JSON object with 277 | # the current user object. The developer can opt-in to expanding account 278 | # resources on this enpdoint. 279 | me: 280 | enabled: true 281 | uri: "/me" 282 | expand: 283 | apiKeys: false 284 | applications: false 285 | customData: false 286 | directory: false 287 | groupMemberships: false 288 | groups: false 289 | providerData: false 290 | tenant: false 291 | 292 | -------------------------------------------------------------------------------- /handlers.md: -------------------------------------------------------------------------------- 1 | # Handlers 2 | 3 | The framework integration should support the following handlers in a way that 4 | makes sense for the framework. These handlers are convenience handlers that 5 | allow developer to work with the current account or authentication context, 6 | without having to re-implement our default HTTP response behaviors. 7 | 8 | All of these handlers should give the developer the following control: 9 | 10 | * Access to the HTTP request and HTTP response data 11 | * Work with the account object of the context 12 | * Allow the default HTTP response cycle to continue (default behavior) 13 | * Manually end the HTTP response 14 | 15 | The default HTTP response behavior for login and registration is defined in 16 | [login.md](login.md) [registration.md](registration.md). 17 | 18 | ### Post-Registration Handler 19 | 20 | This handler should be invoked immediately after an account is created via the 21 | account registration process (i.e. the end user has just registered for a new 22 | account). 23 | 24 | **Common use cases**: 25 | 26 | * Populate custom data on the user object. 27 | * Synchronize an external database with Stormpath. 28 | 29 | ### Pre-Registration Handler 30 | 31 | This handler should be invoked immediately before an account will be created. 32 | The developer can modify the data that will be used to create the account. 33 | 34 | **Common use cases**: 35 | 36 | * Assert that the registration is permitted by inspecting the email address. 37 | * Target an account store for the account creation attempt. 38 | * Custom redirect based on business logic 39 | 40 | ### Post-Login Handler 41 | 42 | This handler should be invoked immediately after an account has been 43 | authenticated by a login form submission or OAuth2 authentication flow. 44 | 45 | **Common use cases**: 46 | 47 | * Login auditing 48 | * Custom redirect based on business logic 49 | 50 | ### Pre-Login Handler 51 | 52 | This handler should be invoked immediately before an account will be 53 | authenticated. The developer can modify the data that will be used to 54 | authenticate the account. 55 | 56 | 57 | **Common use cases**: 58 | 59 | * Determine which account store should be used for the login attempt -------------------------------------------------------------------------------- /idsite.md: -------------------------------------------------------------------------------- 1 | # ID Site 2 | 3 | ID Site is a hosted login & registration application that simplifies single-sign on (SSO) flows by providing a common place for redirects and cookie storage. If the developer has chosen to use ID Site, they likely have multiple Stormpath Applications that are redirecting the user to ID Site for authentication. When the user authenticates at ID Site, they are returned to the original application (service provider, AKA the SP) with Stormpath Assertion JWT. The application can verify this token and resolve the account that has authenticated. 4 | 5 | If the developer needs to use ID Site, they will enable it with `stormpath.web.idSite.enabled`. `stormpath.web.callback.enabled` must also be true, otherwise the integration should throw an error. 6 | 7 | When ID Site is enabled, we should redirect the user to ID Site when the following URIs are accessed with a GET request: 8 | 9 | * `stormpath.web.login.uri` - with a path of `stormpath.web.idSite.loginUri` 10 | * `stormpath.web.logout.uri` 11 | * `stormpath.web.register.uri` - with a path of `stormpath.web.idSite.registerUri` 12 | * `stormpath.web.forgot.uri` - with a path of `stormpath.web.idSite.forgotUri` 13 | 14 | In addition, ID Site needs `stormpath.web.callback.enabled` to be true, so we can pass back the Stormpath assertion JWT. 15 | 16 | # `/stormpathCallback` 17 | 18 | A callback so Stormpath can pass information to the web application. This is currently being used for ID Site, but may be used in the future for SAML, Stormpath handled social login, webhooks, and other messages from Stormpath. 19 | 20 | ## Configuration 21 | 22 | callback: 23 | enabled: true 24 | uri: "/stormpathCallback" 25 | 26 | ## Request Types 27 | 28 | * GET 29 | 30 | ## Content Type 31 | 32 | * Any - always follows `text/html` logic. 33 | 34 | ## Parameters 35 | 36 | `jwtResponse` - a [Stormpath JWT](http://docs.stormpath.com/guides/using-id-site/#handling-the-callback-to-your-application-from-id-site). This should be handled using the underlying SDK's Stormpath Token authenticator. 37 | 38 | ## Response 39 | 40 | The `jwtResponse` from ID Site can have three states, with the following response logic: 41 | 42 | **REGISTERED** 43 | 44 | A new user was created. Follow the standard [post registration logic](registration.md#-post-response-handling) (like redirecting to the login page or autologin). 45 | 46 | **AUTHENTICATED** 47 | 48 | A user logged in. Follow the standard [post login logic](login.md#-post-response-handling) (like setting cookies, and redirecting the user) 49 | 50 | **LOGOUT** 51 | 52 | A user logged out. Follow the standard [post logout logic](logout.md). 53 | 54 | ## Errors 55 | 56 | If not a valid Stormpath JWT, render a `text/html` 401 Unauthorized` error. -------------------------------------------------------------------------------- /login.md: -------------------------------------------------------------------------------- 1 | Back to Top 2 | 3 | # Account Login 4 | 5 | ## Feature Description 6 | 7 | This document describes the endpoints and logic that must exist in order to 8 | facilitate self-service login of user accounts. 9 | 10 | If enabled by `stormpath.web.login.enabled`, our library MUST inspect 11 | incoming requests for `stormpath.web.login.uri`. and determine if a response 12 | is needed, based on our [Content Negotiation Strategy][]. 13 | 14 | GET requests may: 15 | 16 | * Serve a default HTML page with a dynamic login form. 17 | 18 | * Serve the dynamic login JSON view model. 19 | 20 | * Redirect the user to ID Site if `stormpath.web.idSite.enabled` is `true`. This 21 | should be done with the ID Site URL Builder in the SDK. 22 | 23 | * Pass on the request. 24 | 25 | POST requests may: 26 | 27 | * Handle a POST request from the default HTML login form or from a JSON client. 28 | 29 | ## Options 30 | 31 | The default options for this feature are: 32 | 33 | ```yaml 34 | stormpath: 35 | web: 36 | login: 37 | enabled: true 38 | uri: "/login" 39 | nextUri: "/" 40 | view: "login" 41 | form: 42 | fields: 43 | login: 44 | enabled: true 45 | visible: true 46 | label: "Username or Email" 47 | placeholder: "Username or Email" 48 | required: true 49 | type: "text" 50 | password: 51 | enabled: true 52 | visible: true 53 | label: "Password" 54 | placeholder: "Password" 55 | required: true 56 | type: "password" 57 | fieldOrder: 58 | - "login" 59 | - "password" 60 | ``` 61 | 62 | #### enabled 63 | 64 | Default: `true` 65 | 66 | If `true`, this feature will be enabled and our library will intercept requests 67 | for `uri`. 68 | 69 | If `false`, this feature is disabled and the base framework will be responsible 70 | for the `uri`, likely resulting in a 404 Not Found error. 71 | 72 | **NOTE**: If this feature is enabled, and no Account Stores are mapped to this 73 | Application -- then throw an error during framework initialization as there are 74 | no possible ways for a user to authenticate. 75 | 76 | #### uri 77 | 78 | Default: `/login` 79 | 80 | The URI that our integration should bind to for handling GET and POST requests 81 | for the `uri`. 82 | 83 | #### nextUri 84 | 85 | Default: `/` 86 | 87 | Where to send the user by default after successful login. This behavior can be overridden on a per-request basis (see [Cookie Authentication#Rejecting Requests](cookie-authentication.md#rejecting-requests)). 88 | 89 | #### view 90 | 91 | Default: `login` 92 | 93 | A string key which identifies the view template that should be used. The 94 | default value may look different for your framework. The point of this value 95 | is to allow the developer to override our default view with their own. 96 | 97 | #### Form 98 | 99 | See [Registration Form](registration.md#-formfields) 100 | 101 | Back to Top 102 | 103 | 104 | 105 | ## Default HTML Login Form 106 | 107 | The form MUST: 108 | 109 | * Require a login (email address or username) AND password. 110 | 111 | * Render provider login buttons, if provider account stores are mapped to the 112 | specified Stormpath application. 113 | 114 | * Render context specific messages, depending on the status query parameter 115 | (see ["Status Messages"](#status-messages) section). 116 | 117 | * Sets [authenication cookies](https://github.com/stormpath/stormpath-framework-spec/blob/master/cookie-authentication.md) (OAuth 2.0 Access / Refresh Tokens) on successful authentication (username/password). 118 | 119 | 120 | ## Login View Model 121 | 122 | The login view model should be returned to the client if the GET request 123 | prefers `application/json` and `stormpath.web.produces` contains 124 | `application/json`. This is for front-end or mobile clients that need to 125 | dynamically know how to render the login form. 126 | 127 | The model should have: 128 | 129 | * A list of fields, as defined by `stormpath.web.login.fields`, and ordered by 130 | `stormpath.web.login.fieldOrder`. Fields should only be in the list if their 131 | `enabled` property is `true`. As such the enabled property can be omitted 132 | from each list element. Any enabled fields not in `fieldOrder` should be appended to the end of the list. 133 | 134 | * A list of providers, such as social providers or SAML providers. Providers 135 | are found by looking at the account store mappings of the specified 136 | application. 137 | * Social providers will need to expose the `clientId`, as front-end 138 | applications will need this. 139 | * SAML providers need to provide the name of the directory, so that we know 140 | what text to use for the button, and the href, so that we can place this 141 | value into the SAML request (as a query parameter in the link that the 142 | button points to). 143 | * The ordering of this list should follow the ordering of the account store 144 | mappings. **NOTE**: this may change in the future. 145 | 146 | 147 | Example view model definition: 148 | 149 | ```javascript 150 | { 151 | "form": { 152 | "fields": [ 153 | { 154 | "label": "Username or Email", 155 | "name": "login", 156 | "placeholder": "Username or Email", 157 | "required": true, 158 | "type": "text" 159 | }, 160 | { 161 | "label": "Password" 162 | "name": "password", 163 | "placeholder": "Password", 164 | "required": true, 165 | "type": "password" 166 | } 167 | ], 168 | }, 169 | "accountStores": [ 170 | { 171 | "href": "https://api.stormpath.com/v1/directories/whatev" 172 | "name": "Name of account store" 173 | "provider": { 174 | "href": "...", 175 | "providerId": "saml" 176 | //DO NOT EXPOSE ANY OTHER SAML DATA 177 | } 178 | }, 179 | { 180 | "href": "https://api.stormpath.com/v1/directories/whatev2" 181 | "name": "Google" 182 | "provider": { 183 | "href": "href of the directory", 184 | "providerId": "google", 185 | "clientId": "WHATEVS", 186 | "scope": "email profile" // <-- from stormpath.web.social.google.scope 187 | //DO NOT EXPOSE ANY SECRET HERE. NO CLIENT SECRETS! 188 | } 189 | } 190 | ] 191 | } 192 | ``` 193 | 194 | 195 | ## POST Body Format 196 | 197 | This endpoint accepts password based login, and social login. If the POST is 198 | coming from an HTML based login form, the data will be submitted as 199 | `application/x-www-form-urlencoded`. Front-end applications will post the data 200 | as `application/json`. 201 | 202 | **Password-based login** 203 | 204 | For example, a form-based post would look like this: 205 | 206 | ```x-www-form-urlencoded 207 | login=robert@stormpath.com& 208 | password=mypassword 209 | ``` 210 | 211 | * The `login` field value can be either a username or email. 212 | 213 | * If any required field is omitted, an error will be raised and the page will be 214 | re-rendered. 215 | 216 | * Additional configured login form parameters will be ignored by the framework integration, but should be exposed to the application via the [pre and post login handlers](https://github.com/stormpath/stormpath-framework-spec/blob/master/handlers.md#post-login-handler). 217 | 218 | **Social login** 219 | 220 | In this situation, the front-end client has obtained an access code or 221 | authorization code from the provider. The front-end client is now submitting 222 | this information, along with our providerId for the provider. 223 | 224 | ```json 225 | { 226 | "providerData": { 227 | "providerId": "google", 228 | "accessToken": "xxx", 229 | "code": "xxx" 230 | } 231 | } 232 | ``` 233 | 234 | * Only one of `accessToken` or `code` will be provided, both are listed 235 | for example purposes only. 236 | 237 | * See [social.md][] for more information about social login. 238 | 239 | Back to Top 240 | 241 | 242 | ## POST Error Handling 243 | 244 | **For HTML responses:** 245 | 246 | For any errors, the response should be a 200 OK and the form should be 247 | re-rendered with a UX that indicates which field is in error and what can be 248 | done to fix the problem. 249 | 250 | **For JSON responses:** 251 | 252 | Respond with the JSON error from the API, according to the [Error Handling][] 253 | specification. 254 | 255 | ## POST Response Handling 256 | 257 | This describes how we handle the response, after an account has been 258 | successfully authenticated. 259 | 260 | **For HTML responses:** 261 | 262 | Issue a 302 redirect to the `nextUri` and create a new user session. If the original destination was a different page, redirect to that location. (See [Cookie Authentication#Rejecting Requests](https://github.com/nbarbettini/stormpath-framework-spec/blob/patch-4/cookie-authentication.md#rejecting-requests)) 263 | 264 | **For JSON responses:** 265 | 266 | If the request prefers `application/json`, the response should be status 200 267 | with a JSON body, where the body contains the account object, but ONLY the root 268 | properties of the account should be presented. All linked resources must be 269 | omitted, to prevent leakage of sensitive user data. For example: 270 | 271 | ``` 272 | HTTP/1.1 200 OK 273 | Content-Type: application/json; charset=utf-8 274 | 275 | { 276 | "account": { 277 | "href": "https://api.stormpath.com/v1/accounts/xxx", 278 | "username": "foo", 279 | "modifiedAt": "2016-01-26T20:50:03.931Z", 280 | "status": "ENABLED", 281 | "createdAt": "2015-10-13T20:54:22.215Z", 282 | "email": "bar@stormpath.com", 283 | "middleName": null, 284 | "surname": "foo", 285 | "givenName": "bar" 286 | "fullName": "bar foo" 287 | } 288 | } 289 | ``` 290 | 291 | Back to Top 292 | 293 | 294 | 295 | ## Status Messages 296 | 297 | The user may be redirected to this page from another workflow. The redirect 298 | will append a query parameter that tells you the context of the redirect. The 299 | query parameter name will be `status`. For each status, the login page should 300 | render the appropriate message above the login form: 301 | 302 | * `?status=unverified` - the user has successfully registered, but their account 303 | is unverified. Message to show: 304 | 305 | > Your account verification email has been sent! Before you can log into your 306 | account, you need to activate your account by clicking the link we sent to 307 | your inbox. Didn't get the email? 308 | `Click Here` 309 | 310 | * `?status=verified` - the user has successfully verified their account and can 311 | now login. Message to show: 312 | 313 | > Your Account Has Been Verified. You may now login. 314 | 315 | * `?status=created` - the user has successfully registered, and email 316 | verification is disabled so the user may login immediately. Message to show: 317 | 318 | > Your Account Has Been Created. You may now login. 319 | 320 | * `?status=forgot` - the user has submitted the forgot password form and we have 321 | sent them an email with a password reset link. Message to show: 322 | 323 | > Password Reset Requested. If an account exists for the email provided, you 324 | will receive an email shortly. 325 | 326 | * `?status=reset` - the user has finished the password reset workflow and has 327 | set a new password for their account. They may now login with their new 328 | password. Message to show: 329 | 330 | > Password Reset Successfully. You can now login with your new password. 331 | 332 | Back to Top 333 | 334 | 335 | [Content Negotiation Strategy]: requests.md#content-type-negotiation 336 | [Error Handling]: error-handling.md 337 | [social.md]: social.md 338 | -------------------------------------------------------------------------------- /logout.md: -------------------------------------------------------------------------------- 1 | # Logout 2 | 3 | The integration must implement the `/logout` endpoint. The endpoint is used to 4 | delete the [Authentication Cookies](cookie-authentication.md) that were set on 5 | login. The access and refresh tokens that were issued (and stored in cookies) 6 | must also be deleted from the Stormpath REST API. 7 | 8 | This endpoint should respond to POST requests only. Responding to GET requests 9 | is problematic because the browser's Omnibar can make arbitrary GET requests to 10 | this endpoint, and Robert can troll you with superlogout-dot-com. For more 11 | information please see 12 | [Logout: GET or POST?](http://stackoverflow.com/questions/3521290/logout-get-or-post) 13 | 14 | The response depends on the request preference: 15 | 16 | * `text/html` should redirect to `stormpath.web.logout.nextUri`. 17 | 18 | * `application/json` should respond with `200 OK` and an empty body. 19 | 20 | If ID Site is enabled with `stormpath.web.idSite.enabled`, the user should also 21 | be redirected through ID site after deleting the local cookies and deleting the 22 | tokens. This should be done with the ID Site URL Builder in the SDK, by 23 | setting the `logout: true` option when building a URL. This needs to happen 24 | because we maintain a cookie at `api.stormpath.com/sso` which needs to be 25 | removed. 26 | -------------------------------------------------------------------------------- /multi-tenancy.md: -------------------------------------------------------------------------------- 1 | # Multi Tenancy 2 | 3 | This document describes how our framework integrations should support our multi-tenancy feature, with a sane set of defaults. At the moment we are focusing on the subdomain-based used case. 4 | 5 | ## Subdomain-Based Multi-Tenancy With Organizations 6 | 7 | The following design guidelines must be followed when adding multi-tenancy features to a framework integration: 8 | 9 | * This is an opt-in configuration, the developer must define these properties to declare that they want to opt-in to our default organization-based multi-tenant solution: 10 | 11 | ````yaml 12 | stormpath.web.multiTenancy.enabled: true 13 | stormpath.web.multiTenancy.strategy: "subodmain" 14 | stormpath.web.domainName: "my-company.com 15 | ```` 16 | If the first property is enabled, but the others are not present, this should be a configuration warning, and the feature should not be enabled. 17 | 18 | * On login, registration, email verification, and password reset, we need to resolve which organization should be used for the operation (all of these REST API operations accept an account store as an optional parameter, where the account store is identified by `nameKey` or `href`). 19 | 20 | * The resolution should be achieved with a "Organization Resolver". The developer should be able to provide their own resolver, but our framework should provide a default resolver. 21 | 22 | ### Default Organization Resolver 23 | 24 | * The default resolver must have the following characteristics: 25 | 26 | - It receives a HTTP request for inspection 27 | - It returns an Organization if one can be resolved from the request context. 28 | - It only returns an Organization that is mapped to the configured application. 29 | - It attaches the resolved Organization to the request, so that the developer can make use of this context. 30 | - If the request `Host` header is `org-a.example.com` it returns the `Organization` that has the `nameKey` of `org-a`. 31 | - If the request body has a field of `organizationNameKey=org-a`, then the `Organization` which has the `nameKey` of `org-a` is returned. 32 | - The `Host` header value takes precedence over the form value if both are provided. 33 | 34 | * The default resolver may have the following characteristics: 35 | - It can be used as a standalone request filter/middleware component, so that the developer can make use of the resolution without opting in to the rest of the behavior that is described in this document. 36 | 37 | ### Changes to standard workflows 38 | 39 | If the developer has opted in to the subdomain strategy, the framework should do the following: 40 | 41 | * If the user requests a view that we control and the subdomain does not resolve to a organization, we should redirect the user to the parent domain, `/`, so that the user can provide their organization context. 42 | 43 | * If the user visits the root domain to specify organization context, the form should only show a field for specifying an organization name key. The user must enter a valid organization name key. An error is shown if the org is not valid. If the org is valid we redirect them back to the same view on the correct subdomain. 44 | 45 | ### Email Veficiation & Password Reset 46 | 47 | While it is possible to specify an account store when generating tokens for email verification and password reset, this context is not retained in the 48 | token/resource that is generated. This means that we can't know which organization the user intends to use when they are arriving on the webapp with an sptoken. We will likely fix this via the REST API in the future, but in the meantime the following options are available to the developer: 49 | 50 | * **Use a distinct directory per Organization**. This will allow them to define a subdomain-specific Link Base URL in the email templates for the directory, so that the user will always land on the correct subdomain when they click the link in their email. 51 | 52 | * **Use the parent domain as the Link Base Url**. In this situation, the end user will always land on the parent domain with the `sptoken` in the URL. The user must enter their organization name to get redirected to the correct subdomain, carrying the `sptoken` through, where they can complete the operation on the subdomain. 53 | 54 | ## Out of Scope 55 | 56 | The following features are currently out of scope, but please use documentation to suggest implementation strategies, if possible: 57 | 58 | * A "Forgot Organization" feature for the end-user. 59 | * Self-service creation of new organizations. 60 | -------------------------------------------------------------------------------- /oauth2.md: -------------------------------------------------------------------------------- 1 | Back to Top 2 | 3 | # Oauth2 Features 4 | 5 | Stormpath provides three Oauth2 grant types, `client_credentials`, `password`, and `refresh_token`. In 6 | this document we discuss how these should be exposed in the developer's web 7 | application. 8 | 9 | ## OAuth2 Configuration Options 10 | 11 | The OAuth2 endpoint can be configured, or disabled entirely: 12 | 13 | ```yaml 14 | web: 15 | oauth2: 16 | enabled: true 17 | uri: "/oauth/token" 18 | ``` 19 | 20 | #### stormpath.web.oauth2.enabled 21 | 22 | By default we accept POSTS to this token uri and respond according to the 23 | grant type. If the developer sets this value to false we should not attach any 24 | handler to this uri, allowing the framework to return it's default 404 response. 25 | 26 | ## Errors 27 | 28 | If the grant type requested is not enabled, then we 29 | should return the OAuth-compliant error code: 30 | 31 | HTTP/1.1 400 Bad Request 32 | Content-Type: application/json;charset=UTF-8 33 | Cache-Control: no-store 34 | Pragma: no-cache 35 | 36 | { 37 | "error": "unsupported_grant_type" 38 | } 39 | 40 | If no grant type is specified, the error should be `invalid_request`. 41 | 42 | A GET to the endpoint URL should always return `405 Method Not Allowed`. 43 | 44 | #### Error Transformations 45 | 46 | Error responses from the application's `/oauth/token` endpoint should be 47 | transformed to only include the message and error properties (see 48 | [Error Handling][]). This is an OAuth nuance that is only available on error 49 | responses from this endpoint. As such, an error response for this flow would 50 | look like this: 51 | 52 | ``` 53 | { 54 | "message": "grant_type passwordx is an unsupported value.", 55 | "error": "invalid_request" 56 | } 57 | ``` 58 | 59 | ## Client Credentials Grant Flow 60 | 61 | The product guide for this feature can be found here: 62 | 63 | https://docs.stormpath.com/guides/api-key-management/ 64 | 65 | In this workflow, an api key and secret is provisioned for a stormpath account. 66 | These credentials can be exchanged for an access token by making a POST request 67 | to `/oauth/token` on the web application. The request must look like this: 68 | 69 | ```` 70 | POST /oauth/token 71 | Authorization: Basic 72 | 73 | grant_type=client_credentials 74 | ```` 75 | 76 | For this flow the underlying Stormpath SDK is used for generating the access 77 | token and verifying that the API key & secret is valid, and that the account is 78 | not disabled. The response is an access token response, but this flow does not 79 | return a refresh token (only an access token): 80 | 81 | ``` 82 | HTTP/1.1 200 OK 83 | Content-Type: application/json;charset=UTF-8 84 | Cache-Control: no-store 85 | Pragma: no-cache 86 | 87 | { 88 | "access_token":"2YotnFZFEjr1zCsicMWpAA...", 89 | "expires_in":3600, 90 | "token_type":"Bearer" 91 | } 92 | ``` 93 | 94 | ### Client Credentials Options 95 | 96 | ```yaml 97 | stormpath: 98 | web: 99 | oauth2: 100 | client_credentials: 101 | enabled: true 102 | ``` 103 | 104 | #### stormpath.web.oauth2.client_credentials.enabled 105 | 106 | If set to false, the endpoint should return the `unsupported_grant_type` error 107 | (see above). 108 | 109 | ## Password Grant Flow 110 | 111 | The product guide for this feature can be found here: 112 | 113 | http://docs.stormpath.com/guides/token-management/ 114 | 115 | In this workflow, an account can post their login (username or email) and 116 | password to the `/oauth/token` endpoint, with the following body data: 117 | 118 | ```` 119 | POST /oauth/token 120 | 121 | grant_type=password 122 | &username= 123 | &password= 124 | ```` 125 | 126 | Although the Oauth2 spec requires the parameter to be named `username`, this 127 | value can also be the email address of a Stormpath account. 128 | 129 | The framework should use the underlying SDK to exchange these credentials 130 | for an access token response, using the `/oauth/token` endpoint of the 131 | Stormpath Application that is specified in the configuration. 132 | 133 | If the login is successful, we respond with an access token and refresh token. 134 | This means that you need to remove the `stormpath_access_token_href` from the 135 | API response. Thus, the format of the response should be an OAuth2 access token 136 | response, which looks like this: 137 | 138 | ``` 139 | HTTP/1.1 200 OK 140 | Content-Type: application/json;charset=UTF-8 141 | Cache-Control: no-store 142 | Pragma: no-cache 143 | 144 | { 145 | "access_token":"2YotnFZFEjr1zCsicMWpAA", 146 | "expires_in":3600, 147 | "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", 148 | "token_type":"Bearer" 149 | } 150 | ``` 151 | 152 | ### Password Grant Options 153 | 154 | ```yaml 155 | stormpath: 156 | web: 157 | oauth2: 158 | password: 159 | enabled: true 160 | validationStrategy: stormpath # or 'local' to do local JWT signature validation 161 | ``` 162 | 163 | 164 | #### stormpath.web.oauth2.password.enabled 165 | 166 | If set to false, the endpoint should return the `unsupported_grant_type` error 167 | (see above). 168 | 169 | #### stormpath.web.oauth2.password.validationStrategies 170 | 171 | Determines how we validate an access token. There are two strategies: 172 | 173 | * **local** - only verify the signature, expiration, and issuer 174 | 175 | * **stormpath** - always validate against Stormpath, for additional checks 176 | such as Account and Application status. This is the default, but the most 177 | consistent (will always be aware of Stormpath resource statuses). 178 | 179 | The underlying SDK must have authenticators which can do either type of 180 | validation. The purpose of this configuration option is to inform the framework 181 | intergration as to which type of authenticator it should build and use for 182 | authentication attempts. 183 | 184 | ## Refresh Grant Flow 185 | 186 | The product guide for this feature is found at: http://docs.stormpath.com/guides/token-management/#refreshing-access-tokens 187 | 188 | The refresh grant type is required for clients using the password grant type to refresh their access_token. Thus, it's automatically enabled alongside the password grant type. 189 | 190 | An account can post their refresh_token with the following body data: 191 | 192 | ``` 193 | POST /oauth/token 194 | grant_type=refresh_token& 195 | refresh_token= 196 | ``` 197 | 198 | The response is the same format as the password grant type. 199 | 200 | [Error Handling]: error-handling.md 201 | -------------------------------------------------------------------------------- /password-reset.md: -------------------------------------------------------------------------------- 1 | Back to Top 2 | 3 | # Password Reset 4 | 5 | ## Feature Description 6 | 7 | This document describes the endpoints and logic that must exist in order to 8 | facilitate self-service password reset of existing user accounts. 9 | 10 | This feature has two endpoints: 11 | 12 | * The `/forgot` endpoint, which is used for requesting a password reset email. 13 | 14 | * The `/change` endpoint, which is used for setting a new password and is 15 | linked to from the password reset email. This is where the user arrives 16 | with the `sptoken` that was sent in the email. 17 | 18 | 19 | ## Forgot Password Endpoint (`/forgot`) 20 | 21 | This is the endpoint that a user will use if they want to request a password 22 | reset email. 23 | 24 | If the default account store of the stormapth application has the password reset 25 | workflow enabled, and `stormpath.web.forgotPassword.enabled` is not set to 26 | `false`, our library MUST inspect incoming requests at 27 | `stormpath.web.forgotPassword.uri` and follow the request handling procedure 28 | that is defined below, to determine if a response is required. 29 | 30 | ### Request Handling 31 | 32 | #### GET Requests 33 | 34 | If the request prefers `text/html`, render the forgot-password form that is 35 | defined by `stormpath.web.forgotPassword.view`. 36 | 37 | There is no JSON response for the GET verb of this endpoint. 38 | 39 | **Parameters** 40 | 41 | This endpoint also takes a `status` query parameter, which will render an warning banner in the view. Currently, there is only one `status` parameter: 42 | 43 | `invalid_sptoken` - used when a user hits the `/change` endpoint with an invalid change password token. Renders the following error message: 44 | 45 | >"The password reset link you tried to use is no longer valid. Please request a new link from the form below." 46 | 47 | #### POST Requests 48 | 49 | This endpoint accepts a post from the password reset request form, and the only 50 | field in this form is the `email` field. The endpoint should parse the POST body 51 | content as `application/json` or `application/x-www-form-urlencoded`. 52 | 53 | The format of the request is (JSON example): 54 | 55 | ```javascript 56 | { 57 | "email": "foo@bar.com" 58 | } 59 | ``` 60 | 61 | Regardless of whether or not the email address is associated with a user 62 | account, we must do the following: 63 | 64 | * If the request prefers `text/html`, redirect the user to 65 | `stormpath.web.forgotPassword.nextUri` 66 | 67 | * If the request prefers `application/json`, respond with `200 OK` and no body. 68 | 69 | ### Options 70 | 71 | ```yaml 72 | stormpath: 73 | web: 74 | forgotPassword: 75 | enabled: null 76 | uri: "/forgot" 77 | view: "forgot-password" 78 | nextUri: "/login?status=forgot" 79 | ``` 80 | 81 | #### enabled 82 | 83 | Default: `null` 84 | 85 | Unless explicitly set to `false`, this endpoint must be automatically enabled if 86 | the default account store for the defined Stormpath application has the password 87 | reset workflow enabled. 88 | 89 | Back to Top 90 | 91 | 92 | #### uri 93 | 94 | Default: `/forgot` 95 | 96 | The URI that we'll attach an interceptor to for requests, if `enabled` is 97 | `true`. 98 | 99 | If `stormpath.web.idSite.enabled` is `true`, redirect the user idSite using the ID Site URL Builder in the SDK. 100 | 101 | Back to Top 102 | 103 | 104 | #### nextUri 105 | 106 | Default: `/login?status=forgot` 107 | 108 | This is the URI we'll redirect the user to after a user has successfully 109 | initialized the Password Reset workflow by submitting an email address. 110 | 111 | Back to Top 112 | 113 | 114 | #### view 115 | 116 | Default: `forgot-password` 117 | 118 | A string key which identifies the view template that should be used. 119 | 120 | Back to Top 121 | 122 | 123 | ## Change Password Endpoint (`/change`) 124 | 125 | This is the endpoint that a user will use if they have received a password 126 | reset email and have clicked on the link in the email. The link will point to 127 | this endpoint, and contain the `sptoken` query parameter. For HTML requests, we 128 | verify the `sptoken` and then render a form that the user will use to submit a 129 | new password. 130 | 131 | 132 | 133 | ### Request Handling 134 | 135 | #### GET Requests 136 | 137 | * If there is a `?sptoken` query parameter in the URL: 138 | 139 | * Make an API request to Stormpath to validate the `sptoken`, but **do not** 140 | consume the token yet. 141 | 142 | * If the `sptoken` is invalid, and the request prefers: 143 | 144 | * `text/html`, redirect the user to `stormpath.web.changePassword.errorUri`. 145 | 146 | * `application/json`, respond with the JSON error from the API, according to 147 | the [Error Handling][] specification. 148 | 149 | * If the `sptoken` is valid, and the request prefers: 150 | 151 | * `text/html`, render the change password view that is defined by 152 | `stormpath.web.changePassword.view`. 153 | 154 | * `application/json`, reply with 200 OK. 155 | 156 | * If there isn't a `?sptoken` query parameter in the URL, and the request 157 | prefers: 158 | 159 | * `text/html`, redirect the user to `stormpath.web.forgotPassword.uri`. 160 | 161 | * `application/json`, send this error: 162 | 163 | ```javascript 164 | { 165 | status: 400, 166 | message: 'sptoken parameter not provided.' 167 | } 168 | ``` 169 | 170 | #### POST Handling 171 | 172 | This endpoint accepts a post from the password change form. The endpoint should 173 | parse the post body as `application/json` or `application/x-www-form-urlencoded`. 174 | 175 | `application/x-www-form-urlencoded` requests look like: 176 | 177 | ``` 178 | POST /change?sptoken=the_sent_token 179 | 180 | password=new%20password 181 | ``` 182 | 183 | `application/json` requests look like: 184 | 185 | ```javascript 186 | POST /change 187 | 188 | { 189 | "sptoken": "the sent token", 190 | "password": "new password" 191 | } 192 | ``` 193 | 194 | -The Stormpath API should be invoked to consume the token and reset the password. 195 | 196 | **Success Responses** 197 | 198 | If the operation is successful (the password has been changed), respond with the 199 | appropriate case: 200 | 201 | * If the request prefers `text/html`: 202 | 203 | * If `autoLogin` is `false`, redirect the user to 204 | `stormpath.web.changePassword.nextUri` 205 | 206 | * If `autoLogin` is `true`, log the user in (create the Oauth2 cookies) and 207 | redirect the user to `stormpath.web.login.nextUri` 208 | 209 | * If the request prefers `application/json`: 210 | 211 | * If `autoLogin` is `false`, respond with `200 OK` and an empty body. 212 | 213 | * If `autoLogin` is `true`, log the user in (create the Oauth2 cookies) and 214 | respond with the authentication response: 215 | 216 | ``` 217 | HTTP/1.1 200 OK 218 | Content-Type: application/json; charset=utf-8 219 | 220 | { 221 | account: { 222 | // the account that was created, stripped of all non-root properties 223 | } 224 | } 225 | ``` 226 | 227 | **Error Responses** 228 | 229 | If an error is encountered, respond with the appropriate case: 230 | 231 | * If the request is `Accept: text/html`: 232 | 233 | * If the error is a password policy validation error, re-render the change 234 | password form and display the error to the user 235 | 236 | * If the error is an invalid or expired token error, redirect the user to 237 | `stormpath.web.changePassword.errorUri` 238 | 239 | * If the request is `Accept: application/json`: 240 | 241 | * Respond with the JSON error from the API, according to the [Error Handling][] 242 | specification. 243 | 244 | ### Options 245 | 246 | ```yaml 247 | stormpath: 248 | web: 249 | changePassword: 250 | enabled: null 251 | autoLogin: false 252 | uri: "/change" 253 | errorUri: "/forgot?status=invalid_sptoken" 254 | nextUri: "/login?status=reset" 255 | view: "change-password" 256 | 257 | ``` 258 | 259 | 260 | 261 | #### enabled 262 | 263 | Default: `null` 264 | 265 | Unless explicitly set to `false`, this endpoint must be automatically enabled if 266 | the default account store for the defined Stormpath application has the password 267 | reset workflow enabled. 268 | 269 | Back to Top 270 | 271 | 272 | #### autoLogin 273 | 274 | Default: `false` 275 | 276 | If `true`, then once a user has successfully reset their password, they will be 277 | automatically logged in and redirected to `stormpath.web.login.nextUri`. 278 | 279 | 280 | Back to Top 281 | 282 | 283 | #### uri 284 | 285 | Default: `/change` 286 | 287 | The URI that we'll attach an interceptor to for requests, if `enabled` is 288 | `true`. 289 | 290 | Back to Top 291 | 292 | 293 | #### errorUri 294 | 295 | Default: `/forgot?status=invalid_sptoken` 296 | 297 | This is the URI we'll redirect a user to if they have arrived with an invalid 298 | `sptoken`. 299 | 300 | Back to Top 301 | 302 | 303 | #### nextUri 304 | 305 | Default: `/login?status=reset` 306 | 307 | This is the URI we'll redirect the user to after they have successfully reset 308 | their account password. 309 | 310 | Back to Top 311 | 312 | 313 | #### view 314 | 315 | A string key which identifies the view template that should be used. 316 | 317 | Back to Top 318 | 319 | [Error Handling]: error-handling.md 320 | -------------------------------------------------------------------------------- /registration.md: -------------------------------------------------------------------------------- 1 | # Account Registration 2 | 3 | ## Feature Description 4 | 5 | This document describes the endpoints and logic that must exist in order to 6 | facilitate self-service registration of user accounts. 7 | 8 | If enabled by `stormpath.web.register.enabled`, our library MUST inspect 9 | incoming requests for `stormpath.web.register.uri`. and determine if a response 10 | is needed, based on our [Content Negotiation Strategy][]. 11 | 12 | GET requests may: 13 | 14 | * Serve a default HTML page with a dynamic registration form. 15 | 16 | * Serve the dynamic registration JSON view model. 17 | 18 | * Redirect the user to ID Site if `stormpath.web.idSite.enabled` is `true`. This 19 | should be done with the ID Site URL Builder in the SDK. 20 | 21 | * Pass on the request. 22 | 23 | POST requests may: 24 | 25 | * Handle a POST request from the default HTML registration form or from a JSON 26 | client. 27 | 28 | ## Default Registration Form 29 | 30 | The default registration form MUST: 31 | 32 | * Require a first name (givenName). 33 | * Require a last name (surname). 34 | * Require an email address. 35 | * Require a password. 36 | 37 | The form MAY be configured by the developer, allowing them to: 38 | 39 | * Make the `givenName` and `surname` fields optional (`required: false`). 40 | * Remove the `givenName` and `surname` fields (`enabled: false`). 41 | * Enable other default Stormpath Account fields (`username`, `middleName`). 42 | * Enable a password confirmation field. 43 | * Make fields hidden, but submittable. 44 | * Define custom fields. 45 | 46 | ## Options 47 | 48 | This is the set of default configuration for the registration feature, it 49 | defines the default fields we support and which ones we want to show by 50 | default. If a field is `enabled`, it will allow data to be POSTed as that parameter. If a field is `visible`, it will be shown on the registration form. 51 | If it's `required` then an error should be returned if the value is not 52 | supplied by the user. 53 | 54 | The developer can define custom fields by creating new field definitions in the 55 | `fields` map. New fields MUST have all the properties that you see on our 56 | default fields. 57 | 58 | ```yaml 59 | stormpath: 60 | web: 61 | register: 62 | enabled: true 63 | uri: "/register" 64 | nextUri: "/" 65 | autoLogin: false 66 | form: 67 | fields: 68 | givenName: 69 | enabled: true 70 | visible: true 71 | label: "First Name" 72 | placeholder: "First Name" 73 | required: true 74 | type: "text" 75 | middleName: 76 | enabled: false 77 | visible: true 78 | label: "Middle Name" 79 | placeholder: "Middle Name" 80 | required: true 81 | type: "text" 82 | surname: 83 | enabled: true 84 | visible: true 85 | label: "Last Name" 86 | placeholder: "Last Name" 87 | required: true 88 | type: "text" 89 | username: 90 | enabled: false 91 | visible: true 92 | label: "Username" 93 | placeholder: "Username" 94 | required: true 95 | type: "text" 96 | email: 97 | enabled: true 98 | visible: true 99 | label: "Email" 100 | placeholder: "Email" 101 | required: true 102 | type: "email" 103 | password: 104 | enabled: true 105 | visible: true 106 | label: "Password" 107 | placeholder: "Password" 108 | required: true 109 | type: "password" 110 | confirmPassword: 111 | enabled: false 112 | visible: true 113 | label: "Confirm Password" 114 | placeholder: "Confirm Password" 115 | required: true 116 | type: "password" 117 | fieldOrder: 118 | - "username" 119 | - "givenName" 120 | - "middleName" 121 | - "surname" 122 | - "email" 123 | - "password" 124 | - "confirmPassword" 125 | view: "register" 126 | ``` 127 | 128 | 129 | #### enabled 130 | 131 | Default: `true` 132 | 133 | This determines if the integration will handle the registration route, defined 134 | by `stormpath.web.registration.uri` 135 | 136 | 137 | #### uri 138 | 139 | Default: `/register` 140 | 141 | The URI that our integration should bind to for handling GET and POST requests 142 | for the `uri`. 143 | 144 | 145 | #### nextUri 146 | 147 | Default: `/` 148 | 149 | Where to send the user after successful registration, if [autoLogin](#autoLogin) 150 | is `true`. 151 | 152 | 153 | #### autoLogin 154 | 155 | If enabled, the user should be automatically logged in and redirected to the 156 | `nextUri` after a successful registration. If email verification is enabled, this will automatically log in the user after email verification. 157 | 158 | 159 | #### form.fields 160 | 161 | A map of field definitions. The name of the property, e.g. `givenName`, should 162 | be applied to the HTML input element as the `name` attribute. Each field 163 | definition must have the following properties: 164 | 165 | * `enabled` - Allows the other options to take effect, and saves data if applications to POST to this parameter. 166 | 167 | * `visible` - Determines if this field should be shown in the registration form. If false, this field should not be present in the view model or HTML. 168 | 169 | * `label` - The value that is shown as a descriptive label for the field. 170 | 171 | * `placeholder` - The placeholder attribute value for the HTML input element. 172 | 173 | * `required` - Determines if this field should be required by the user. This 174 | attribute should be applied to the input element, and validated when parsing 175 | a form post. 176 | 177 | * `type` - the value to apply to the `type` attribute of HTML input element. 178 | 179 | The intention of this configuration is to give the developer some simple control 180 | over the basic things: labels, placeholders, and text input types. Mileage will 181 | vary with other HTML input types, as some of those types require additional 182 | attributes that we do not define in our configuration at this time. 183 | 184 | #### form.fieldOrder 185 | 186 | A configurable array that allows the developer to change the order in which the 187 | fields are rendered. 188 | 189 | 190 | #### view 191 | 192 | Default: `register` 193 | 194 | A string key which identifies the view template that should be used. The 195 | default value may look different for your framework. The point of this value 196 | is to allow the developer to override our default view with their own. 197 | 198 | Back to Top 199 | 200 | 201 | 202 | ## Registration View Model 203 | 204 | The registration view model should be returned when the request prefers 205 | `application/json` and `stormpath.web.produces` contains `application/json`. 206 | This is for front-end or mobile clients that need to dynamically know how to 207 | render the registration form. 208 | 209 | The model should have: 210 | 211 | * A list of fields, as defined by `stormpath.web.register.form.fields`, and 212 | ordered by `stormpath.web.register.form.fieldOrder`. Fields should only be in 213 | the list if their `enabled` property is `true`. As such the enabled property 214 | can be omitted from each list element. Any enabled fields not in `fieldOrder` should be appended to the end of the list. 215 | 216 | * A list of providers, such as social providers or SAML providers. Providers 217 | are found by looking at the account store mappings of the specified 218 | application. 219 | * Social providers will need to expose the `clientId`, as front-end 220 | applications will need this. 221 | * SAML providers need to provide the name of the directory, so that we know 222 | what text to use for the button, and the href, so that we can place this 223 | value into the SAML request (as a query parameter in the link that the 224 | button points to). 225 | * The ordering of this list should follow the ordering of the account store 226 | mappings. **NOTE**: this may change in the future. 227 | 228 | Example view model definition: 229 | 230 | ```javascript 231 | { 232 | "form": { 233 | "fields": [ 234 | { 235 | "label": "First Name", 236 | "name": "givenName", 237 | "placeholder": "First Name", 238 | "required": true, 239 | "type": "text" 240 | }, 241 | { 242 | "label": "Last Name", 243 | "name": "surname", 244 | "placeholder": "Last Name", 245 | "required": true, 246 | "type": "text" 247 | }, 248 | { 249 | "label": "Email", 250 | "name": "email", 251 | "placeholder": "Email", 252 | "required": true, 253 | "type": "email" 254 | }, 255 | { 256 | "label": "Password", 257 | "name": "password", 258 | "placeholder": "Password", 259 | "required": true, 260 | "type": "password" 261 | }, 262 | { 263 | // ... other fields, as configured 264 | } 265 | ], 266 | }, 267 | "accountStores": [ 268 | { 269 | "href": "https://api.stormpath.com/v1/directories/whatev" 270 | "name": "Name of account store" 271 | "provider": { 272 | "href": "...", 273 | "providerId": "saml" 274 | //DO NOT EXPOSE ANY OTHER SAML DATA 275 | } 276 | }, 277 | { 278 | "href": "https://api.stormpath.com/v1/directories/whatev2" 279 | "name": "Google" 280 | "provider": { 281 | "href": "href of the directory", 282 | "providerId": "google", 283 | "clientId": "WHATEVS", 284 | "scope": "email profile" // <-- from stormpath.web.social.google.scope 285 | //DO NOT EXPOSE ANY SECRET HERE. NO CLIENT SECRETS! 286 | } 287 | } 288 | ] 289 | } 290 | ``` 291 | 292 | 293 | 294 | ## POST Body Format 295 | 296 | The content type of the POST body may be `application/x-www-form-urlencoded` or 297 | `application/json`, the framework integration should parse both. 298 | 299 | This section uses JSON for example purposes. 300 | 301 | ### Required Fields 302 | 303 | Every POST body MUST contain, at a minimum, these fields: 304 | 305 | ```javascript 306 | { 307 | "email": "robert@stormpath.com", 308 | "password": "changeme" 309 | } 310 | ``` 311 | 312 | If those fields are omitted, it is an error. If any of the required fields are 313 | omitted, that is also an error. 314 | 315 | ### Custom Fields 316 | 317 | The developer should be able to define their own fields in the 318 | `stormpath.web.register.form.fields` block. If the configured field has 319 | `required: true`, the form parser should error if the user does not submit 320 | the value. 321 | 322 | Custom fields can be supplied on the root of the post body, or as child 323 | properties of the custom data field. Regardless of how they are supplied, the 324 | library should apply these properties to the account's custom data object. 325 | 326 | ### Disabled or Unknown Fields 327 | 328 | If the post contains a field that is disabled or not defined by the developer, the 329 | library MUST reject the request with an error. We do not allow arbitrary data 330 | to be posted to an account's custom data object. 331 | 332 | A post with custom fields may look like this: 333 | 334 | ```javascript 335 | { 336 | "email": "robert@stormpath.com", 337 | "password": "d", 338 | "customValue": "custom value can be on root object or in customData object", 339 | "customData": { 340 | "hello": "world" 341 | } 342 | } 343 | ``` 344 | 345 | Back to Top 346 | 347 | ## POST Error Handling 348 | 349 | For any errors indicated in this document, the following should happen: 350 | 351 | * If the request prefers `text/html`, the response should be 200 OK and the HTML 352 | form should be re-rendered with a UX that indicates which field is in error and 353 | what can be done to fix the problem. 354 | 355 | * If the request prefers `application/json`, the response should be HTTP 400 and 356 | a JSON object of the format: 357 | 358 | * Respond with the JSON error from the API, according to the [Error Handling][] 359 | specification. 360 | * If the error is not from the API (i.e. it's a form validation error) then 361 | create a custom error message, also following the [Error Handling][] 362 | specification. 363 | 364 | ## POST Response Handling 365 | 366 | This describes how we handle the response, if an account has been successfully 367 | created. 368 | 369 | * If the request prefers `application/json`, the response should be status 200 370 | with a JSON body, where the body contains the account object, but ONLY the 371 | root properties of the account should be presented. All linked resources must 372 | be omitted, to prevent leakage of sensitive user data. For example: 373 | 374 | ``` 375 | HTTP/1.1 200 OK 376 | Content-Type: application/json; charset=utf-8 377 | 378 | { 379 | "account": { 380 | { 381 | "href": "https://api.stormpath.com/v1/accounts/xxx", 382 | "username": "foo", 383 | "modifiedAt": "2016-01-26T20:50:03.931Z", 384 | "status": "ENABLED", 385 | "createdAt": "2015-10-13T20:54:22.215Z", 386 | "email": "bar@stormpath.com", 387 | "middleName": null, 388 | "surname": "foo", 389 | "givenName": "bar" 390 | "fullName": "bar foo" 391 | } 392 | } 393 | } 394 | ``` 395 | 396 | * If the request prefers `text/html`, and... 397 | * The newly created account's status is ENABLED and [autoLogin](#autoLogin) 398 | is: 399 | * `True`: issue a 302 Redirect to the `nextUri` and log the user in (create 400 | the OAuth2 token cookies). 401 | * `False`: 302 redirect the user to `stormpath.web.login.uri`, and append 402 | the query parameter `?status=created` (see [login page status messages][]). 403 | 404 | * The newly created account status is UNVERIFIED, then 302 redirect the user 405 | to `stormpath.web.login.uri` and append the query parameter 406 | `?status=unverified` (see [login page status messages][]). 407 | 408 | Back to Top 409 | 410 | [Content Negotiation Strategy]: requests.md#content-type-negotiation 411 | [Error Handling]: error-handling.md 412 | [login page status messages]: login.md#status-messages 413 | [social]: social.md 414 | -------------------------------------------------------------------------------- /requests.md: -------------------------------------------------------------------------------- 1 | ## Request Behavior 2 | 3 | This document describes how our framework integrations should treat incoming 4 | requests and how it should respond, based on the nature of the request. 5 | 6 | ### Filter-Based approach 7 | 8 | Our library should try to respond to the verbs and content types that it is 9 | configured for, but "pass" on the request otherwise. We take this approach so 10 | that we don't interfere with the developers application. 11 | 12 | ### Disabled Routes 13 | 14 | If the route is disabled in configuration, the request should be passed on to 15 | the next component/filter of the underlying web framework handler, likely 16 | resulting in a 404 error. 17 | 18 | ### Content-Type Negotiation 19 | 20 | At every point where our library can render an HTML or JSON response, the 21 | following decision tree must be followed: 22 | 23 | The following logic applies to all incoming requests: 24 | 25 | * If the request contains no Accept header, treat it as `Accept: */*`. 26 | 27 | * If the request prefers `Accept: */*`, the first Content-Type in 28 | `stormpath:web:produces` will be returned. 29 | 30 | * If the request prefers an Accept type that is in `stormpath:web:produces`, 31 | that Content-Type will be returned, after following the order of contet types in `stormpath.web.produces` 32 | 33 | * If the request specifies an Accept type that is *not* in 34 | `stormpath:web:produces`, the integration should pass on the request 35 | 36 | This can also be visualized as a flow diagram: 37 | 38 | 39 | ``` 40 | Is Accept Header 41 | undefined or */* ? 42 | | 43 | +-------+-------+ 44 | | | 45 | Yes No 46 | | | 47 | Serve first | 48 | type in produces | 49 | config | 50 | | 51 | Is HTML preferred, 52 | over JSON, in the 53 | Accept Header? 54 | | 55 | +---------------+---------------+ 56 | | | 57 | Yes No 58 | | | 59 | Is HTML Is JSON in 60 | in produces? Accept Header? 61 | | | 62 | +-------+-------+ +-------+-------+ 63 | | | | | 64 | Yes No Yes No 65 | | | | | 66 | Serve the HTML | Is JSON | 67 | view as defined | in produces? | 68 | by .view | | | 69 | | +-------+-------+ | 70 | | | | | 71 | | Yes No | 72 | | | | | 73 | | Serve JSON | | 74 | | view model | | 75 | | | | 76 | +-----------------------+-------+ 77 | | 78 | Pass, developer needs to serve a SPA. Otherwise 79 | the framework will let this fall through to it's 80 | default 404 response. 81 | ``` 82 | -------------------------------------------------------------------------------- /single-page-apps.md: -------------------------------------------------------------------------------- 1 | # Single Page Applications 2 | 3 | Our integration needs to integrate well with Single Page Applications (SPAs), 4 | such as AngularJS and React. We've taken the approach that our frameworks 5 | should not be involved with rendering these SPA assets, but they should be 6 | configurable to support these primary developer stories: 7 | 8 | * **I just want a JSON interface that my front-end library can use to integrate 9 | with Stormpath, I already have an asset pipeline for my SPA.** 10 | 11 | * **I want the `/login` URL to serve my SPA, but when I post to it I want 12 | Stormpath to handle the login post.** 13 | 14 | In order to achieve these stories, we need to give the developer a way to 15 | "disable" the default HTML responses that we provide, e.g. to turn off our 16 | default login and registration forms. To achieve this we have the `produces` 17 | option, which is a whitelist of content types that this library CAN produce, 18 | and is ordered by preference. 19 | 20 | As such, to achieve either of the stories above, the developer would remove 21 | `text/html` from the default list, like this: 22 | 23 | 24 | ```yaml 25 | stormpath: 26 | web: 27 | produces: 28 | - application/json 29 | ``` 30 | 31 | In this configuration, we do not serve our default HTML pages when we see 32 | requests for URLs that we handle. As such, it is expected that the developer 33 | will be serving those assets themselves. 34 | 35 | This relies on our [Content Negotiation Strategy][]. 36 | 37 | 38 | ## View Models 39 | 40 | The login and registration views are dynamic. The registration form has 41 | configurable fields, and both login and registration need to dynamically render 42 | buttons for any providers (Google, Facebook, SAML) that are mapped to the 43 | specified Stormpath application. 44 | 45 | For that reason, we support JSON GET requests of the `/login` and `/register` 46 | views. The response is a view model that the client can use to render the 47 | form appropriately. 48 | 49 | Please see [login][] and [registration][] for specific information on each 50 | model. 51 | 52 | 53 | ## Background: SPA "Routing" 54 | 55 | Typical SPAs have an "entry", which is an HTML file (usually index.html) that 56 | loads several JavaScript files and CSS files. Once loaded, the application 57 | will bootstrap. 58 | 59 | "Routing" for SPAs refers to the strategy by which URLs map onto specific views 60 | or states in the SPA. 61 | 62 | For example, you may have URLs like this: 63 | 64 | ``` 65 | http://myapp.com 66 | http://myapp.com/blog 67 | http://myapp.com/blog/:postId 68 | ``` 69 | 70 | Or like this: 71 | 72 | ``` 73 | http://myapp.com 74 | http://myapp.com/#blog 75 | http://myapp.com/#blog/:postId 76 | ``` 77 | 78 | The latter is referred to as "hash bang" routing. This is a simpler situation 79 | and does not require coordination from the server. 80 | 81 | The former is referred to as "HTML5" routing, as it leverages an HTML5 API in 82 | the browser. This API allows the user to navigate to absolute URLS, but the 83 | browser will not make GET requests of the server for each URL. The browser 84 | merely updates the URL in the URL bar, and expects that the SPA will observe 85 | this and render the appropriate view. The SPA will likely make a JSON GET 86 | request of the server, to load the data that is required to populate the view. 87 | 88 | However, if the user hits the reload button (or visits the URL directly, from 89 | another site) a GET request *will* be made of the server. In this situation, 90 | the server needs to render the entry for the SPA - so that the SPA can 91 | bootstrap and then render the appropriate view. 92 | 93 | 94 | [Content Negotiation Strategy]: requests.md#content-type-negotiation 95 | [login]: login.md 96 | [registration]: registration.md -------------------------------------------------------------------------------- /social.md: -------------------------------------------------------------------------------- 1 | # Social Provider Integrations 2 | 3 | ## Feature Description 4 | 5 | The framework integration should provide convenience support for the Social 6 | Login providers that the Stormpath API supports. At the time of writing we 7 | support: 8 | 9 | * Facebook 10 | * GitHub 11 | * Google 12 | * LinkedIn 13 | 14 | ## Background: Social Login Workflows 15 | 16 | Social providers typically implement two authentication workflows, which we will 17 | describe in detail: 18 | 19 | ### Page-based Redirect Workflow 20 | 21 | In this situation, the user leaves your login page by redirect and is taken to 22 | the provider for authentication. Once authentication is complete, the user is 23 | redirected back to a "callback" URI on your website. This callback will provide 24 | an access token or access code as a query parameter. Your server uses a 25 | confidential keypair of the provider to validate the token/code, then retrieves 26 | the account data of the authenticated user. 27 | 28 | In order to support this workflow, the framework integration must: 29 | 30 | * Parse the provider configuration of the specified Stormpath application (see 31 | next section). 32 | 33 | * Provide callback handlers for this workflow. See "Implementing Page-Based 34 | Workflows" in this document. 35 | 36 | ### AJAX-based Workflow 37 | 38 | In this situation the user does not leave your login page. Instead a popup 39 | window is created, and the user authenticates with the provider in that window. 40 | The popup window is created by a JavaScript API in the browser, and when the 41 | user is done with the popup window (either by authenticating or rejecting the 42 | prompt) the JavaScript API will call back to your JavaScript application. If 43 | the user has authenticated you will be provided with a token/code, which you 44 | must send to your server and validate with your confidential provider 45 | credentials. At this point the workflow is identical to the page-base redirect 46 | workflow. 47 | 48 | In order to support this worklow, the framework integration must do the 49 | following: 50 | 51 | * Parse the provider configuration of the specified Stormpath application (see 52 | next section). 53 | 54 | * Accept the access token or code via the login endpoint that this framework 55 | provides, see [login][] for implementation spec. 56 | 57 | * Render buttons for social login, on the registration and login views (or 58 | view model), see [login][] and [registration][]. 59 | 60 | 61 | ## Parsing Provider Configuration 62 | 63 | During framework bootstrap, the specified Stormpath application should be parsed 64 | for it's account store mappings. If any of these account stores are a social 65 | provider, the following must be done: 66 | 67 | * Render a login button on the login page (see [login][]). 68 | 69 | * Create a callback handler for the provider's page-based redirect flow (see 70 | next section) 71 | 72 | 73 | ## Implementing Page-Based Workflows 74 | 75 | The library must implement provider callbacks for all social provider 76 | directories that are mapped to the specified Stormpath application. The 77 | callback URLs for each provider should take the form of: 78 | 79 | > `` 80 | 81 | Where `providerId` is the property that is found in the `provider` object of 82 | the Stormpath directory, e.g. `google` or `facebook`. 83 | 84 | When the callback URL is requested with a GET request, the user is being 85 | redirected back to the server after authentication with the provider. The 86 | callback handler must complete the following tasks: 87 | 88 | * Parse the callback data from the provider to get the authorization code 89 | * Perform the authorization code exchange, if needed. 90 | * Retrieve or create the account, by posting the provider data to Stormpath 91 | * Create the OAuth2 token cookies for the user 92 | * Redirect the user to `stormpath.web.login.nextUri` 93 | 94 | If any of these tasks fail, an error should be immediately rendered to the user 95 | and the next task should not be attempted. 96 | 97 | The error should be displayed on the login form in the same way other login 98 | errors are shown. 99 | 100 | Recommendation: the callback endpoints should not handle access tokens in the query string because applications that log URLs will inadvertently log access tokens, which presents a security risk. 101 | 102 | ## Implementing Popup-Based Workflows 103 | 104 | Single-page applications will need to query the server to determine which social 105 | providers are configured, and which fields to render for the login or 106 | registration views. In either context, the front-end application should make 107 | a GET request to the login or registration endpoint, to fetch the view model 108 | as JSON. The view model will contain the necessary information. See [login][] 109 | and [registration][] pages for the view model definitions. 110 | 111 | ## Access Tokens and Authorization Codes 112 | 113 | This is a reference to supported functionality from both Stormpath and different social providers with their OAuth flows. 114 | 115 | **Stormpath REST API Support** 116 | 117 | |Social Provider|Authorization Code|Access Token| 118 | |---|---|---| 119 | |Facebook|NO|YES| 120 | |Google|YES|YES| 121 | |LinkedIn|YES|YES| 122 | |GitHub|NO|YES| 123 | 124 | **Social Provider OAuth Support** 125 | 126 | |Social Provider|Authorization Code|Implicit| 127 | |---|---|---| 128 | |Facebook|YES|YES| 129 | |Google|YES|NO| 130 | |LinkedIn|YES|NO| 131 | |GitHub|YES|NO| 132 | 133 | Implementation of social login is up to the framework integration, but here are some potential ways to approach it: 134 | 135 | * **Facebook**: Use the Facebook [Javascript SDK](https://developers.facebook.com/docs/facebook-login/web) and use the popup-based workflow to get an access token, and POST it to the `/callbacks/facebook` endpoint. Send the access token to Stormpath. 136 | * **Facebook**: Redirect the user to the [Facebook OAuth server](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow). The user will be redirected back to the `/callbacks/facebook` endpoint. Perform the authorization code exchange for an access token, and send the access token to Stormpath. 137 | * **Google**: Redirect the user to the [Google OAuth server](https://developers.google.com/identity/protocols/OAuth2WebServer#preparing-to-start-the-oauth-20-flow). The user will be redirected to the `/callbacks/google` endpoint. Send the authorization code to Stormpath, which will do the exchange. 138 | * **LinkedIn**: Redirect the user to the [LinkedIn OAuth server](https://developer.linkedin.com/docs/oauth2). The user will be redirected to the `/callbacks/linkedin` endpoint. Send the authorization code to Stormpath, which will do the exchange. 139 | * **GitHub**: Redirect the user to the [GitHub OAuth server](https://developer.github.com/v3/oauth/). The user will be redirected to the `/callbacks/github` endpoint. Perform the authorization code exchange for an access token, and send the access token to Stormpath. 140 | 141 | Back to Top 142 | 143 | [login]: login.md 144 | [registration]: registration.md -------------------------------------------------------------------------------- /user-agent.md: -------------------------------------------------------------------------------- 1 | # User Agent 2 | 3 | Each framework integration is responsible for generating a `User-Agent` string containing the library and runtime version, as well as optional client SDK versions. These strings are passed down to the SDK layer to be sent to the Stormpath API. 4 | 5 | ## Client SDK Tracking 6 | 7 | We need to collect usage metrics on our front-end integrations (Angular, React) 8 | and our mobile integrations (iOS, Android). As such, the following needs to be 9 | implemented: 10 | 11 | * In the front-end/mobile library, if a request to the framework is on the same 12 | domain (won't cause a cross-domain error), the library should add the 13 | following header: 14 | 15 | > X-Stormpath-Agent: stormpath-sdk-angularjs/0.9.0 angular/1.4.7 16 | 17 | This value indicates the version of our library, and the version of the 18 | framework that is being used. 19 | 20 | * If the `X-Stormpath-Agent` header is seen on an incoming request, the 21 | framework integration should capture this value and include it in the string passed down to the SDK on any requests that are made to fulfill the framework response. 22 | 23 | Note: there will not be a 1-1 mapping from framework endpoint to REST API 24 | endpoint. This is okay. We aren't looking to get metrics on specific endpoint 25 | requests. Rather, we simply want to know if a tenant is using a given front-end 26 | SDK or not. 27 | 28 | ## Format 29 | 30 | The framework `User-Agent` string will follow this format: 31 | 32 | ``` 33 | [client SDK token(s)?] [framework token] [web library token(s)] 34 | ``` 35 | 36 | A complete framework integration string will look like the following: 37 | 38 | ``` 39 | stormpath-sdk-angularjs/0.9.0 angular/1.4.7 express-stormpath/3.0.0 express/4.13.4 40 | |----------- client SDK tokens -----------| |-- framework token --| | web lib tkn | 41 | ``` 42 | 43 | The underlying SDK will [provide a mechanism](https://github.com/stormpath/stormpath-sdk-spec/blob/master/specifications/user-agent.md) for the framework integration to pass this string to the Client object. 44 | 45 | ### Token Groups 46 | 47 | #### Client SDK Tokens *(if any)* 48 | 49 | The integration will look for an `X-Stormpath-Agent` from the frontend client, as described in Client SDK Tracking above. 50 | 51 | This group is optional. If not null or empty, the agent tokens should be **prepended** to the rest of the framework user agent string. 52 | 53 | #### Framework Token 54 | 55 | The framework integration name and version separated by a slash '/'. For example, `express-stormpath/3.0.0`. 56 | 57 | #### Web Library Token 58 | 59 | Any web libraries in use, and their versions. For example, `express/4.13.4`. 60 | -------------------------------------------------------------------------------- /user-context.md: -------------------------------------------------------------------------------- 1 | # Current User Context 2 | 3 | This endpoint supports rich browser clients such as AngularJs, and mobile 4 | clients. This endpoint allows the client application to fetch the account 5 | object of the currently authenticated user. 6 | 7 | We must provide this endpoint because, for security reasons, we don't allow the 8 | client to store any information about the user. It must be fetched from the 9 | server at runtime, and the request must be authenticated. 10 | 11 | ### Configuration Options 12 | 13 | This route is enabled by default at the `/me` uri, but this endpoint can be 14 | changed or disabled entirely with these options: 15 | 16 | ```yaml 17 | stormpath: 18 | web: 19 | me: 20 | enabled: true 21 | uri: "/me" 22 | ``` 23 | 24 | ### Authentication 25 | 26 | This endpoint requires authentication, and accepts all forms of authentication. 27 | 28 | ### Endpoint Response 29 | 30 | This endpoint should always respond with `Content-Type: application/json`, and 31 | the body should be the JSON representation of the currently authenticated user. 32 | 33 | By default, all linked resources should be removed from the object. However the 34 | developer can opt-in to expansion through configuration (see next section). In 35 | this situation the linked resource can be returned. 36 | 37 | This endpoint must always send the following headers, so that the browser does 38 | not cache this response: 39 | 40 | ``` 41 | Cache-Control: no-cache, no-store 42 | Pragma: no-cache 43 | ``` 44 | 45 | Example default response body: 46 | 47 | ```json 48 | { 49 | "account": { 50 | "href": "https://api.stormpath.com/v1/accounts/xxx", 51 | "username": "robert@stormpath.com", 52 | "email": "robert@stormpath.com", 53 | "givenName": "Robert", 54 | "middleName": null, 55 | "surname": "Damphousse", 56 | "fullName": "Robert Damphousse", 57 | "status": "ENABLED", 58 | "createdAt": "2014-04-07T16:38:44.000Z", 59 | "modifiedAt": "2014-08-28T22:35:10.000Z", 60 | "emailVerificationToken": null 61 | } 62 | } 63 | ``` 64 | 65 | ### Expansion Options 66 | 67 | The developer can opt-in to expanding the account's linked resources, for 68 | example: 69 | 70 | ```yaml 71 | stormpath: 72 | web: 73 | me: 74 | expand: 75 | groups: true 76 | ``` 77 | 78 | For collection resources, the default behavior should be to include **all** items in the expanded collection. Optionally, the framework integration can allow the developer to override this behavior. 79 | 80 | #### Expanded response 81 | 82 | If a linked collection is expanded (such as `groups`), the response should follow this format: 83 | 84 | ```json 85 | "collection": { 86 | "size": 10, 87 | "items": [ 88 | { "stuff": "here" }, 89 | { "stuff2": "here2" } 90 | ] 91 | } 92 | ``` 93 | 94 | One or more expanded collections may be included in the `/me` endpoint response, like so: 95 | 96 | ```json 97 | { 98 | "account": { 99 | "href": "https://api.stormpath.com/v1/accounts/xxx", 100 | "groups": { 101 | "size": 10, 102 | "items": [ 103 | { "stuff": "here" }, 104 | { "stuff2": "here2" } 105 | ] 106 | }, 107 | "otherStuff": "(truncated for example)" 108 | } 109 | } 110 | ``` 111 | --------------------------------------------------------------------------------